整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          前端系列:在線CSS3動畫制作

          前端系列:在線CSS3動畫制作

          分享下之前的文章:

          前端系列:純CSS代碼來寫多邊形和不規(guī)則圖形

          http://www.toutiao.com/i6366385579411636738/

          前端系列:如何用CSS代碼來寫26個(gè)字母,前端福利

          http://www.toutiao.com/i6366084511695897089/

          前端系列:在線認(rèn)識貝塞爾曲線的運(yùn)動軌跡(中文版網(wǎng)站)

          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)度

          eb Animation API 介紹

          當(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è)庫你可以放心大膽的使用了。

          小節(jié)

          好了今天的代碼擼完了,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 動畫。

          最新版本的 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;
              }
          }
          
          1. animation: move 1s 部分就是動畫的第一部分,用于描述動畫的各個(gè)規(guī)則;
          2. @keyframes move {} 部分就是動畫的第二部分,用于指定動畫開始、結(jié)束以及中間點(diǎn)樣式的關(guān)鍵幀;

          一個(gè) CSS 動畫一定要由上述兩部分組成。

          CSS 動畫的語法

          接下來,我們簡單看看 CSS 動畫的語法。

          創(chuàng)建動畫序列,需要使用 animation 屬性或其子屬性,該屬性允許配置動畫時(shí)間、時(shí)長以及其他動畫細(xì)節(jié),但該屬性不能配置動畫的實(shí)際表現(xiàn),動畫的實(shí)際表現(xiàn)是由 @keyframes 規(guī)則實(shí)現(xiàn)。

          animation 的子屬性有:

          • animation-name:指定由 @keyframes 描述的關(guān)鍵幀名稱。
          • animation-duration:設(shè)置動畫一個(gè)周期的時(shí)長。
          • animation-delay:設(shè)置延時(shí),即從元素加載完成之后到動畫序列開始執(zhí)行的這段時(shí)間。
          • animation-direction:設(shè)置動畫在每次運(yùn)行完后是反向運(yùn)行還是重新回到開始位置重復(fù)運(yùn)行。
          • animation-iteration-count:設(shè)置動畫重復(fù)次數(shù), 可以指定 infinite 無限次重復(fù)動畫
          • animation-play-state:允許暫停和恢復(fù)動畫。
          • animation-timing-function:設(shè)置動畫速度, 即通過建立加速度曲線,設(shè)置動畫在關(guān)鍵幀之間是如何變化。
          • animation-fill-mode:指定動畫執(zhí)行前后如何為目標(biāo)元素應(yīng)用樣式
          • @keyframes 規(guī)則,當(dāng)然,一個(gè)動畫想要運(yùn)行,還應(yīng)該包括 @keyframes 規(guī)則,在內(nèi)部設(shè)定動畫關(guān)鍵幀

          其中,對于一個(gè)動畫:

          • 必須項(xiàng)animation-nameanimation-duration@keyframes規(guī)則
          • 非必須項(xiàng)animation-delayanimation-directionanimation-iteration-countanimation-play-stateanimation-timing-functionanimation-fill-mode,當(dāng)然不是說它們不重要,只是不設(shè)置時(shí),它們都有默認(rèn)值

          上面已經(jīng)給了一個(gè)簡單的 DEMO, 就用上述的 DEMO,看看結(jié)果:

          這就是一個(gè)最基本的 CSS 動畫,本文將從 animation 的各個(gè)子屬性入手,探究 CSS 動畫的方方面面。

          animation-name / animation-duration 詳解

          整體而言,單個(gè)的 animation-nameanimation-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 詳解

          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í)間。

          animation-delay 可以為負(fù)值

          關(guān)于 animation-delay,最有意思的技巧在于,它可以是負(fù)數(shù)。也就是說,雖然屬性名是動畫延遲時(shí)間,但是運(yùn)用了負(fù)數(shù)之后,動畫可以提前進(jìn)行

          假設(shè)我們要實(shí)現(xiàn)這樣一個(gè) loading 動畫效果:

          有幾種思路:

          1. 初始 3 個(gè)球的位置就是間隔 120°,同時(shí)開始旋轉(zhuǎn),但是這樣代碼量會稍微多一點(diǎn)
          2. 另外一種思路,同一個(gè)動畫,3 個(gè)元素的其中兩個(gè)延遲整個(gè)動畫的 1/3,2/3 時(shí)間出發(fā)

          方案 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)行 1s2s

          .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é)果:

          利用 animation-duration 和 animation-delay 構(gòu)建隨機(jī)效果

          還有一個(gè)有意思的小技巧。

          同一個(gè)動畫,我們利用一定范圍內(nèi)隨機(jī)的 animation-duration 和一定范圍內(nèi)隨機(jī)的 animation-delay,可以有效的構(gòu)建更為隨機(jī)的動畫效果,讓動畫更加的自然。

          我在下述兩個(gè)純 CSS 動畫中,都使用了這樣的技巧:

          1. 純 CSS 實(shí)現(xiàn)華為充電動畫:
          1. 純 CSS 實(shí)現(xiàn)火焰動畫:

          純 CSS 實(shí)現(xiàn)華為充電動畫為例子,簡單講解一下。

          仔細(xì)觀察這一部分,上升的一個(gè)一個(gè)圓球,拋去這里的一些融合效果,只關(guān)注不斷上升的圓球,看著像是沒有什么規(guī)律可言:

          我們來模擬一下,如果是使用 10 個(gè) animation-durationanimation-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-durationanimation-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ī)動畫效果

          animation-timing-function 緩動函數(shù)

          緩動函數(shù)在動畫中非常重要,它定義了動畫在每一動畫周期中執(zhí)行的節(jié)奏。

          緩動主要分為兩類:

          1. cubic-bezier-timing-function 三次貝塞爾曲線緩動函數(shù)
          2. step-timing-function 步驟緩動函數(shù)(這個(gè)翻譯是我自己翻的,可能有點(diǎn)奇怪)

          三次貝塞爾曲線緩動函數(shù)

          首先先看看三次貝塞爾曲線緩動函數(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ù)

          接下來再講講步驟緩動函數(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. 我們設(shè)定了一個(gè)大小都為 256px 的 div,給這個(gè) div 賦予了一個(gè) animation: sprite .6s steps(6) infinite 動畫;
          2. 其中 steps(6) 的意思就是將設(shè)定的 @keyframes 動畫分為 6 次(6幀)執(zhí)行,而整體的動畫時(shí)間是 0.6s,所以每一幀的停頓時(shí)長為 0.1s
          3. 動畫效果是由 background-position: 0 0background-position: -1536px 0,由于上述的 CSS 代碼沒有設(shè)置 background-repeat,所以其實(shí) background-position: 0 0 是等價(jià)于 background-position: -1536px 0,就是圖片在整個(gè)動畫過程中推進(jìn)了一輪,只不過每一幀停在了特點(diǎn)的地方,一共 6 幀;

          將上述 1、2、3,3 個(gè)步驟畫在圖上簡單示意:

          從上圖可知,其實(shí)在動畫過程中,background-position 的取值其實(shí)只有 background-position: 0 0background-position: -256px 0background-position: -512px 0 依次類推一直到 background-position: -1536px 0,由于背景的 repeat 的特性,其實(shí)剛好回到原點(diǎn),由此又重新開始新一輪同樣的動畫。

          所以,整個(gè)動畫就會是這樣,每一幀停留 0.1s 后切換到下一幀(注意這里是個(gè)無限循環(huán)動畫),:

          完整的代碼你可以戳這里 -- CodePen Demo -- Sprite Animation with steps()

          animation-duration 動畫長短對動畫的影響

          在這里再插入一個(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)鍵字 startend 的差異。對于上述的無限動畫而言,其實(shí)基本是可以忽略不計(jì)的,它主要是控制動畫第一幀的開始和持續(xù)時(shí)長,比較小的一個(gè)知識點(diǎn)但是想講明白需要比較長的篇幅,限于本文的內(nèi)容,在這里不做展開,讀者可以自行了解。

          同個(gè)動畫效果的補(bǔ)間動畫和逐幀動畫演繹對比

          上述的三次貝塞爾曲線緩動和步驟緩動,其實(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

          接下來,我們講講 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 小技巧,默認(rèn)暫停,點(diǎn)擊運(yùn)行

          正常而言,按照正常思路使用 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è)技巧的身影。

          1. 頁面 render 后,無任何操作,動畫不會開始。只有當(dāng)鼠標(biāo)對元素進(jìn)行 click ,通過觸發(fā)元素的 :active 偽類效果的時(shí)候,賦予動畫 animation-play-state: running,動畫才開始進(jìn)行;
          2. 動畫進(jìn)行到任意時(shí)刻,鼠標(biāo)停止點(diǎn)擊,偽類消失,則動畫停止;

          animation-fill-mode 控制元素在各個(gè)階段的狀態(tài)

          下一個(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)系:

          • 橫軸為表示 時(shí)間,為 0 時(shí)表示動畫開始的時(shí)間,也就是向 box 加上 on 類名的時(shí)間,橫軸一格表示 0.5s
          • 縱軸表示 translateY 的值,為 0 時(shí)表示 translateY 的值為 0,縱軸一格表示 50px
          1. animation-fill-mode: none 表現(xiàn)如圖:

          一句話總結(jié),元素在動畫時(shí)間之外,樣式只受到它的 CSS 規(guī)則限制,與 @keyframes 內(nèi)的關(guān)鍵幀定義無關(guān)。

          1. animation-fill-mode: backwards 表現(xiàn)如圖:

          一句話總結(jié),元素在動畫開始之前(包含未觸發(fā)動畫階段及 animation-delay 期間)的樣式為動畫運(yùn)行時(shí)的第一幀,而動畫結(jié)束后的樣式則恢復(fù)為 CSS 規(guī)則設(shè)定的樣式。

          1. animation-fill-mode: forwards 表現(xiàn)如圖:

          一句話總結(jié),元素在動畫開始之前的樣式為 CSS 規(guī)則設(shè)定的樣式,而動畫結(jié)束后的樣式則表現(xiàn)為由執(zhí)行期間遇到的最后一個(gè)關(guān)鍵幀計(jì)算值(也就是停在最后一幀)。

          1. animation-fill-mode: both 表現(xiàn)如圖:

          一句話總結(jié),綜合了 animation-fill-mode: backwardsanimation-fill-mode: forwards 的設(shè)定。動畫開始前的樣式為動畫運(yùn)行時(shí)的第一幀,動畫結(jié)束后停在最后一幀。

          animation-iteration-count/animation-direction 動畫循環(huán)次數(shù)和方向

          講到了 animation-fill-mode,我們就可以順帶講講這個(gè)兩個(gè)比較好理解的屬性 -- animation-iteration-countanimation-direction

          • animation-iteration-count 控制動畫運(yùn)行的次數(shù),可以是數(shù)字或者 infinite,注意,數(shù)字可以是小數(shù)
          • animation-direction 控制動畫的方向,正向、反向、正向交替與反向交替

          在上面講述 animation-fill-mode 時(shí),我使用了動畫運(yùn)行時(shí)的第一幀替代了@keyframes 中定義的第一幀這種說法,因?yàn)閯赢嬤\(yùn)行的第一幀和最后一幀的實(shí)際狀態(tài)還會受到動畫運(yùn)行方向 animation-directionanimation-iteration-count 的影響。

          在 CSS 動畫中,由 animation-iteration-countanimation-direction 共同決定動畫運(yùn)行時(shí)的第一幀和最后一幀的狀態(tài)。

          1. 動畫運(yùn)行的第一幀由 animation-direction 決定
          2. 動畫運(yùn)行的最后一幀由 animation-iteration-countanimation-direction 決定

          動畫的最后一幀,也就是動畫運(yùn)行的最終狀態(tài),并且我們可以利用 animation-fill-mode: forwards 讓動畫在結(jié)束后停留在這一幀,這個(gè)還是比較好理解的,但是 animation-fill-mode: backwardsanimation-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-countanimation-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 分別是 100px300px,配合不同的 animation-direction 初始狀態(tài)如下。

          下圖假設(shè)我們設(shè)置了動畫默認(rèn)是暫停的 -- animation-play-state: paused,那么動畫在開始前的狀態(tài)為:

          動畫的分治與復(fù)用

          講完了每一個(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è)屬性層面的動畫能夠有著更為精確的控制。

          keyframes 規(guī)則的設(shè)定

          我們經(jīng)常能夠在各種不同的 CSS 代碼見到如下兩種 CSS @keyframes 的設(shè)定:

          1. 使用百分比
          @keyframes fadeIn {
              0% {
                  opacity: 1;
              }
              100% {
                  opacity: 0;
              }
          }
          
          1. 使用 fromto
          @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-mode0%from 這一幀是可以刪除的。

          動畫狀態(tài)的高優(yōu)先級性

          我曾經(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ī)則按升序排列):

          • Normal user agent declarations
          • Normal user declarations
          • Normal author declarations
          • Animation declarations
          • Important author declarations
          • Important user declarations
          • Important user agent declarations
          • Transition declarations

          簡單翻譯一下:

          按照上述算法,大概是這樣:

          過渡動畫過程中每一幀的樣式 > 用戶代理、用戶、頁面作者設(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

          CSS 動畫的優(yōu)化

          這也是非常多人非常關(guān)心的一個(gè)重點(diǎn)。

          我的 CSS 動畫很卡,我應(yīng)該如何去優(yōu)化它?

          動畫元素生成獨(dú)立的 GraphicsLayer,強(qiáng)制開始 GPU 加速

          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)容,你可以再看看這幾篇文章:

          • 【W(wǎng)eb動畫】CSS3 3D 行星運(yùn)轉(zhuǎn) && 瀏覽器渲染原理
          • Accelerated Rendering in Chrome

          除了上述準(zhǔn)則之外,還有一些提升 CSS 動畫性能的建議:

          減少使用耗性能樣式

          不同樣式在消耗性能方面是不同的,改變一些屬性的開銷比改變其他屬性要多,因此更可能使動畫卡頓。

          例如,與改變元素的文本顏色相比,改變元素的 box-shadow 將需要開銷大很多的繪圖操作。box-shadow 屬性,從渲染角度來講十分耗性能,原因就是與其他樣式相比,它們的繪制代碼執(zhí)行時(shí)間過長。這就是說,如果一個(gè)耗性能嚴(yán)重的樣式經(jīng)常需要重繪,那么你就會遇到性能問題。

          類似的還有 CSS 3D 變換、mix-blend-modefilter,這些樣式相比其他一些簡單的操作,會更加的消耗性能。我們應(yīng)該盡可能的在動畫過程中降低其使用的頻率或者尋找替代方案。

          當(dāng)然,沒有不變的事情,在今天性能很差的樣式,可能明天就被優(yōu)化,并且瀏覽器之間也存在差異。

          因此關(guān)鍵在于,我們需要針對每一起卡頓的例子,借助開發(fā)工具來分辨出性能瓶頸所在,然后設(shè)法減少瀏覽器的工作量。學(xué)會 Chrome 開發(fā)者工具的 Performance 面板及其他渲染相關(guān)的面板非常重要,當(dāng)然這不是本文的重點(diǎn)。大家可以自行探索。

          使用 will-change 提高頁面滾動、動畫等渲染性能

          will-change 為 Web 開發(fā)者提供了一種告知瀏覽器該元素會有哪些變化的方法,這樣瀏覽器可以在元素屬性真正發(fā)生變化之前提前做好對應(yīng)的優(yōu)化準(zhǔn)備工作。 這種優(yōu)化可以將一部分復(fù)雜的計(jì)算工作提前準(zhǔn)備好,使頁面的反應(yīng)更為快速靈敏。

          值得注意的是,用好這個(gè)屬性并不是很容易:

          • 不要將 will-change 應(yīng)用到太多元素上:瀏覽器已經(jīng)盡力嘗試去優(yōu)化一切可以優(yōu)化的東西了。有一些更強(qiáng)力的優(yōu)化,如果與 will-change 結(jié)合在一起的話,有可能會消耗很多機(jī)器資源,如果過度使用的話,可能導(dǎo)致頁面響應(yīng)緩慢或者消耗非常多的資源。
          • 有節(jié)制地使用:通常,當(dāng)元素恢復(fù)到初始狀態(tài)時(shí),瀏覽器會丟棄掉之前做的優(yōu)化工作。但是如果直接在樣式表中顯式聲明了 will-change 屬性,則表示目標(biāo)元素可能會經(jīng)常變化,瀏覽器會將優(yōu)化工作保存得比之前更久。所以最佳實(shí)踐是當(dāng)元素變化之前和之后通過腳本來切換 will-change 的值。
          • 不要過早應(yīng)用 will-change 優(yōu)化:如果你的頁面在性能方面沒什么問題,則不要添加 will-change 屬性來榨取一丁點(diǎn)的速度。 will-change 的設(shè)計(jì)初衷是作為最后的優(yōu)化手段,用來嘗試解決現(xiàn)有的性能問題。它不應(yīng)該被用來預(yù)防性能問題。過度使用 will-change 會導(dǎo)致大量的內(nèi)存占用,并會導(dǎo)致更復(fù)雜的渲染過程,因?yàn)闉g覽器會試圖準(zhǔn)備可能存在的變化過程。這會導(dǎo)致更嚴(yán)重的性能問題。
          • 給它足夠的工作時(shí)間:這個(gè)屬性是用來讓頁面開發(fā)者告知瀏覽器哪些屬性可能會變化的。然后瀏覽器可以選擇在變化發(fā)生前提前去做一些優(yōu)化工作。所以給瀏覽器一點(diǎn)時(shí)間去真正做這些優(yōu)化工作是非常重要的。使用時(shí)需要嘗試去找到一些方法提前一定時(shí)間獲知元素可能發(fā)生的變化,然后為它加上 will-change 屬性。

          有人說 will-change 是良藥,也有人說是毒藥,在具體使用的時(shí)候,可以多測試一下。

          最后

          好了,本文從多個(gè)方面,由淺入深地描述了 CSS 動畫我認(rèn)為的一些比較重要、值得一講、需要注意的點(diǎn)。當(dāng)然很多地方點(diǎn)到即止,或者限于篇幅沒有完全展開,很多細(xì)節(jié)還需要讀者進(jìn)一步閱讀規(guī)范或者自行嘗試驗(yàn)證,實(shí)踐出真知,紙上得來終覺淺。

          OK,本文到此結(jié)束,希望本文對你有所幫助 :)


          主站蜘蛛池模板: 国产精久久一区二区三区| 无码少妇一区二区三区芒果| 在线观看精品一区| 日韩一区二区视频| 亚洲国产美国国产综合一区二区| 亚洲av无码天堂一区二区三区| 人妻互换精品一区二区| 在线观看精品一区| 高清一区二区在线观看| 欧美日韩一区二区成人午夜电影| 综合无码一区二区三区四区五区| 精品人无码一区二区三区| 亚洲一区二区三区高清不卡| 性色AV一区二区三区天美传媒| 97精品国产一区二区三区| 日本激情一区二区三区| 精品国产天堂综合一区在线| 亚洲av成人一区二区三区在线观看 | 无码午夜人妻一区二区不卡视频| 精品欧美一区二区在线观看| 国产成人av一区二区三区在线| 日本丰满少妇一区二区三区| 久久久99精品一区二区| 一区二区三区精品| 国产成人一区二区在线不卡| 无码人妻精品一区二区三区99不卡 | 久久国产精品无码一区二区三区 | 国产免费一区二区三区| 毛片一区二区三区无码| 丰满人妻一区二区三区视频| 91大神在线精品视频一区| 日本一道高清一区二区三区| 亚洲成av人片一区二区三区| 麻豆文化传媒精品一区二区 | 精品无码成人片一区二区98| 色综合一区二区三区| 人妻无码一区二区三区AV| 一区二区三区无码高清视频| av无码免费一区二区三区| 中文字幕亚洲一区| 后入内射国产一区二区|