Win10 Metro 相較於前一代徹底扁平化風格的 Win8 Metro 在動畫效果與交互體驗上有了比較大的差別,那麼想要實現一個較爲逼真的Win10 Metro須要哪些動畫效果呢?css
先來看一下這個Demo,看似複雜的交互實現起來其實並不難,下面咱們就來拆解一下其中的動畫效果以及實現原理吧。html
Win10 Metro的一個比較顯著的特色就是磁貼的3D旋轉。vue
咱們將每個磁貼展現爲橫截面爲正方形的長方體,而後經過css旋轉動畫來實現3D旋轉。git
<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
, left
。github
.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中,有如下幾點值得注意的地方瀏覽器
.box-container
中使用了transform-style: preserve-3d
來保留子節點在3D變換時的3D座標。並用了transform-origin: 50% 50% -75px
將.box-container
的旋轉原點放在了長方體的中心。.back
的定義中咱們須要在transform
中額外添加rotateZ(180deg)
預先顛倒背面的內容。Z軸
的座標而放大或者縮小,咱們始終保持了當前正對觀察者面的Z座標
爲0
。3D旋轉的demo
佈局
在點擊並按住磁貼的時,磁貼會出現一個向點擊位置傾斜的動畫。動畫
若是仔細觀察原版會發現,面的傾斜角會隨着按壓位置的不一樣而產生不一樣的變化。當按壓位置靠近磁貼邊緣時,傾斜角會變大一些;而當按壓位置靠近磁貼中心時,傾斜角會隨之減少。總結以上的規律咱們能夠得出:ui
以磁貼的中心做爲座標系原點(0, 0),當點擊位置爲(x, y)時,X軸上的傾斜角Ɵx ∝ |y|,而Y軸上的傾斜角 Ɵy ∝ |x|。spa
<div class="container">
<div class="tile">
<span>Hello World</span>
</div>
</div>
複製代碼
.container {
width: 200px;
height: 200px;
perspective: 700px;
}
.tile {
background-color: #2d89ef;
width: 100%;
height: 100%;
transition: transform 0.5s;
text-align: center;
color: white;
}
複製代碼
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);
複製代碼
在傾斜的實現上,有如下幾點須要注意的地方
當鼠標懸停在磁鐵上時,磁貼上會有一個跟隨鼠標移動的光圈。
光暈從中心至外圍顏色漸漸淡化,光暈中心的位置會隨着鼠標的移動而移動。
<div class="container">
<div class="hoverLayer">
</div>
<div class="hoverGlare">
</div>
</div>
複製代碼
.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;
}
複製代碼
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);
複製代碼
在光暈的實現上,有如下幾點須要注意的地方
z-index
爲1
的.hoverLayer
來看成鼠標事件節點,避免由於子節點覆蓋父節點而產生鼠標定位不許確的問題。translate
移動這個懸浮層來實現高效率的光暈位置變換。當鼠標點擊磁貼時,在點擊位置會造成圓形向外擴散的波紋動畫
咱們能夠看到圓形的波紋由點擊位置開始向外漸漸擴散直至消散。
<div class="tile">
<div class="clickGlare">
</div>
</div>
複製代碼
.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;
}
}
複製代碼
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);
複製代碼
在點擊波紋的實現上,有如下幾點須要注意的地方
filter: blur
這一條css,這條css可能會引發老版本瀏覽器或者IE/Edge中的兼容性問題。若是將以上的動畫結合起來,咱們就能夠實現一個比較逼真的Windows 10 Metro 佈局了。看上去複雜的Windows 10 Metro,是否是其實挺簡單的呢?
Windows 10磁貼中包含的動畫拆解開來都是一些比較常見的、能提高用戶體驗的動畫。你們也能夠在平時的項目或者工做中嘗試去模仿一下這些簡易的動畫,來使得交互與設計更加得友好。
最後,我也使用Vue作了一個小組件,方便你們在Vue中實現Win 10 Metro佈局,歡迎各位的交流與討論~