還原一個 Windows 10 Metro 佈局

前言

Win10 Metro 相較於前一代徹底扁平化風格的 Win8 Metro 在動畫效果與交互體驗上有了比較大的差別,那麼想要實現一個較爲逼真的Win10 Metro須要哪些動畫效果呢?css

真的是Windows 10 Metro嗎?

先來看一下這個Demo,看似複雜的交互實現起來其實並不難,下面咱們就來拆解一下其中的動畫效果以及實現原理吧。html

Metro動畫效果與實現

1. 3D旋轉

Win10 Metro的一個比較顯著的特色就是磁貼的3D旋轉。vue

原版

實現

咱們將每個磁貼展現爲橫截面爲正方形的長方體,而後經過css旋轉動畫來實現3D旋轉。git

html

<div class="scene">
  <div class="box-container">
    <div class="front">
    </div>
    <div class="back">
    </div>
    <div class="top">
    </div>
    <div class="bottom">
    </div>
    <div class="right">
    </div>
    <div class="left">
    </div>
  </div>
</div>
複製代碼

在html結構中,scene節點的主要做用是做爲一個場景容器,有了scene咱們即可以對場景中的屬性做出一些調整,例如經過調節perspective,便可加強或者減弱長方體在旋轉時的立體感。 box-container是整個長方體的容器節點,其中包含了長方體的6個面節點front, back, top, bottom, right, leftgithub

css

.scene {
  position: relative;
  width: 300px;
  height: 150px;
  perspective: 700px;
}
.box-container {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transition: transform 0.5s;
  transform-origin: 50% 50% -75px;
}
.front {
  background-color: rgba(3,122,241,0.5);
  position: absolute;
  width: 300px;
  height: 150px;
}
.back {
  background-color: rgba(241,3,3,0.5);
  position: absolute;
  width: 300px;
  height: 150px;
  transform: translateZ(-150px) rotateZ(180deg) rotateY(180deg);
}
.top {
  background-color: rgba(3,241,122,0.5);
  position: absolute;
  width: 300px;
  height: 150px;
  transform: translate3d(0,-75px,-75px) rotateX(90deg);
}
.bottom {
  background-color: rgba(241,241,3,0.5);
  position: absolute;
  width: 300px;
  height: 150px;
  transform: translate3d(0,75px,-75px) rotateX(-90deg);
}
.left {
  background-color: rgba(270,97,48,0.5);
  position: absolute;
  width: 150px;
  height: 150px;
  transform: translate3d(-75px,0,-75px) rotateY(-90deg);
}
.right {
  background-color: rgba(30,97,48,0.5);
  position: absolute;
  width: 150px;
  height: 150px;
  transform: translate3d(225px,0,-75px) rotateY(90deg);
}

複製代碼

在css中,有如下幾點值得注意的地方瀏覽器

  1. .box-container中使用了transform-style: preserve-3d來保留子節點在3D變換時的3D座標。並用了transform-origin: 50% 50% -75px.box-container的旋轉原點放在了長方體的中心。
  2. 爲了使長方體背面的內容在旋轉後不顛倒,在.back的定義中咱們須要在transform中額外添加rotateZ(180deg)預先顛倒背面的內容。
  3. 爲了保持每一面中內容不由於Z軸的座標而放大或者縮小,咱們始終保持了當前正對觀察者面的Z座標0

Demo

3D旋轉的demo







佈局

2. 傾斜

在點擊並按住磁貼的時,磁貼會出現一個向點擊位置傾斜的動畫。動畫

原版

實現

若是仔細觀察原版會發現,面的傾斜角會隨着按壓位置的不一樣而產生不一樣的變化。當按壓位置靠近磁貼邊緣時,傾斜角會變大一些;而當按壓位置靠近磁貼中心時,傾斜角會隨之減少。總結以上的規律咱們能夠得出:ui

以磁貼的中心做爲座標系原點(0, 0),當點擊位置爲(x, y)時,X軸上的傾斜角Ɵx ∝ |y|,而Y軸上的傾斜角 Ɵy ∝ |x|。spa

html

<div class="container">
  <div class="tile">
    <span>Hello World</span>
  </div>
</div>
複製代碼

css

.container {
  width: 200px;
  height: 200px;
  perspective: 700px;
}
.tile {
  background-color: #2d89ef;
  width: 100%;
  height: 100%;
  transition: transform 0.5s;
  text-align: center;
  color: white;
}
複製代碼

js

const maxTiltAngle = 30; // 設置最大傾斜角度

const container = document.getElementsByClassName('container')[0];
const tile = document.getElementsByClassName('tile')[0];
const boundingRect = container.getBoundingClientRect();

const tilt = event => {
  // 計算鼠標相對於容器的位置
  const relativeX = event.pageX - (boundingRect.left + window.scrollX);
  const relativeY = event.pageY - (boundingRect.top + window.scrollY);
  // 將原點從容器左上角移至容器中心
  const normalizedX = relativeX - boundingRect.width / 2;
  const normalizedY = -(relativeY - boundingRect.height / 2);
  // 計算傾斜角
  const tiltX = normalizedY / (boundingRect.height / 2) * maxTiltAngle;
  const tiltY = normalizedX / (boundingRect.width / 2) * maxTiltAngle;
  // 傾斜
  tile.style.transform = `rotateX(${tiltX}deg) rotateY(${tiltY}deg)`;
}

const recover = () => {
  // 恢復傾斜
  tile.style.transform = '';
}

container.addEventListener('mousedown', tilt);
container.addEventListener('mouseup', recover);
container.addEventListener('mouseleave', recover);

複製代碼

在傾斜的實現上,有如下幾點須要注意的地方

  1. 以長爲L寬爲W的磁貼中心做爲座標系原點(0, 0),當點擊位置爲(x, y)時,實現中使用的公式爲:X軸上的傾斜角Ɵx = y / (W / 2) * Ɵmax,而Y軸上的傾斜角 Ɵy = x / (L / 2) * Ɵmax。
  2. 僅在mouseup事件中恢復傾斜是不夠的,在mouseleave的時候也須要恢復傾斜。

Demo

傾斜的demo







3. 懸停光暈

當鼠標懸停在磁鐵上時,磁貼上會有一個跟隨鼠標移動的光圈。

原版

實現

光暈從中心至外圍顏色漸漸淡化,光暈中心的位置會隨着鼠標的移動而移動。

html

<div class="container">
  <div class="hoverLayer">
  </div>
  <div class="hoverGlare">
  </div>
</div>
複製代碼

css

.container {
  position: relative;
  background-color: #000;
  width: 200px;
  height: 200px;
  overflow: hidden;
}

.hoverLayer {
  position: absolute;
  z-index: 1;
  width: 100%;
  height: 100%;
}

.hoverGlare {
  position: absolute;
  background-image: radial-gradient(circle at center, rgba(255,255,255, 0.7) 0%, rgba(255,255,255,0.1) 100%);
  transform: translate(-100px, -100px);
  width: 400px;
  height: 400px;
  opacity: 0.4;
}
複製代碼

js

const boundingRect = document.getElementsByClassName('container')[0].getBoundingClientRect();

const hoverGlare = document.getElementsByClassName('hoverGlare')[0];

const glare = event => {
  // 計算鼠標相對於容器的位置
  const relativeX = event.pageX - (boundingRect.left + window.scrollX);
  const relativeY = event.pageY - (boundingRect.top + window.scrollY);
  // 將原點從容器左上角移至容器中心
  const normalizedX = relativeX - boundingRect.width / 2;
  const normalizedY = relativeY - boundingRect.height / 2;
  // 調整光暈透明度及位置
  hoverGlare.style.opacity = 0.4;
  hoverGlare.style.transform = `translate(${normalizedX}px, ${normalizedY}px) translate(-${boundingRect.width / 2}px, -${boundingRect.height / 2}px)`;
}

const resetGlare = () => {
  // 隱藏光暈
  hoverGlare.style.opacity = 0;
}

const hoverLayer = document.getElementsByClassName('hoverLayer')[0];

hoverLayer.addEventListener('mousemove', glare);
hoverLayer.addEventListener('mouseleave', resetGlare);
複製代碼

在光暈的實現上,有如下幾點須要注意的地方

  1. 咱們使用了z-index1.hoverLayer來看成鼠標事件節點,避免由於子節點覆蓋父節點而產生鼠標定位不許確的問題。
  2. 咱們建立了一個2倍於容器寬高的光暈懸浮層,並經過translate移動這個懸浮層來實現高效率的光暈位置變換。

Demo

懸停光暈的demo







4. 點擊波紋

當鼠標點擊磁貼時,在點擊位置會造成圓形向外擴散的波紋動畫

原版

實現

咱們能夠看到圓形的波紋由點擊位置開始向外漸漸擴散直至消散。

html

<div class="tile">
  <div class="clickGlare">
  </div>
</div>
複製代碼

css

.tile {
  position: relative;
  width: 200px;
  height: 200px;
  background-color: #000;
  overflow: hidden;
}

.clickGlare {
  position: absolute;
  width: 90px;
  height: 90px;
  border-radius: 50%;
  opacity: 0;
  filter: blur(5px);
  background-image: radial-gradient(rgba(255, 255, 255, 0.7) 0%, rgba(255, 255, 255, 0) 100%);
}

.ripple {
  animation-name: ripple;
  animation-duration: 1.3s;
  animation-timing-function: ease-in;
}

@keyframes ripple {
  0% {
    opacity: 0.5;
  }

  100% {
    transform: scale(5);
    opacity: 0;
  }

}
複製代碼

js

const tile = document.getElementsByClassName('tile')[0];
const boundingRect = tile.getBoundingClientRect();
const clickGlare = document.getElementsByClassName('clickGlare')[0];

const ripple = event => {
  // 僅當節點的class中不含ripple時執行
  if (clickGlare.classList.contains('ripple')) return;
  // 計算鼠標相對於容器的位置
  const relativeX = event.pageX - (boundingRect.left + window.scrollX);
  const relativeY = event.pageY - (boundingRect.top + window.scrollY);
  // 根據鼠標位置調整波紋的中心位置
  clickGlare.style.top = `${relativeY - 45}px`;
  clickGlare.style.left =  `${relativeX - 45}px`;
  // 添加波紋動畫
  clickGlare.classList.add('ripple');
}

const resetRipple = () => {
  // 移除波紋動畫
  clickGlare.classList.remove('ripple');
}

tile.addEventListener('mousedown', ripple);
clickGlare.addEventListener('animationend', resetRipple);
複製代碼

在點擊波紋的實現上,有如下幾點須要注意的地方

  1. 爲了使得波紋動畫更加近似於原版,咱們使用了filter: blur這一條css,這條css可能會引發老版本瀏覽器或者IE/Edge中的兼容性問題。

Demo

點擊波紋的demo







小結

若是將以上的動畫結合起來,咱們就能夠實現一個比較逼真的Windows 10 Metro 佈局了。看上去複雜的Windows 10 Metro,是否是其實挺簡單的呢?

Windows 10磁貼中包含的動畫拆解開來都是一些比較常見的、能提高用戶體驗的動畫。你們也能夠在平時的項目或者工做中嘗試去模仿一下這些簡易的動畫,來使得交互與設計更加得友好。

最後,我也使用Vue作了一個小組件,方便你們在Vue中實現Win 10 Metro佈局,歡迎各位的交流與討論~

相關文章
相關標籤/搜索