分享下之前的文章:
http://www.toutiao.com/i6366385579411636738/
http://www.toutiao.com/i6366084511695897089/
http://www.toutiao.com/i6363443065914393090/
有興趣的可以看看,學(xué)習(xí)學(xué)習(xí)
接下來說正題,先說明一下,這個(gè)是英文的網(wǎng)站,你可以用翻譯的插件來實(shí)現(xiàn)翻譯的功能,這個(gè)網(wǎng)站還是蠻不錯(cuò)的,動畫制作好后,可以copy代碼,可以直接用,也可以自己研究是怎么實(shí)現(xiàn)這個(gè)功能的,一起學(xué)習(xí),一起交流
首先介紹頁面的整體布局,看下圖
網(wǎng)站截圖-英文版
網(wǎng)站截圖-中文版
怎么編輯動畫呢,這里簡單做下介紹:
首先重置一下動畫,點(diǎn)擊reset(重置),看下圖:
重置操作
動畫屬性調(diào)節(jié)
時(shí)間,動畫屬性等設(shè)置好后,開始來編輯制作動畫,先在時(shí)間軸上點(diǎn)擊一個(gè)時(shí)間,然后在編輯區(qū)域里改變物體的位置(旋轉(zhuǎn)也可以),看不懂的看下面的動圖:
動畫編輯演示
也可以旋轉(zhuǎn):
旋轉(zhuǎn)也是可以的
最后動畫做好了,就可以copy代碼了,直接點(diǎn)擊
copy代碼
有興趣的可以看看學(xué)學(xué)。
網(wǎng)址是:http://cssanimate.com/
想學(xué)習(xí)交流的可以打開下面網(wǎng)址掃描加入一起交流有趣好玩的分享:
http://www.mackxin.com/xininn.html
馨客棧導(dǎo)航:http://www.mackxin.com/nav.html
馨客棧前端導(dǎo)航:http://www.mackxin.com/webnav.html
關(guān)注分享,體驗(yàn)樂趣
分享是一種態(tài)度
當(dāng)我們談及網(wǎng)頁動畫時(shí),自然聯(lián)想到的是 CSS3 動畫、JS 動畫、SVG 動畫 等技術(shù)以及 jQuery.animate() 等動畫封裝庫,根據(jù)實(shí)際動畫內(nèi)容設(shè)計(jì)去選擇不同的實(shí)現(xiàn)方式,然而,每個(gè)現(xiàn)行的動畫技術(shù)都存在一定的缺點(diǎn),如 CSS3動畫必須通過JS去獲取動態(tài)改變的值,一個(gè)動畫效果分散在css文件和js文件里不好維護(hù),setInterval 的時(shí)間往往是不精確的而且還會卡頓,引入額外的動畫封裝庫也并非對性能敏感的業(yè)務(wù)適用。
Web Animation API 的歷史也應(yīng)該有幾年了,但是每當(dāng)做動畫效果時(shí),筆者就是依賴各種庫,很少想著去原生實(shí)現(xiàn),最終造成了我們的項(xiàng)目各種依賴庫,體積也不斷變大,性能如何也不得而知,作為前端開發(fā)的我們多么希望原生的JS去支持通用的動畫解決方案, Web Animation API 可能就是一個(gè)不錯(cuò)的解決方案。
W3C 提出 Web Animation API(簡稱 WAAPI)正緣于此,它致力于集合 CSS3 動畫的性能、JavaScript 的靈活、動畫庫的豐富等各家所長,將盡可能多的動畫控制由原生瀏覽器實(shí)現(xiàn),并添加許多 CSS 不具備的變量、控制以及或調(diào)的選項(xiàng)。它為我們提供了一種通用語言來描述DOM元素的動畫,主要方法有:Animation,KeyframeEffect,AnimationEvent,DocumentTimeline,EffectTiming。關(guān)于這個(gè)API的詳細(xì)介紹,可以參照MDN的這篇文檔,鏈接地址:https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API。
使用Web Animations API,我們可以將交互式動畫從樣式表移動到JavaScript,將表示與行為分開。我們不再需要依賴DOM的技術(shù),例如編寫CSS屬性作用于元素以控制方向。為了構(gòu)建自定義動畫庫和創(chuàng)建交互式動畫,Web Animations API可能是完成工作的完美工具,你無需借助第三方動畫庫,就可以輕松實(shí)現(xiàn)一個(gè)效果不錯(cuò)的動畫。
為了讓大家對這個(gè)API有個(gè)清晰的認(rèn)識,筆者在接下來的系列文章里,用五六個(gè)例子讓大家理解這個(gè)API,今天筆者將用此API實(shí)現(xiàn)一個(gè)隨機(jī)移動的圖片開始進(jìn)行介紹,比如用這個(gè)效果我們可以制作一個(gè)隨機(jī)飄浮移動的廣告位,游戲里隨機(jī)走動的怪物等等,本例中的特點(diǎn)就是為了體現(xiàn)Web Animation API的靈活性和強(qiáng)大性,我沒有引用任何第三方類庫,比如(JQ)以及也沒有使用setTimeout和requestAnimationFrame()函數(shù)。
本篇文章預(yù)計(jì)時(shí)間 5 分鐘
開始前,我們先來看看完成后的動畫效果,示例如下效果:
無論圖片怎么隨機(jī)移動,我們都希望在指定的容器里,而不是漫無邊際,首先我們在html頁面定義容器:
<div id="container"> </div>
接下來定義容器的樣式:
body { margin: 0; } div#container { height:500px; width:100%; background: #C6CEF7; } #target { position: absolute; filter: drop-shadow(-12px 12px 7px rgba(0,0,0,0.5)); }
獲取容器
var container=document.getElementById("container");
加載動畫
為了更加直觀性,我選擇一個(gè)走動的gif圖片,由于圖片的加載需要一些時(shí)間,為了不破壞動畫的連貫性,確保圖片加載完了我們在執(zhí)行動畫,相關(guān)代碼如下:
var target=document.createElement("img"); target.id="target"; target.onload=function() { floatHead(); } target.src="walk.gif"; container.appendChild(target);
大家都看到了,onload部分我們加載了floatHead()函數(shù),接下來我們來進(jìn)行相關(guān)實(shí)現(xiàn),此函數(shù)主要包含以下功能:創(chuàng)建一個(gè)隨機(jī)位置,計(jì)算移動時(shí)間,封裝移動動畫。
隨機(jī)位置
我們利用Math.floor函數(shù)實(shí)現(xiàn)了其隨機(jī)位置的變化,示例代碼如下:
function makeNewPosition() { var containerVspace=container.offsetHeight - target.offsetHeight, containerHspace=container.offsetWidth - target.offsetWidth, newX=Math.floor(Math.random() * containerVspace), newY=Math.floor(Math.random() * containerHspace); return [newX, newY]; }
這里的隨機(jī)位置,我們返回了一個(gè)數(shù)組,描述的是圖片相對容器的位置,即top,left。這里你需要理解offsetHeight,offsetWidth,可理解為div的可視高度或?qū)挾龋瑯邮降膆eight或Width+上下padding或左右padding+上下border-width或左右border-width。
計(jì)算時(shí)間
動畫是有時(shí)間屬性的,我們進(jìn)行位置的移動,需要花多久時(shí)間,假設(shè)運(yùn)動速度為0.1個(gè)單位/毫秒。這個(gè)函數(shù)包含兩個(gè)數(shù)組:prev為當(dāng)前目標(biāo)的原始X和Y位置,next為移動目標(biāo)的位置。此函數(shù)沒有進(jìn)行進(jìn)行精確的距離計(jì)算,只是判斷了x和y軸上移動的距離大小用最大的距離除以速度,示例代碼如下:
function velocity(prev, next) { var x=Math.abs(prev[1] - next[1]), y=Math.abs(prev[0] - next[0]), larger=x > y ? x : y, speedModifier=0.1, time=Math.ceil(larger / speedModifier); return time; }
封裝移動動畫
接下來是我們Web Animations API的核心部分,我們使用其核心API在加上上述我們完成的兩個(gè)函數(shù)讓其動起來,示例代碼如下:
function floatHead() { var newPos=makeNewPosition(), oldTop=target.offsetTop, oldLeft=target.offsetLeft, target.animate([ { top: oldTop+"px", left: oldLeft+"px" }, { top: newPos[0]+"px", left: newPos[1]+"px" } ], { duration: velocity([oldTop, oldLeft],newPos), fill: "forwards" }).onfinish=function() { floatHead(); } }
該Animation的animate函數(shù)有兩個(gè)參數(shù),一個(gè)是KeyframeEffects數(shù)組和AnimationEffectTimingPropertiesoptions 的對象。基本上,第一個(gè)參數(shù)映射到您將放入CSS中的內(nèi)容@keyframes,你可以想象成css中的@keyframes內(nèi)容,比如以下代碼:
@keyframes emphasis { 0% { transform: scale(1); opacity: 1; } 30% { transform: scale(.5); opacity: .5; } 78.75% { transform: scale(.667); opacity: .667; } 100% { transform: scale(.6); opacity: .6; } }
你可以將“{}”里的信息順序依次放到一個(gè)數(shù)組里;第二個(gè)參數(shù)是時(shí)間控制 timing,包括有 duration 持續(xù)時(shí)間、iterations 執(zhí)行次數(shù)、direction 動畫方向、easing 緩動函數(shù)等屬性。比如以下代碼:
#toAnimate { animation: emphasis 700ms ease-in-out 10ms infinite alternate forwards; }
你還可能注意到我們使用了onfinish事件完成了floatHead函數(shù)的反復(fù)調(diào)用,其是Animation的屬性,監(jiān)聽動畫完成事件,如果動畫完成繼續(xù)執(zhí)行floatHead(),相當(dāng)不斷的遞歸調(diào)用。
<!DOCTYPE html> <html lang="en"> <head> <style> body { margin: 0; } div#container { height:500px; width:100%; background: #C6CEF7; } #target { position: absolute; filter: drop-shadow(-12px 12px 7px rgba(0,0,0,0.5)); } </style> <meta charset="UTF-8"> <title>前端達(dá)人示例展示——圖片隨機(jī)移動</title> </head> <body> <div id="container"></div> <script> function makeNewPosition() { var containerVspace=container.offsetHeight - target.offsetHeight, containerHspace=container.offsetWidth - target.offsetWidth, newX=Math.floor(Math.random() * containerVspace), newY=Math.floor(Math.random() * containerHspace); return [newX, newY]; } function velocity(prev, next) { var x=Math.abs(prev[1] - next[1]), y=Math.abs(prev[0] - next[0]), larger=x > y ? x : y, speedModifier=0.2, time=Math.ceil(larger / speedModifier); return time; } function floatHead() { var newPos=makeNewPosition(), oldTop=target.offsetTop, oldLeft=target.offsetLeft; target.animate([ { top: oldTop+"px", left: oldLeft+"px" }, { top: newPos[0]+"px", left: newPos[1]+"px" } ], { duration: velocity([oldTop, oldLeft],newPos), fill: 'forwards' }).onfinish=function() { floatHead(); } } var container=document.getElementById("container"), target=document.createElement("img"); target.id="target"; target.onload=function() { floatHead(); } target.src="walk.gif"; target.width="200"; container.appendChild(target); </script> </body> </html>
最后聊聊你關(guān)心的各瀏覽器兼容問題,如下所示顯示了各個(gè)瀏覽器的兼容情況:
看來好多都是部分支持,沒有完全支持,筆者也親自測試了下,在pc端最新版的谷歌瀏覽器和Firefox是沒有任何問題的可以完美運(yùn)行,筆者的safari還是運(yùn)行不起來,在iPhone XS Max無法運(yùn)行。
作為一名前端開發(fā)者,在移動端大行其道怎么能容忍在手機(jī)端沒有效果,為了在現(xiàn)代瀏覽器廠商還沒完全跟進(jìn)到位的時(shí)候搶先用上 WAAPI(Web Animation API簡稱),我們可以選擇引入針對 Web Animation API 的 Polyfill 庫 [https://github.com/web-animations/web-animations-js],從而在 IE/Firefox/Safari 等瀏覽器上體驗(yàn)到 WAAPI 的精彩。
因此我們只需要文件里引入以下js,就可以完美體驗(yàn):
<script src="https://cdn.jsdelivr.net/web-animations/latest/web-animations.min.js"></script>
移動端瀏覽器,Android 5.0 以上的 Android Browser 和 Chrome for Android 本身就已經(jīng)支持 WAAPI 了,加上 Polyfill 之后,筆者的手機(jī)終于可以看到運(yùn)行效果了,微信里的QQ內(nèi)核瀏覽器也能完美運(yùn)行,pc端的safari也可以完美運(yùn)行。可以說是全平臺支持了,有了這個(gè)庫你可以放心大膽的使用了。
好了今天的代碼擼完了,js代碼還不到50行(注:為了在手機(jī)端運(yùn)行,引入了web-animations.min.js),您可以點(diǎn)擊"https://www.qianduandaren.com/demo/walk/"行預(yù)覽,筆者親測在iPhone XS Max運(yùn)行良好,其他手機(jī)沒有,有待親們的測試,歡迎到留言區(qū)告知。下一篇文章我們用不到20行的原生js代碼純手工擼一個(gè)漂亮的時(shí)鐘,敬請期待。
更多精彩內(nèi)容,請微信關(guān)注“前端達(dá)人”公眾號!
文將比較全面細(xì)致的梳理一下 CSS 動畫的方方面面,針對每個(gè)屬性用法的講解及進(jìn)階用法的示意,希望能成為一個(gè)比較好的從入門到進(jìn)階的教程。
首先,我們來簡單介紹一下 CSS 動畫。
最新版本的 CSS 動畫由規(guī)范 -- CSS Animations Level 1 定義。
CSS 動畫用于實(shí)現(xiàn)元素從一個(gè) CSS 樣式配置轉(zhuǎn)換到另一個(gè) CSS 樣式配置。
動畫包括兩個(gè)部分: 描述動畫的樣式規(guī)則和用于指定動畫開始、結(jié)束以及中間點(diǎn)樣式的關(guān)鍵幀。
簡單來說,看下面的例子:
div {
animation: change 3s;
}
@keyframes change {
0% {
color: #f00;
}
100% {
color: #000;
}
}
一個(gè) CSS 動畫一定要由上述兩部分組成。
接下來,我們簡單看看 CSS 動畫的語法。
創(chuàng)建動畫序列,需要使用 animation 屬性或其子屬性,該屬性允許配置動畫時(shí)間、時(shí)長以及其他動畫細(xì)節(jié),但該屬性不能配置動畫的實(shí)際表現(xiàn),動畫的實(shí)際表現(xiàn)是由 @keyframes 規(guī)則實(shí)現(xiàn)。
animation 的子屬性有:
其中,對于一個(gè)動畫:
上面已經(jīng)給了一個(gè)簡單的 DEMO, 就用上述的 DEMO,看看結(jié)果:
這就是一個(gè)最基本的 CSS 動畫,本文將從 animation 的各個(gè)子屬性入手,探究 CSS 動畫的方方面面。
整體而言,單個(gè)的 animation-name 和 animation-duration 沒有太多的技巧,非常好理解,放在一起。
首先介紹一下 animation-name,通過 animation-name,CSS 引擎將會找到對應(yīng)的 @keyframes 規(guī)則。
當(dāng)然,它和 CSS 規(guī)則命名一樣,也存在一些騷操作。譬如,他是支持 emoji 表情的,所以代碼中的 animation-name 命名也可以這樣寫:
div {
animation: 3s;
}
@keyframes {
0% {
color: #f00;
}
100% {
color: #000;
}
}
而 animation-duration 設(shè)置動畫一個(gè)周期的時(shí)長,上述 DEMO 中,就是設(shè)定動畫整體持續(xù) 3s,這個(gè)也非常好理解。
animation-delay 就比較有意思了,它可以設(shè)置動畫延時(shí),即從元素加載完成之后到動畫序列開始執(zhí)行的這段時(shí)間。
簡單的一個(gè) DEMO:
<div></div>
<div></div>
div {
width: 100px;
height: 100px;
background: #000;
animation-name: move;
animation-duration: 2s;
}
div:nth-child(2) {
animation-delay: 1s;
}
@keyframes move {
0% {
transform: translate(0);
}
100% {
transform: translate(200px);
}
}
比較下列兩個(gè)動畫,一個(gè)添加了 animation-delay,一個(gè)沒有,非常直觀:
上述第二個(gè) div,關(guān)于 animation 屬性,也可以簡寫為 animation: move 2s 1s,第一個(gè)時(shí)間值表示持續(xù)時(shí)間,第二個(gè)時(shí)間值表示延遲時(shí)間。
關(guān)于 animation-delay,最有意思的技巧在于,它可以是負(fù)數(shù)。也就是說,雖然屬性名是動畫延遲時(shí)間,但是運(yùn)用了負(fù)數(shù)之后,動畫可以提前進(jìn)行。
假設(shè)我們要實(shí)現(xiàn)這樣一個(gè) loading 動畫效果:
有幾種思路:
方案 2 的核心偽代碼如下:
.item:nth-child(1) {
animation: rotate 3s infinite linear;
}
.item:nth-child(2) {
animation: rotate 3s infinite 1s linear;
}
.item:nth-child(3) {
animation: rotate 3s infinite 2s linear;
}
但是,在動畫的前 2s,另外兩個(gè)元素是不會動的,只有 2s 過后,整個(gè)動畫才是我們想要的:
此時(shí),我們可以讓第 2、3 個(gè)元素的延遲時(shí)間,改為負(fù)值,這樣可以讓動畫延遲進(jìn)行 -1s、-2s,也就是提前進(jìn)行 1s、2s:
.item:nth-child(1) {
animation: rotate 3s infinite linear;
}
.item:nth-child(2) {
animation: rotate 3s infinite -1s linear;
}
.item:nth-child(3) {
animation: rotate 3s infinite -2s linear;
}
這樣,每個(gè)元素都無需等待,直接就是運(yùn)動狀態(tài)中的,并且元素間隔位置是我們想要的結(jié)果:
還有一個(gè)有意思的小技巧。
同一個(gè)動畫,我們利用一定范圍內(nèi)隨機(jī)的 animation-duration 和一定范圍內(nèi)隨機(jī)的 animation-delay,可以有效的構(gòu)建更為隨機(jī)的動畫效果,讓動畫更加的自然。
我在下述兩個(gè)純 CSS 動畫中,都使用了這樣的技巧:
以純 CSS 實(shí)現(xiàn)華為充電動畫為例子,簡單講解一下。
仔細(xì)觀察這一部分,上升的一個(gè)一個(gè)圓球,拋去這里的一些融合效果,只關(guān)注不斷上升的圓球,看著像是沒有什么規(guī)律可言:
我們來模擬一下,如果是使用 10 個(gè) animation-duration 和 animation-delay 都一致的圓的話,核心偽代碼:
<ul>
<li></li>
<!--共 10 個(gè)...-->
<li></li>
</ul>
ul {
display: flex;
flex-wrap: nowrap;
gap: 5px;
}
li {
background: #000;
animation: move 3s infinite 1s linear;
}
@keyframes move {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(0, -100px);
}
}
這樣,小球的運(yùn)動會是這樣的整齊劃一:
要讓小球的運(yùn)動顯得非常的隨機(jī),只需要讓 animation-duration 和 animation-delay 都在一定范圍內(nèi)浮動即可,改造下 CSS:
@for $i from 1 to 11 {
li:nth-child(#{$i}) {
animation-duration: #{random(2000)/1000 + 2}s;
animation-delay: #{random(1000)/1000 + 1}s;
}
}
我們利用 SASS 的循環(huán)和 random() 函數(shù),讓 animation-duration 在 2-4 秒范圍內(nèi)隨機(jī),讓 animation-delay 在 1-2 秒范圍內(nèi)隨機(jī),這樣,我們就可以得到非常自然且不同的上升動畫效果,基本不會出現(xiàn)重復(fù)的畫面,很好的模擬了隨機(jī)效果:
CodePen Demo -- 利用范圍隨機(jī) animation-duration 和 animation-delay 實(shí)現(xiàn)隨機(jī)動畫效果
緩動函數(shù)在動畫中非常重要,它定義了動畫在每一動畫周期中執(zhí)行的節(jié)奏。
緩動主要分為兩類:
首先先看看三次貝塞爾曲線緩動函數(shù)。在 CSS 中,支持一些緩動函數(shù)關(guān)鍵字。
/* Keyword values */
animation-timing-function: ease; // 動畫以低速開始,然后加快,在結(jié)束前變慢
animation-timing-function: ease-in; // 動畫以低速開始
animation-timing-function: ease-out; // 動畫以低速結(jié)束
animation-timing-function: ease-in-out; // 動畫以低速開始和結(jié)束
animation-timing-function: linear; // 勻速,動畫從頭到尾的速度是相同的
關(guān)于它們之間的效果對比:
除了 CSS 支持的這 5 個(gè)關(guān)鍵字,我們還可以使用 cubic-bezier() 方法自定義三次貝塞爾曲線:
animation-timing-function: cubic-bezier(0.1, 0.7, 1.0, 0.1);
這里有個(gè)非常好用的網(wǎng)站 -- cubic-bezier 用于創(chuàng)建和調(diào)試生成不同的貝塞爾曲線參數(shù)。
關(guān)于緩動函數(shù)對動畫的影響,這里有一個(gè)非常好的示例。這里我們使用了純 CSS 實(shí)現(xiàn)了一個(gè)鐘的效果,對于其中的動畫的運(yùn)動,如果是 animation-timing-function: linear,效果如下:
而如果我們我把緩動函數(shù)替換一下,變成 animation-timing-function: cubic-bezier(1,-0.21,.85,1.29),它的曲線對應(yīng)如下:
整個(gè)鐘的動畫律動效果將變成這樣,完全不一樣的感覺:
CodePen Demo - 緩動不同效果不同
對于許多精益求精的動畫,在設(shè)計(jì)中其實(shí)都考慮到了緩動函數(shù)。我很久之前看到過一篇《基于物理學(xué)的動畫用戶體驗(yàn)設(shè)計(jì)》,可惜如今已經(jīng)無法找到原文。其中傳達(dá)出的一些概念是,動畫的設(shè)計(jì)依據(jù)實(shí)際在生活中的表現(xiàn)去考量。
譬如 linear 這個(gè)緩動,實(shí)際應(yīng)用于某些動畫中會顯得很不自然,因?yàn)橛捎诳諝庾枇Φ拇嬖冢绦蚰M的勻速直線運(yùn)動在現(xiàn)實(shí)生活中是很難實(shí)現(xiàn)的。因此對于這樣一個(gè)用戶平時(shí)很少感知到的運(yùn)動是很難建立信任感的。這樣的勻速直線運(yùn)動也是我們在進(jìn)行動效設(shè)計(jì)時(shí)需要極力避免的。
接下來再講講步驟緩動函數(shù)。在 CSS 的 animation-timing-function 中,它有如下幾種表現(xiàn)形態(tài):
{
/* Keyword values */
animation-timing-function: step-start;
animation-timing-function: step-end;
/* Function values */
animation-timing-function: steps(6, start)
animation-timing-function: steps(4, end);
}
在 CSS 中,使用步驟緩動函數(shù)最多的,就是利用其來實(shí)現(xiàn)逐幀動畫。假設(shè)我們有這樣一張圖(圖片大小為 1536 x 256,圖片來源于網(wǎng)絡(luò)):
可以發(fā)現(xiàn)它其實(shí)是一個(gè)人物行進(jìn)過程中的 6 種狀態(tài),或者可以為 6 幀,我們利用 animation-timing-function: steps(6) 可以將其用一個(gè) CSS 動畫串聯(lián)起來,代碼非常的簡單:
<div class="box"></div>
.box {
width: 256px;
height: 256px;
background: url('https://github.com/iamalperen/playground/blob/main/SpriteSheetAnimation/sprite.png?raw=true');
animation: sprite .6s steps(6, end) infinite;
}
@keyframes sprite {
0% {
background-position: 0 0;
}
100% {
background-position: -1536px 0;
}
}
簡單解釋一下上述代碼,首先要知道,剛好 256 x 6=1536,所以上述圖片其實(shí)可以剛好均分為 6 段:
將上述 1、2、3,3 個(gè)步驟畫在圖上簡單示意:
從上圖可知,其實(shí)在動畫過程中,background-position 的取值其實(shí)只有 background-position: 0 0,background-position: -256px 0,background-position: -512px 0 依次類推一直到 background-position: -1536px 0,由于背景的 repeat 的特性,其實(shí)剛好回到原點(diǎn),由此又重新開始新一輪同樣的動畫。
所以,整個(gè)動畫就會是這樣,每一幀停留 0.1s 后切換到下一幀(注意這里是個(gè)無限循環(huán)動畫),:
完整的代碼你可以戳這里 -- CodePen Demo -- Sprite Animation with steps()
在這里再插入一個(gè)小章節(jié),animation-duration 動畫長短對動畫的影響也是非常明顯的。
在上述代碼的基礎(chǔ)上,我們再修改 animation-duration,縮短每一幀的時(shí)間就可以讓步行的效果變成跑步的效果,同理,也可以增加每一幀的停留時(shí)間。讓每一步變得緩慢,就像是在步行一樣。
需要提出的是,上文說的每一幀,和瀏覽器渲染過程中的 FPS 的每一幀不是同一個(gè)概念。
看看效果,設(shè)置不同的 animation-duration 的效果(這里是 0.6s -> 0.2s),GIF 錄屏丟失了一些關(guān)鍵幀,實(shí)際效果會更好點(diǎn):
當(dāng)然,在 steps() 中,還有 steps(6, start) 和 steps(6, end) 的差異,也就是其中關(guān)鍵字 start 和 end 的差異。對于上述的無限動畫而言,其實(shí)基本是可以忽略不計(jì)的,它主要是控制動畫第一幀的開始和持續(xù)時(shí)長,比較小的一個(gè)知識點(diǎn)但是想講明白需要比較長的篇幅,限于本文的內(nèi)容,在這里不做展開,讀者可以自行了解。
上述的三次貝塞爾曲線緩動和步驟緩動,其實(shí)就是對應(yīng)的補(bǔ)間動畫和逐幀動畫。
對于同個(gè)動畫而言,有的時(shí)候兩種緩動都是適用的。我們在具體使用的時(shí)候需要具體分析選取。
假設(shè)我們用 CSS 實(shí)現(xiàn)了這樣一個(gè)圖形:
現(xiàn)在想利用這個(gè)圖形制作一個(gè) Loading 效果,如果利用補(bǔ)間動畫,也就是三次貝塞爾曲線緩動的話,讓它旋轉(zhuǎn)起來,得到的效果非常的一般:
.g-container{
animation: rotate 2s linear infinite;
}
@keyframes rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
動畫效果如下:
但是如果這里,我們將補(bǔ)間動畫換成逐幀動畫,因?yàn)橛?20 個(gè)點(diǎn),所以設(shè)置成 steps(20),再看看效果,會得到完全不一樣的感覺:
.g-container{
animation: rotate 2s steps(20) infinite;
}
@keyframes rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
動畫效果如下:
整個(gè) loading 的圈圈看上去好像也在旋轉(zhuǎn),實(shí)際上只是 20 幀關(guān)鍵幀在切換,整體的效果感覺更適合 Loading 的效果。
因此,兩種動畫效果都是很有必要掌握的,在實(shí)際使用的時(shí)候靈活嘗試,選擇更適合的。
上述 DEMO 效果完整的代碼:CodePen Demo -- Scale Loading steps vs linear
接下來,我們講講 animation-play-state,顧名思義,它可以控制動畫的狀態(tài) -- 運(yùn)行或者暫停。類似于視頻播放器的開始和暫停。是 CSS 動畫中有限的控制動畫狀態(tài)的手段之一。
它的取值只有兩個(gè)(默認(rèn)為 running):
{
animation-play-state: paused | running;
}
使用起來也非常簡單,看下面這個(gè)例子,我們在 hover 按鈕的時(shí)候,實(shí)現(xiàn)動畫的暫停:
<div class="btn stop">stop</div>
<div class="animation"></div>
.animation {
width: 100px;
height: 100px;
background: deeppink;
animation: move 2s linear infinite alternate;
}
@keyframes move {
100% {
transform: translate(100px, 0);
}
}
.stop:hover ~ .animation {
animation-play-state: paused;
}
一個(gè)簡單的 CSS 動畫,但是當(dāng)我們 hover 按鈕的時(shí)候,給動畫元素添加上 animation-play-state: paused:
正常而言,按照正常思路使用 animation-play-state: paused 是非常簡單的。
但是,如果我們想創(chuàng)造一些有意思的 CSS 動畫效果,不如反其道而行之。
我們都知道,正常情況下,動畫應(yīng)該是運(yùn)行狀態(tài),那如果我們將一些動畫的默認(rèn)狀態(tài)設(shè)置為暫停,只有當(dāng)鼠標(biāo)點(diǎn)擊或者 hover 的時(shí)候,才設(shè)置其 animation-play-state: running,這樣就可以得到很多有趣的 CSS 效果。
看個(gè)倒酒的例子,這是一個(gè)純 CSS 動畫,但是默認(rèn)狀態(tài)下,動畫處于 animation-play-state: paused,也就是暫停狀態(tài),只有當(dāng)鼠標(biāo)點(diǎn)擊杯子的時(shí),才設(shè)置 animation-play-state: running,讓酒倒下,利用 animation-play-state 實(shí)現(xiàn)了一個(gè)非常有意思的交互效果:
完整的 DEMO 你可以戳這里:CodePen Demo -- CSS Beer!
在非常多 Web 創(chuàng)意交互動畫我們都可以看到這個(gè)技巧的身影。
下一個(gè)屬性 animation-fill-mode,很多人會誤認(rèn)為它只是用于控制元素在動畫結(jié)束后是否復(fù)位。這個(gè)其實(shí)是不準(zhǔn)確的,不全面的。
看看它的取值:
{
// 默認(rèn)值,當(dāng)動畫未執(zhí)行時(shí),動畫將不會將任何樣式應(yīng)用于目標(biāo),而是使用賦予給該元素的 CSS 規(guī)則來顯示該元素的狀態(tài)
animation-fill-mode: none;
// 動畫將在應(yīng)用于目標(biāo)時(shí)立即應(yīng)用第一個(gè)關(guān)鍵幀中定義的值,并在 `animation-delay` 期間保留此值,
animation-fill-mode: backwards;
// 目標(biāo)將保留由執(zhí)行期間遇到的最后一個(gè)關(guān)鍵幀計(jì)算值。 最后一個(gè)關(guān)鍵幀取決于 `animation-direction` 和 `animation-iteration-count`
animation-fill-mode: forwards;
// 動畫將遵循 `forwards` 和 `backwards` 的規(guī)則,從而在兩個(gè)方向上擴(kuò)展動畫屬性
animation-fill-mode: both;
}
對于 animation-fill-mode 的解讀,我在 Segment Fault 上的一個(gè)問答中(SF - 如何理解 animation-fill-mode)看到了 4 副很好的解讀圖,這里借用一下:
假設(shè) HTML 如下:
<div class="box"></div>
CSS如下:
.box{
transform: translateY(0);
}
.box.on{
animation: move 1s;
}
@keyframes move{
from{transform: translateY(-50px)}
to {transform: translateY( 50px)}
}
使用圖片來表示 translateY 的值與 時(shí)間 的關(guān)系:
一句話總結(jié),元素在動畫時(shí)間之外,樣式只受到它的 CSS 規(guī)則限制,與 @keyframes 內(nèi)的關(guān)鍵幀定義無關(guān)。
一句話總結(jié),元素在動畫開始之前(包含未觸發(fā)動畫階段及 animation-delay 期間)的樣式為動畫運(yùn)行時(shí)的第一幀,而動畫結(jié)束后的樣式則恢復(fù)為 CSS 規(guī)則設(shè)定的樣式。
一句話總結(jié),元素在動畫開始之前的樣式為 CSS 規(guī)則設(shè)定的樣式,而動畫結(jié)束后的樣式則表現(xiàn)為由執(zhí)行期間遇到的最后一個(gè)關(guān)鍵幀計(jì)算值(也就是停在最后一幀)。
一句話總結(jié),綜合了 animation-fill-mode: backwards 和 animation-fill-mode: forwards 的設(shè)定。動畫開始前的樣式為動畫運(yùn)行時(shí)的第一幀,動畫結(jié)束后停在最后一幀。
講到了 animation-fill-mode,我們就可以順帶講講這個(gè)兩個(gè)比較好理解的屬性 -- animation-iteration-count 和 animation-direction
在上面講述 animation-fill-mode 時(shí),我使用了動畫運(yùn)行時(shí)的第一幀替代了@keyframes 中定義的第一幀這種說法,因?yàn)閯赢嬤\(yùn)行的第一幀和最后一幀的實(shí)際狀態(tài)還會受到動畫運(yùn)行方向 animation-direction 和 animation-iteration-count 的影響。
在 CSS 動畫中,由 animation-iteration-count 和 animation-direction 共同決定動畫運(yùn)行時(shí)的第一幀和最后一幀的狀態(tài)。
動畫的最后一幀,也就是動畫運(yùn)行的最終狀態(tài),并且我們可以利用 animation-fill-mode: forwards 讓動畫在結(jié)束后停留在這一幀,這個(gè)還是比較好理解的,但是 animation-fill-mode: backwards 和 animation-direction 的關(guān)系很容易弄不清楚,這里簡答講解下。
設(shè)置一個(gè) 100px x 100px 的滑塊,在一個(gè) 400px x 100px 的容器中,其代碼如下:
<div class="g-father">
<div class="g-box"></div>
</div>
.g-father {
width: 400px;
height: 100px;
border: 1px solid #000;
}
.g-box {
width: 100px;
height: 100px;
background: #333;
}
表現(xiàn)如下:
那么,加入 animation 之后,在不同的 animation-iteration-count 和 animation-direction 作用下,動畫的初始和結(jié)束狀態(tài)都不一樣。
如果設(shè)置了 animation-fill-mode: backwards,則元素在動畫未開始前的狀態(tài)由 animation-direction 決定:
.g-box {
...
animation: move 4s linear;
animation-play-state: paused;
transform: translate(0, 0);
}
@keyframes move {
0% {
transform: translate(100px, 0);
}
100% {
transform: translate(300px, 0);
}
}
注意這里 CSS 規(guī)則中,元素沒有設(shè)置位移 transform: translate(0, 0),而在動畫中,第一個(gè)關(guān)鍵幀和最后一個(gè)關(guān)鍵的 translateX 分別是 100px、300px,配合不同的 animation-direction 初始狀態(tài)如下。
下圖假設(shè)我們設(shè)置了動畫默認(rèn)是暫停的 -- animation-play-state: paused,那么動畫在開始前的狀態(tài)為:
講完了每一個(gè)屬性,我們再來看看一些動畫使用過程中的細(xì)節(jié)。
看這樣一個(gè)動畫:
<div></div>
div {
width: 100px;
height: 100px;
background: #000;
animation: combine 2s;
}
@keyframes combine {
100% {
transform: translate(0, 150px);
opacity: 0;
}
}
這里我們實(shí)現(xiàn)了一個(gè) div 塊下落動畫,下落的同時(shí)產(chǎn)生透明度的變化:
對于這樣一個(gè)多個(gè)屬性變化的動畫,它其實(shí)等價(jià)于:
div {
animation: falldown 2s, fadeIn 2s;
}
@keyframes falldown {
100% {
transform: translate(0, 150px);
}
}
@keyframes fadeIn {
100% {
opacity: 0;
}
}
在 CSS 動畫規(guī)則中,animation 是可以接收多個(gè)動畫的,這樣做的目的不僅僅只是為了復(fù)用,同時(shí)也是為了分治,我們對每一個(gè)屬性層面的動畫能夠有著更為精確的控制。
我們經(jīng)常能夠在各種不同的 CSS 代碼見到如下兩種 CSS @keyframes 的設(shè)定:
@keyframes fadeIn {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes fadeIn {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
在 CSS 動畫 @keyframes 的定義中,from 等同于 0%,而 to 等同于 100%。
當(dāng)然,當(dāng)我們的關(guān)鍵幀不止 2 幀的時(shí),更推薦使用百分比定義的方式。
除此之外,當(dāng)動畫的起始幀等同于 CSS 規(guī)則中賦予的值并且沒有設(shè)定 animation-fill-mode,0% 和 from 這一幀是可以刪除的。
我曾經(jīng)在這篇文章中 -- 深入理解 CSS(Cascading Style Sheets)中的層疊(Cascading) 講過一個(gè)很有意思的 CSS 現(xiàn)象。
這也是很多人對 CSS 優(yōu)先級的一個(gè)認(rèn)知誤區(qū),在 CSS 中,優(yōu)先級還需要考慮選擇器的層疊(級聯(lián))順序。
只有在層疊順序相等時(shí),使用哪個(gè)值才取決于樣式的優(yōu)先級。
那什么是層疊順序呢?
根據(jù) CSS Cascading 4 最新標(biāo)準(zhǔn):
CSS Cascading and Inheritance Level 5(Current Work)
定義的當(dāng)前規(guī)范下申明的層疊順序優(yōu)先級如下(越往下的優(yōu)先級越高,下面的規(guī)則按升序排列):
簡單翻譯一下:
按照上述算法,大概是這樣:
過渡動畫過程中每一幀的樣式 > 用戶代理、用戶、頁面作者設(shè)置的!important樣式 > 動畫過程中每一幀的樣式優(yōu)先級 > 頁面作者、用戶、用戶代理普通樣式。
然而,經(jīng)過多個(gè)瀏覽器的測試,實(shí)際上并不是這樣。(尷尬了)
舉個(gè)例子,我們可以通過這個(gè)特性,覆蓋掉行內(nèi)樣式中的 !important 樣式:
<p class="txt" style="color:red!important">123456789</p>
.txt {
animation: colorGreen 2s infinite;
}
@keyframes colorGreen {
0%,
100% {
color: green;
}
}
在 Safari 瀏覽器下,上述 DEMO 文本的顏色為綠色,也就是說,處于動畫狀態(tài)中的樣式,能夠覆蓋掉行內(nèi)樣式中的 !important 樣式,屬于最最高優(yōu)先級的一種樣式,我們可以通過無限動畫、或者 animation-fill-mode: forwards,利用這個(gè)技巧,覆蓋掉本來應(yīng)該是優(yōu)先級非常非常高的行內(nèi)樣式中的 !important 樣式。
我在早兩年的 Chrome 中也能得到同樣的結(jié)果,但是到今天(2022-01-10),最新版的 Chrome 已經(jīng)不支持動畫過程中關(guān)鍵幀樣式優(yōu)先級覆蓋行內(nèi)樣式 !important 的特性。
對于不同瀏覽器,感興趣的同學(xué)可以利用我這個(gè) DEMO 自行嘗試,CodePen Demo - the priority of CSS Animation
這也是非常多人非常關(guān)心的一個(gè)重點(diǎn)。
我的 CSS 動畫很卡,我應(yīng)該如何去優(yōu)化它?
CSS 動畫很卡,其實(shí)是一個(gè)現(xiàn)象描述,它的本質(zhì)其實(shí)是在動畫過程中,瀏覽器刷新渲染頁面的幀率過低。通常而言,目前大多數(shù)瀏覽器刷新率為 60 次/秒,所以通常來講 FPS 為 60 frame/s 時(shí)動畫效果較好,也就是每幀的消耗時(shí)間為 16.67ms。
頁面處于動畫變化時(shí),當(dāng)幀率低于一定數(shù)值時(shí),我們就感覺到頁面的卡頓。
而造成幀率低的原因就是瀏覽器在一幀之間處理的事情太多了,超過了 16.67ms,要優(yōu)化每一幀的時(shí)間,又需要完整地知道瀏覽器在每一幀干了什么,這個(gè)就又涉及到了老生常談的瀏覽器渲染頁面。
到今天,雖然不同瀏覽器的渲染過程不完全相同,但是基本上大同小異,基本上都是:
簡化一下也就是這個(gè)圖:
這兩張圖,你可以在非常多不同的文章中看到。
回歸本文的重點(diǎn),Web 動畫很大一部分開銷在于層的重繪,以層為基礎(chǔ)的復(fù)合模型對渲染性能有著深遠(yuǎn)的影響。當(dāng)不需要繪制時(shí),復(fù)合操作的開銷可以忽略不計(jì),因此在試著調(diào)試渲染性能問題時(shí),首要目標(biāo)就是要避免層的重繪。那么這就給動畫的性能優(yōu)化提供了方向,減少元素的重繪與回流。
這其中,如何減少頁面的回流與重繪呢,這里就會運(yùn)用到我們常說的** GPU 加速**。
GPU 加速的本質(zhì)其實(shí)是減少瀏覽器渲染頁面每一幀過程中的 reflow 和 repaint,其根本,就是讓需要進(jìn)行動畫的元素,生成自己的 GraphicsLayer。
瀏覽器渲染一個(gè)頁面時(shí),它使用了許多沒有暴露給開發(fā)者的中間表現(xiàn)形式,其中最重要的結(jié)構(gòu)便是層(layer)。
在 Chrome 中,存在有不同類型的層: RenderLayer(負(fù)責(zé) DOM 子樹),GraphicsLayer(負(fù)責(zé) RenderLayer 的子樹)。
GraphicsLayer ,它對于我們的 Web 動畫而言非常重要,通常,Chrome 會將一個(gè)層的內(nèi)容在作為紋理上傳到 GPU 前先繪制(paint)進(jìn)一個(gè)位圖中。如果內(nèi)容不會改變,那么就沒有必要重繪(repaint)層。
而當(dāng)元素生成了自己的 GraphicsLayer 之后,在動畫過程中,Chrome 并不會始終重繪整個(gè)層,它會嘗試智能地去重繪 DOM 中失效的部分,也就是發(fā)生動畫的部分,在 Composite 之前,頁面是處于一種分層狀態(tài),借助 GPU,瀏覽器僅僅在每一幀對生成了自己獨(dú)立 GraphicsLayer 元素層進(jìn)行重繪,如此,大大的降低了整個(gè)頁面重排重繪的開銷,提升了頁面渲染的效率。
因此,CSS 動畫(Web 動畫同理)優(yōu)化的第一條準(zhǔn)則就是讓需要動畫的元素生成了自己獨(dú)立的 GraphicsLayer,強(qiáng)制開始 GPU 加速,而我們需要知道是,GPU 加速的本質(zhì)是利用讓元素生成了自己獨(dú)立的 GraphicsLayer,降低了頁面在渲染過程中重繪重排的開銷。
當(dāng)然,生成自己的獨(dú)立的 GraphicsLayer,不僅僅只有 transform3d api,還有非常多的方式。對于上述一大段非常繞的內(nèi)容,你可以再看看這幾篇文章:
除了上述準(zhǔn)則之外,還有一些提升 CSS 動畫性能的建議:
不同樣式在消耗性能方面是不同的,改變一些屬性的開銷比改變其他屬性要多,因此更可能使動畫卡頓。
例如,與改變元素的文本顏色相比,改變元素的 box-shadow 將需要開銷大很多的繪圖操作。box-shadow 屬性,從渲染角度來講十分耗性能,原因就是與其他樣式相比,它們的繪制代碼執(zhí)行時(shí)間過長。這就是說,如果一個(gè)耗性能嚴(yán)重的樣式經(jīng)常需要重繪,那么你就會遇到性能問題。
類似的還有 CSS 3D 變換、mix-blend-mode、filter,這些樣式相比其他一些簡單的操作,會更加的消耗性能。我們應(yīng)該盡可能的在動畫過程中降低其使用的頻率或者尋找替代方案。
當(dāng)然,沒有不變的事情,在今天性能很差的樣式,可能明天就被優(yōu)化,并且瀏覽器之間也存在差異。
因此關(guān)鍵在于,我們需要針對每一起卡頓的例子,借助開發(fā)工具來分辨出性能瓶頸所在,然后設(shè)法減少瀏覽器的工作量。學(xué)會 Chrome 開發(fā)者工具的 Performance 面板及其他渲染相關(guān)的面板非常重要,當(dāng)然這不是本文的重點(diǎn)。大家可以自行探索。
will-change 為 Web 開發(fā)者提供了一種告知瀏覽器該元素會有哪些變化的方法,這樣瀏覽器可以在元素屬性真正發(fā)生變化之前提前做好對應(yīng)的優(yōu)化準(zhǔn)備工作。 這種優(yōu)化可以將一部分復(fù)雜的計(jì)算工作提前準(zhǔn)備好,使頁面的反應(yīng)更為快速靈敏。
值得注意的是,用好這個(gè)屬性并不是很容易:
有人說 will-change 是良藥,也有人說是毒藥,在具體使用的時(shí)候,可以多測試一下。
好了,本文從多個(gè)方面,由淺入深地描述了 CSS 動畫我認(rèn)為的一些比較重要、值得一講、需要注意的點(diǎn)。當(dāng)然很多地方點(diǎn)到即止,或者限于篇幅沒有完全展開,很多細(xì)節(jié)還需要讀者進(jìn)一步閱讀規(guī)范或者自行嘗試驗(yàn)證,實(shí)踐出真知,紙上得來終覺淺。
OK,本文到此結(jié)束,希望本文對你有所幫助 :)
*請認(rèn)真填寫需求信息,我們會在24小時(shí)內(nèi)與您取得聯(lián)系。