Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 国产激情视频,www.色综合,日本一级全黄大片

          整合營(yíng)銷服務(wù)商

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

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

          HTML使用Canvas繪制動(dòng)畫(huà)時(shí)鐘

          么是Canvas

          <canvas> 是HTML中的一個(gè)元素,它可被用來(lái)通過(guò) JavaScript(Canvas API 或 WebGL API)繪制圖形及圖形動(dòng)畫(huà)。

          Canvas API 提供了一個(gè)通過(guò) JavaScriptHTML<canvas> 元素來(lái)繪制圖形的方式。它可以用于動(dòng)畫(huà)、游戲畫(huà)面、數(shù)據(jù)可視化、圖片編輯以及實(shí)時(shí)視頻處理等方面。

          <canvas>標(biāo)簽本身沒(méi)有繪圖能力,它僅僅是圖形的容器。在HTML,一般通過(guò)Javascript語(yǔ)言來(lái)完成實(shí)際的操作。

          創(chuàng)建HTML頁(yè)面并添加繪圖容器

          本文通過(guò)Javascript操作Canvas制作一個(gè)簡(jiǎn)單的顯示當(dāng)前時(shí)間的動(dòng)畫(huà)時(shí)鐘,了解和學(xué)習(xí)簡(jiǎn)單的canvas用法,僅以拋磚引玉。

          首先創(chuàng)建一個(gè)HTML文件,為了方便管理,使用一個(gè)div標(biāo)簽包裹兩個(gè)canvas標(biāo)簽,并加上一些簡(jiǎn)單的css樣式。

          <!doctype html>
          <html lang="zh-cn">
          <head><title>Canvas繪制動(dòng)畫(huà)時(shí)鐘</title>
          <style>
          html,body{margin:0;padding:0}
          #clockWrap {
          	position: relative;
          }
          canvas {
          	position: absolute;
          }
          #clock-ui {
          	z-index: 2;
          }
          #clock-plate {
          	z-index: 1;
          }
          </style>
          </head>
          <body>
            <div id="clockWrap">
            <canvas id="clock-plate"></canvas>
            <canvas id="clock-ui"></canvas>
          </div>
          <script></script>
          </body></html>

          本示例中使用了兩個(gè)canvas標(biāo)簽(為什么使用兩個(gè),一個(gè)不是更簡(jiǎn)單嗎?),一個(gè)用于繪制鐘面,一個(gè)根據(jù)當(dāng)前時(shí)間實(shí)時(shí)顯示和更新時(shí)針、分針和秒針的動(dòng)態(tài)指向。好了,話不多說(shuō),開(kāi)干。

          繪制鐘面刻度

          一個(gè)簡(jiǎn)單的時(shí)鐘,可以分為鐘面上的刻度和指針。其中刻度和12個(gè)數(shù)字是固定的,我們可以將它們繪制在當(dāng)作背景的canvas上(示例中id為clock-plate的canvas)。

          (1)要使用canvas,首先必須通過(guò)容器獲取渲染上下文:

          var $=function(id){return document.querySelector(id);}//這個(gè)函數(shù)只是為了方便獲取dom元素
          const canvasbg=$("#clock-plate"),
                canvas=$("#clock-ui"),
                ctx = canvasbg.getContext("2d"),//背景容器上下文
                ctxUI = canvas.getContext("2d");//指針容器上下文,后面代碼要用
          //定義畫(huà)布寬度和高度,時(shí)鐘圓直徑,并設(shè)置畫(huà)布大小
          const oW=1000,oH=800,cW=400,r=cW/2,oX=oW/2,oY=oH/2;
          canvas.width=oW;
          canvas.height=oH;
          canvasbg.width=oW;
          canvasbg.height=oH;

          (2)畫(huà)鐘的邊框,為了好看,這里畫(huà)兩個(gè)圈:

           //畫(huà)出時(shí)鐘外圓框
            ctx.lineWidth = 12;
          	ctx.beginPath();
          	ctx.arc(oX, oY, r+14, 0, 2 * Math.PI);
          	ctx.stroke();
          	ctx.closePath();
          	ctx.lineWidth = 8;
          	//畫(huà)出時(shí)鐘內(nèi)圓框(刻度圈)
          	ctx.beginPath();
          	ctx.arc(oX, oY, r, 0, 2 * Math.PI);
          	ctx.stroke();
          	ctx.closePath();
          	ctx.beginPath();

          邊框效果圖

          (3)繪制刻度線和數(shù)字,可以利用三角函數(shù)計(jì)算出每個(gè)刻度的坐標(biāo):

          利用三角函數(shù)計(jì)算刻度線的坐標(biāo)位置

          鐘面上有12個(gè)大格,從正上方12開(kāi)始,它們的度數(shù)分別是270,300,330,0,30,60,90,120,150,180,210,240。然后利用JS的Math.sin和Math.cos分別計(jì)算出各大格的坐標(biāo)。注意:js中Math.sin()和Math.cos()的參數(shù)不是角度數(shù)弧度。可以使用Math.PI/180*角度來(lái)轉(zhuǎn)化,比如將30度轉(zhuǎn)換成弧度=Math.PI/180*30

          //繪制鐘表中心點(diǎn)
          	ctx.beginPath();
          	ctx.arc(oX, oY, 8, 0, 2 * Math.PI);//圓心
          	ctx.fill();
          	ctx.closePath();
          	//設(shè)置刻度線粗細(xì)度
          	ctx.lineWidth = 3;
          	//設(shè)置鐘面12個(gè)數(shù)字的字體、大小和對(duì)齊方式
          	ctx.font = "30px serif";
          	ctx.textAlign="center";
          	ctx.textBaseline="middle";
          	var kdx,kdy;
          	//繪制12個(gè)大刻度和12個(gè)數(shù)字
          	//為方便計(jì)算,先定義了0-11這12個(gè)刻度對(duì)應(yīng)的度數(shù),也可以直接定義對(duì)應(yīng)的弧度。
          	const hd=Math.PI/180,degr=[270,300,330,0,30,60,90,120,150,180,210,240];
          	for(var i=0;i<12;i++){
          		kdx=oX+Math.cos(hd*degr[i])*(r-3);
          		kdy=oY+Math.sin(hd*degr[i])*(r-3);
          		ctx.beginPath();
          		ctx.arc(kdx, kdy, 6, 0, 2 * Math.PI);//畫(huà)圓形大刻度
          		ctx.fill();
              //繪制刻度對(duì)應(yīng)的數(shù)字
          		ctx.strokeText(i==0? 12 : i,oX+Math.cos(hd*degr[i])*(r-24),oY+Math.sin(hd*degr[i])*(r-24));
          		ctx.closePath();
          	}
          	
          	//繪制小刻度
          	ctx.lineWidth = 2;
          	for(var i=0;i<60;i++){
          		if(i % 5 == 0) continue;//跳過(guò)與刻度重疊的刻度
          		x0=Math.cos(hd*i*6);
          		y0=Math.sin(hd*i*6);
          		ctx.beginPath();
          		ctx.moveTo(oX+x0*(r-10), oY+y0*(r-10)); 
          		ctx.lineTo(oX+x0*r, oY+y0*r); //畫(huà)短刻度線
          		ctx.stroke(); 
          		ctx.closePath();
          	}

          效果如圖:

          鐘面效果圖

          (4)根據(jù)當(dāng)前時(shí)間繪制指針

          習(xí)慣上,時(shí)針粗短,分針略粗而長(zhǎng),秒針細(xì)長(zhǎng)。為加大區(qū)別,示例中秒針細(xì)長(zhǎng)并且繪制成紅色。

          function drawHp(i){//繪制時(shí)針
          	const x0=Math.cos(hd*i*30),y0=Math.sin(hd*i*30);
          	drawPointer(oX,oY,oX+x0*(r-90),oY+y0*(r-90),10,"#000000");
          }
          function drawMp(i){//繪制分針
          	const x0=Math.cos(hd*i*6),y0=Math.sin(hd*i*6);
          	drawPointer(oX,oY,oX+x0*(r-60),oY+y0*(r-60),5,"#000000");
          }
          function drawSp(i){//繪制秒針
          	const x0=Math.cos(hd*i*6),y0=Math.sin(hd*i*6);
          	drawPointer(oX,oY,oX+x0*(r-20),oY+y0*(r-20),2,"#FF0000");
          }
          //抽取出繪制三種指針時(shí)共同的部分,注意指針繪制在渲染上下文ctxUI中
          function drawPointer(ox,oy,tx,ty,width,color){
          	ctxUI.strokeStyle = color;
          	ctxUI.lineCap = "round";
          	ctxUI.lineWidth = width;
          	ctxUI.beginPath();
          	ctxUI.moveTo(ox, oy);
          	ctxUI.lineTo(tx,ty);
          	ctxUI.stroke();
          	ctxUI.closePath();
          }

          現(xiàn)在已經(jīng)有了繪制三種指針的方法,參數(shù)是當(dāng)前時(shí)間的時(shí)、分和秒,將根據(jù)它們的值確定指針的坐標(biāo)。不過(guò),因?yàn)槭褂玫氖悄J(rèn)的convas坐標(biāo)體系,0值實(shí)際指向3的位置,需要小小的修正一下。

          window.requestAnimationFrame(function fn(){
          		var d = new Date();
          		ctxUI.clearRect(0,0,oW,oH);
          		//度數(shù)從0開(kāi)始,而0在3刻度(15分/秒位置),修正為全值減15,如果小于0則修正回來(lái)
              var hour=d.getHours(),minute=d.getMinutes()-15,second=d.getSeconds()-15;
          		hour=hour>11? hour-15 : hour-3;
          		drawHp(hour>=0? hour : 12+hour);
          		drawMp(minute>=0? minute : 60+minute);
          		drawSp(second>=0? second : 60+second);
          		window.requestAnimationFrame(fn);
          });

          接下來(lái),調(diào)用window.requestAnimationFrame,在其中繪制并更新指標(biāo)的位置。看看效果如何:

          指針繪制情況與實(shí)際時(shí)間相符

          貌似效果有了,截圖時(shí)電腦上的時(shí)間是10時(shí)17分,指針繪制上,時(shí)針指向10時(shí),分針指向17。嗯,感覺(jué)有點(diǎn)別扭?對(duì)了,時(shí)針和分針怎么是端端正正地指向它們的整時(shí)整分刻度上呢?實(shí)際鐘表上時(shí)針和分針是展示動(dòng)態(tài)進(jìn)度的,此時(shí)時(shí)針應(yīng)該越過(guò)10時(shí)的位置才對(duì)。沒(méi)關(guān)系,我們?cè)诶L制時(shí)針和分針時(shí)別點(diǎn)東西,讓它的角度值加上分針和秒針的值試試。

          //修改后的繪制三種指針的方法
          function drawHp(i,f,m){//繪制時(shí)針,參數(shù):時(shí),分,秒
          	const x0=Math.cos(hd*(i+(f/60)+(m/600))*30),y0=Math.sin(hd*(i+(f/60)+(m/600))*30);
          	drawPointer(oX,oY,oX+x0*(r-90),oY+y0*(r-90),10,"#000000");
          }
          function drawMp(i,f){//繪制分針,參數(shù),分,秒
          	const x0=Math.cos(hd*(i+(f/60))*6),y0=Math.sin(hd*(i+(f/60))*6);
          	drawPointer(oX,oY,oX+x0*(r-60),oY+y0*(r-60),5,"#000000");
          }
          function drawSp(i){//繪制秒針
          	const x0=Math.cos(hd*i*6),y0=Math.sin(hd*i*6);
          	drawPointer(oX,oY,oX+x0*(r-20),oY+y0*(r-20),2,"#FF0000");
          }

          再來(lái)看看效果,嗯,立竿見(jiàn)影呀:

          指針指向更合理了

          到此為止,canvas繪制一個(gè)簡(jiǎn)易時(shí)鐘就完成了。下面繼續(xù)優(yōu)化一下。剛才使用requestAnimationFrame方法即時(shí)更新繪制情況。這個(gè)方法與刷新率有關(guān),看看mdn上面怎么說(shuō)的:

          window.requestAnimationFrame() 方法會(huì)告訴瀏覽器你希望執(zhí)行一個(gè)動(dòng)畫(huà)。它要求瀏覽器在下一次重繪之前,調(diào)用用戶提供的回調(diào)函數(shù)。

          對(duì)回調(diào)函數(shù)的調(diào)用頻率通常與顯示器的刷新率相匹配。雖然 75hz、120hz 和 144hz 也被廣泛使用,但是最常見(jiàn)的刷新率還是 60hz(每秒 60 個(gè)周期/幀)。為了提高性能和電池壽命,大多數(shù)瀏覽器都會(huì)暫停在后臺(tái)選項(xiàng)卡或者隱藏的 <iframe> 中運(yùn)行的 requestAnimationFrame()。

          本示例中,更新指針的位置并不需要很高的刷新頻率,可以通過(guò)節(jié)流進(jìn)行一下優(yōu)化:

          var fps = 5, fpsInterval = 1000 / fps,lastTime = new Date().getTime(); //記錄上次執(zhí)行的時(shí)間
          function runStep() {
              requestAnimationFrame(runStep);
              var d=new Date(),now = d.getTime()
              var elapsed = now - lastTime;
              if (elapsed > fpsInterval) {
          				ctxUI.clearRect(0,0,oW,oH);
                  lastTime = now - (elapsed % fpsInterval); 
          			//度數(shù)從0開(kāi)始,而0在3刻度(15分/秒位置),修正為全值-15,如果小于0則用60減回
                  var hour=d.getHours(),minute=d.getMinutes()-15,second=d.getSeconds()-15;//console.log(d.getSeconds(),second);
                  hour=hour>11? hour-15 : hour-3;
                  drawHp(hour>=0? hour : 12+hour,minute+15,second+15);
                  drawMp(minute>=0? minute : 60+minute,second+15);
                  drawSp(second>=0? second : 60+second);
              }
          }
          runStep();

          當(dāng)然,實(shí)現(xiàn)時(shí)鐘的方法是很多,比如可以使用畫(huà)布的旋轉(zhuǎn)(rotate方法)來(lái)實(shí)現(xiàn)指針的動(dòng)態(tài)轉(zhuǎn)動(dòng)等等。

          完整HTML+JS源碼:

          CSS 還原拉斯維加斯球數(shù)字動(dòng)畫(huà) 一文中,我們利用純 CSS,實(shí)現(xiàn)了一個(gè)非常 Amazing 的動(dòng)畫(huà)效果:

          其中一個(gè)核心點(diǎn)就是,我們利用了如下的代碼,在一個(gè) DIV 平面內(nèi),實(shí)現(xiàn)了單個(gè)平面下的隨機(jī)文字隨機(jī)顏色效果。

          效果如下:

          其中的 HTML 代碼大致如下:

          <div class="g-container">
            <div></div>
            // ... 一個(gè) 32 個(gè)子 div
            <div></div>
          </div>
          

          這里為了實(shí)現(xiàn)上述效果,其實(shí)是用了 32 列,每列是一個(gè) DIV

          emmm,對(duì)于追求極致的我們,32 個(gè) DIV 確實(shí)不太優(yōu)雅了。那么,CSS 有沒(méi)有什么方式,能夠單個(gè)標(biāo)簽實(shí)現(xiàn)多列多格子,每個(gè)格子顏色不一致呢?像是這樣:

          答案當(dāng)然是可以。本文,我們就將一起來(lái)探尋,使用 CSS 如何實(shí)現(xiàn)單標(biāo)簽下多色塊,及單標(biāo)簽下隨機(jī)文字隨機(jī)顏色動(dòng)畫(huà)效果。

          iCSS前端趣聞

          不止于 CSS,不止于前端。關(guān)注回復(fù) “css”,入群參與大前端技術(shù)討論,答疑解惑,共同成長(zhǎng)進(jìn)步。

          218篇原創(chuàng)內(nèi)容

          公眾號(hào)

          多重背景的威力

          思考一下,單個(gè) DIV,我們?nèi)绾文軌驅(qū)崿F(xiàn)下述效果呢,譬如一個(gè) DIV 內(nèi),有 36 種不同的顏色:

          這里的核心,其實(shí)就是需要借助多重背景。

          正常而言,我們的 DIV 只能有一個(gè) background,設(shè)置一種顏色,像是這樣:

          <div></div>
          
          div {
              width: 300px;
              height: 300px;
              background: #000;
          }
          

          效果如下:

          但是,合理利用漸變語(yǔ)法的規(guī)則,利用多重漸變,我們就可以實(shí)現(xiàn)多重背景,我們改造一些上述代碼:

          div {
              position: relative;
              margin: auto;
              width: 300px;
              height: 300px;
              background-image: 
                  linear-gradient(90deg, #000, #000),
                  linear-gradient(90deg, #f00, #f00);
              background-size: 50% 100%, 50% 100%;
              background-position: 0 0, 150px 0;
              background-repeat: no-repeat;
          }
          

          利用多重背景的能力,我們就得到了黑色和紅色兩個(gè)色塊:

          我們還可以繼續(xù)拆分,1 拆 4:

          div {
              position: relative;
              margin: auto;
              width: 300px;
              height: 300px;
              background-image: 
                  linear-gradient(90deg, #000, #000),
                  linear-gradient(90deg, #0f0, #0f0),
                  linear-gradient(90deg, #00f, #00f),
                  linear-gradient(90deg, #f00, #f00);
              background-size: 50% 50%, 50% 50%, 50% 50%, 50% 50%;
              background-position: 0 0, 150px 0, 0 150px, 150px 150px;
              background-repeat: no-repeat;
          }
          

          效果如下:

          它其實(shí)是這么個(gè)意思,看下面這張圖就能很好的理解:

          這里我們只標(biāo)識(shí)出了黑色色塊和紅色色塊,另外兩個(gè)色塊的原理也是一樣的。

          理解了這一點(diǎn)之后,我們要實(shí)現(xiàn)如下這個(gè)圖形就非常輕松了:

          當(dāng)然,這里有個(gè)問(wèn)題,我們手動(dòng)去寫(xiě)那么多重漸變的代碼,工作量是非常之大的,因此,我們可以嘗試封裝一個(gè) SCSS 函數(shù)或者 mixin 幫助我們減輕代碼量。

          @use "sass:string";
          
          
          @function randomNum($max, $min: 0, $u: 1) {
              @return ($min + random($max)) * $u;
          }
          
          @mixin randomLinear($rows: 6, $cols: 8) {
            $bg: null;
            $pos: null;
          
            $px: 100% / ($cols - 1);
            $py: 100% / ($rows - 1);
          
            @for $i from 0 through $rows - 1 {
              @for $j from 0 through $cols - 1 {
                @if ($bg) {
                  $bg: $bg + string.unquote(",");  
                  $pos: $pos + string.unquote(",");  
                }
                $color: randomColor();
                $bg: $bg + linear-gradient(to right, $color, $color);
                $pos: $pos + string.unquote("#{$j * $px} #{$i * $py}");
              }
            }
            background: {
              image: $bg;
              size: (100% / $cols) (100% / $rows);
              repeat: no-repeat;
              position: $pos;
            }
          }
          
          @function randomColor() {
              @return rgb(randomNum(205, 50), randomNum(255), randomNum(255));
          }
          
          div {
              @include randomLinear(6, 6);
          }
          

          這里,我們借助 SCSS 封裝了一個(gè) randomLinear 的 mixin,它接收兩個(gè)參數(shù),分別表示行數(shù)和列數(shù),基于上面的 background 拆分,實(shí)現(xiàn)了多重漸變,如此一來(lái),我們就能在單個(gè) DIV 下得到這樣一個(gè)隨機(jī)的多色塊格子圖:

          審查元素,SCSS 編譯后的 CSS 代碼其實(shí)就是這樣的:

          好,在此基礎(chǔ)上要實(shí)現(xiàn)顏色的隨機(jī)變化也非常簡(jiǎn)單,我們只需要配合 filter: hue-rotate() 變換即可。

          代碼如下:

          div {
              @include randomLinear(6, 6);
              animation: colorChange 1s infinite steps(10);
          }
          
          @keyframes colorChange {
              100% {
                  filter: hue-rotate(360deg);
              }
          }
          

          這里巧妙的利用了 steps(10),讓整個(gè)圖形在 1s 內(nèi)進(jìn)行 10 次 hue-rotate() 變化。

          這里的核心點(diǎn)有兩個(gè):

          1. 利用 filter: hue-rotate(360deg) 濾鏡,能夠?qū)崿F(xiàn)顏色的切換
          2. 利用 steps(10) 實(shí)現(xiàn)了逐幀動(dòng)畫(huà)而不是連續(xù)的補(bǔ)間動(dòng)畫(huà)

          如此一來(lái),我們就能得到如下效果,實(shí)現(xiàn)了單個(gè)標(biāo)簽內(nèi)多個(gè)不同色塊,并且可以實(shí)現(xiàn)動(dòng)畫(huà)變換

          結(jié)合 background-clip: text 實(shí)現(xiàn)文字效果

          接下來(lái),我們需要實(shí)現(xiàn)單個(gè)標(biāo)簽下的隨機(jī)文字、隨機(jī)顏色的動(dòng)畫(huà)效果。也就是下圖右邊的效果:

          有了上面的鋪墊,其實(shí)整個(gè)效果就剩下兩步:

          1. 利用 background-clip: text 實(shí)現(xiàn)從色塊到文字的裁剪變化
          2. 借助 SCSS 函數(shù)及 CSS 變量,實(shí)現(xiàn)隨機(jī)文字的變化

          首先,與 CSS 還原拉斯維加斯球數(shù)字動(dòng)畫(huà) 一文中一樣,借助 SCSS 函數(shù),編寫(xiě)一個(gè)隨機(jī)字符的函數(shù),通過(guò)元素的偽元素 content 進(jìn)行設(shè)置,并且,我們把背景色,也設(shè)置給元素的偽元素:

          $str: 'QWERTYUIOPASDFGHJKLZXCVBNMabcdefghigklmnopqrstuvwxyz123456789';
          $length: str-length($str);
          
          @function randomChar() {
              $r: random($length);
              @return str-slice($str, $r, $r);
          }
          
          @function randomChars($number) {
              $value: '';
          
              @if $number > 0 {
                  @for $i from 1 through $number {
                      $value: $value + randomChar();
                  }
              }
              @return $value;
          }
          
          div  {
              width: 300px;
              height: 300px;
              font-size: 50px;
              line-height: 50px;
              letter-spacing: 25px;
              word-wrap: break-word;
              font-family: monospace;
              
              &::before {
                  content: randomChars(36);
                  position: absolute;
                  inset: 0;
                  @include randomLinear(6, 6);
                  color: transparent;
              }
          }
          

          這里,有幾個(gè)細(xì)節(jié)點(diǎn)再簡(jiǎn)單講解一下:

          1. 為了讓每個(gè)字符對(duì)齊,我們使用了 font-family: monospace 等寬字符,并且利用 font-sizeletter-spacing 確保一行只能放下 6 個(gè)字符
          2. 利用 randomChars SCSS 函數(shù),隨機(jī)從我們定義的 $str SCSS 字符串變量中取 36 個(gè)隨機(jī)字符
          3. @include randomLinear(6, 6) 就是上面鋪墊的隨機(jī)漸變背景

          如此一來(lái),我們就能得到這么一個(gè)效果:

          此時(shí),我們只需要再給元素的偽元素設(shè)置一個(gè) background-clip: text 配合文字顏色 transparent,即可得到色塊裁剪到只剩下文字部分的效果:

          div  {
              // ...
              
              &::before {
                  //...
                  color: transparent;
                  background-clip: text;
              }
          }
          

          效果如下:

          好,那如何再讓整個(gè)文字隨機(jī)變換起來(lái)呢?我們只需提前生成多個(gè)不同的隨機(jī)文字 content,然后進(jìn)行動(dòng)畫(huà)切換即可,像是這樣:

          div  {
              // ...
              
              &::before {
                  content: randomChars(36);
                  --content1: "#{randomChars(36)}";
                  --content2: "#{randomChars(36)}";
                   --content3: "#{randomChars(36)}";
                  --content4: "#{randomChars(36)}";
                  --content5: "#{randomChars(36)}";
                  --content6: "#{randomChars(36)}";
                  --content7: "#{randomChars(36)}";
                  --content8: "#{randomChars(36)}";
                  --content9: "#{randomChars(36)}";
                  color: transparent;
                  background-clip: text;
                  animation: contentChange 1.5s infinite linear;
              }
          }
          @keyframes contentChange {
              10% {
                  content: var(--content1);
              }
              20% {
                  content: var(--content2);
              }
              30% {
                  content: var(--content3);
              }
              40% {
                  content: var(--content4);
              }
              50% {
                  content: var(--content5);
              }
              60% {
                  content: var(--content6);
              }
              70% {
                  content: var(--content7);
              }
              80% {
                  content: var(--content8);
              }
              90% {
                  content: var(--content9);
              }
          }
          

          這樣,文字也能隨機(jī)動(dòng)起來(lái)了(當(dāng)然,此處其實(shí)是偽隨機(jī)):

          最后,把上面的 hue-rotate 動(dòng)畫(huà)重新打開(kāi),就能讓文字顏色也隨機(jī)變換!

          至此,完整的代碼如下:

          <div></div>
          
          @use "sass:string";
          @import url('https://fonts.googleapis.com/css2?family=Righteous&family=Ubuntu+Mono&display=swap');
          
          $str: 'QWERTYUIOPASDFGHJKLZXCVBNMabcdefghigklmnopqrstuvwxyz123456789';
          $length: str-length($str);
          
          @function randomNum($max, $min: 0, $u: 1) {
              @return ($min + random($max)) * $u;
          }
          
          @mixin randomLinear($rows: 6, $cols: 8) {
            $bg: null;
            $pos: null;
          
            $px: 100% / ($cols - 1);
            $py: 100% / ($rows - 1);
          
            @for $i from 0 through $rows - 1 {
              @for $j from 0 through $cols - 1 {
                @if ($bg) {
                  $bg: $bg + string.unquote(",");  
                  $pos: $pos + string.unquote(",");  
                }
                $color: randomColor();
                $bg: $bg + linear-gradient(to right, $color, $color);
                $pos: $pos + string.unquote("#{$j * $px} #{$i * $py}");
              }
            }
            background: {
              image: $bg;
              size: (100% / $cols) (100% / $rows);
              repeat: no-repeat;
              position: $pos;
            }
          }
          
          @function randomColor() {
              @return rgb(randomNum(205, 50), randomNum(255), randomNum(255));
          }
          
          @function randomChar() {
              $r: random($length);
              @return str-slice($str, $r, $r);
          }
          
          @function randomChars($number) {
              $value: '';
          
              @if $number > 0 {
                  @for $i from 1 through $number {
                      $value: $value + randomChar();
                  }
              }
              @return $value;
          }
          
          div {
              width: 300px;
              height: 300px;
              color: #fff;
              font-size: 50px;
              line-height: 50px;
              letter-spacing: 25px;
              word-wrap: break-word;
              animation: colorChange 1.5s infinite steps(10);
              
              &::before {
                  --content1: "#{randomChars(36)}";
                  --content2: "#{randomChars(36)}";
                  --content3: "#{randomChars(36)}";
                  --content4: "#{randomChars(36)}";
                  --content5: "#{randomChars(36)}";
                  --content6: "#{randomChars(36)}";
                  --content7: "#{randomChars(36)}";
                  --content8: "#{randomChars(36)}";
                  --content9: "#{randomChars(36)}";
                  content: randomChars(36);
                  position: absolute;
                  inset: 0;
                  color: transparent;
                  background-clip: text;
                  animation: contentChange 1.5s infinite linear;
              }
          }
          
          @keyframes colorChange {
              100% {
                  filter: hue-rotate(340deg);
              }
          }
          @keyframes contentChange {
              10% {
                  content: var(--content1);
              }
              20% {
                  content: var(--content2);
              }
              30% {
                  content: var(--content3);
              }
              40% {
                  content: var(--content4);
              }
              50% {
                  content: var(--content5);
              }
              60% {
                  content: var(--content6);
              }
              70% {
                  content: var(--content7);
              }
              80% {
                  content: var(--content8);
              }
              90% {
                  content: var(--content9);
              }
          }
          

          效果如下:

          完整的代碼,你可以戳這里:CodePen Demo -- Single Div Random Text And Random Color[3]

          多色塊的其他解決方案?

          我們繼續(xù)擴(kuò)展延伸一下,本效果的核心還是如何在一個(gè) DIV 下實(shí)現(xiàn)多種不同的顏色。

          那么,除了上述的技巧,還有其他方式能夠在一個(gè) DIV 下實(shí)現(xiàn)多種不同顏色嗎?

          這里,我們還可以利用內(nèi)聯(lián)元素的 background 展示特性來(lái)實(shí)現(xiàn)。

          什么意思呢?其實(shí) background 的展示,在 塊級(jí)元素狀態(tài)內(nèi)聯(lián)元素狀態(tài) 下的展示規(guī)則是不一樣的。

          表現(xiàn)為 display: inline 內(nèi)聯(lián)元素的 background 展現(xiàn)形式與 display: block 塊級(jí)元素(或者 inline-blockflexgrid)不一致。

          簡(jiǎn)單看個(gè)例子:

          <p>Lorem .....</p><a>Lorem .....</a>
          

          這里需要注意,<p> 元素是塊級(jí)元素,而 <a>內(nèi)聯(lián)元素

          我們給它們統(tǒng)一添加上一個(gè)從綠色到藍(lán)色的漸變背景色:

          p, a {
            background: linear-gradient(90deg, blue, green);
          }
          

          看看效果:

          什么意思呢?區(qū)別很明顯:

          1. 塊級(jí)元素的背景整體是一個(gè)漸變整體
          2. 內(nèi)聯(lián)元素的背景效果是以行為單位進(jìn)行串連的,每一行都是會(huì)有不一樣的效果,每行連起來(lái)整體組成一個(gè)完整的背景效果

          基于這一點(diǎn),我們同樣可以實(shí)現(xiàn)單個(gè) DIV 下的多重背景。

          舉個(gè)例子:

          <div class="g-container">
              <span>ABCDEFGHIJKL</span>
          </div>
          
          div {
              width: 300px;
          }
          span{
              color: #000;
              font-size: 50px;
              line-height: 50px;
              letter-spacing: 25px;
              word-wrap: break-word;
              background: #fc0;
          }
          

          此時(shí),我們只設(shè)置了一個(gè)背景 background: #fc0,效果如下:

          基于上面說(shuō)的技巧,我們改造一下 background: #fc0,拆分成多段漸變背景:

          span{
              //...
              background: linear-gradient(
                  90deg, 
                  #fc0 0 25%, 
                  #0f0 0 50%, 
                  #00f 0 75%, 
                  #f00 0 100%
              );
          }
          

          這里,我們每隔 25%,設(shè)置了一段不同的顏色,如此一來(lái),整個(gè)背景色就變成了 4 塊:

          基于這個(gè)技巧,我們同樣可以封裝一個(gè) SCSS 函數(shù),用于在單個(gè) DIV 下生成多段色塊。代碼如下:

          @use "sass:string";
          @import url('https://fonts.googleapis.com/css2?family=Righteous&family=Ubuntu+Mono&display=swap');
          
          $str: 'QWERTYUIOPASDFGHJKLZXCVBNMabcdefghigklmnopqrstuvwxyz123456789';
          $length: str-length($str);
          
          
          @function randomNum($max, $min: 0, $u: 1) {
              @return ($min + random($max)) * $u;
          }
          @function randomColor() {
              @return rgb(randomNum(205, 50), randomNum(255), randomNum(255));
          }
          @function randomLinear($count) {
              $value: '';
              
              @for $i from 0 through ($count - 1) {
                  $j: $i - 1;
                  $value: $value + randomColor() + string.unquote(" #{$j * 50}px #{$i * 50}px,");
              }
              
              @return linear-gradient(90deg, string.unquote(#{$value}) randomColor() 0 100%);
          }
          
          span {
              background: randomLinear(36, 50);
          }
          

          上面的代碼,我們實(shí)現(xiàn)了一個(gè) randomLinear($count, $width) 的 SCSS 函數(shù),其中:

          1. $count 表示需要的色塊個(gè)數(shù)
          2. $width 表示每個(gè)色塊的寬度

          如此一來(lái),在一個(gè) 300px x 300px 的內(nèi)聯(lián)元素內(nèi),我們同樣可以實(shí)現(xiàn)多個(gè)不同的隨機(jī)顏色。利用這個(gè)技巧,一樣可以實(shí)現(xiàn)單個(gè)平面下的隨機(jī)文字隨機(jī)顏色效果:

          剩余的技巧都是相同的,這里就不再贅述,此技巧的完整代碼,你可以戳這里:CodePen Demo -- Single Div Random Text And Random Color[4]

          最后

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

          更多精彩 CSS 技術(shù)文章匯總在我的 Github -- iCSS[5] ,持續(xù)更新,歡迎點(diǎn)個(gè) star 訂閱收藏。

          如果還有什么疑問(wèn)或者建議,可以多多交流,原創(chuàng)文章,文筆有限,才疏學(xué)淺,文中若有不正之處,萬(wàn)望告知。

          參考資料

          [1]

          CSS 還原拉斯維加斯球數(shù)字動(dòng)畫(huà) - 掘金: https://juejin.cn/post/7290968251911356473

          [2]

          CSS 還原拉斯維加斯球數(shù)字動(dòng)畫(huà) - 掘金: https://juejin.cn/post/7290968251911356473

          [3]

          CodePen Demo -- Single Div Random Text And Random Color: https://codepen.io/Chokcoco/pen/rNPNEWY

          [4]

          CodePen Demo -- Single Div Random Text And Random Color: https://codepen.io/Chokcoco/pen/bGzbXpv?editors=1100

          [5]

          Github -- iCSS: https://github.com/chokcoco/iCSS


          作者:SbCo

          來(lái)源:微信公眾號(hào):iCSS前端趣聞

          出處:https://mp.weixin.qq.com/s/gNhhE7q1hbAmHC_1bk1oHg

          文將比較全面細(xì)致的梳理一下 CSS 動(dòng)畫(huà)的方方面面,針對(duì)每個(gè)屬性用法的講解及進(jìn)階用法的示意,希望能成為一個(gè)比較好的從入門到進(jìn)階的教程。

          CSS 動(dòng)畫(huà)介紹及語(yǔ)法

          首先,我們來(lái)簡(jiǎn)單介紹一下 CSS 動(dòng)畫(huà)。

          最新版本的 CSS 動(dòng)畫(huà)由規(guī)范 -- CSS Animations Level 1 定義。

          CSS 動(dòng)畫(huà)用于實(shí)現(xiàn)元素從一個(gè) CSS 樣式配置轉(zhuǎn)換到另一個(gè) CSS 樣式配置。

          動(dòng)畫(huà)包括兩個(gè)部分: 描述動(dòng)畫(huà)的樣式規(guī)則和用于指定動(dòng)畫(huà)開(kāi)始、結(jié)束以及中間點(diǎn)樣式的關(guān)鍵幀。

          簡(jiǎn)單來(lái)說(shuō),看下面的例子:

          div {
              animation: change 3s;
          }
          
          @keyframes change {
              0% {
                  color: #f00;
              }
              100% {
                  color: #000;
              }
          }
          
          1. animation: move 1s 部分就是動(dòng)畫(huà)的第一部分,用于描述動(dòng)畫(huà)的各個(gè)規(guī)則;
          2. @keyframes move {} 部分就是動(dòng)畫(huà)的第二部分,用于指定動(dòng)畫(huà)開(kāi)始、結(jié)束以及中間點(diǎn)樣式的關(guān)鍵幀;

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

          CSS 動(dòng)畫(huà)的語(yǔ)法

          接下來(lái),我們簡(jiǎn)單看看 CSS 動(dòng)畫(huà)的語(yǔ)法。

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

          animation 的子屬性有:

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

          其中,對(duì)于一個(gè)動(dòng)畫(huà):

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

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

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

          animation-name / animation-duration 詳解

          整體而言,單個(gè)的 animation-nameanimation-duration 沒(méi)有太多的技巧,非常好理解,放在一起。

          首先介紹一下 animation-name,通過(guò) animation-name,CSS 引擎將會(huì)找到對(duì)應(yīng)的 @keyframes 規(guī)則。

          當(dāng)然,它和 CSS 規(guī)則命名一樣,也存在一些騷操作。譬如,他是支持 emoji 表情的,所以代碼中的 animation-name 命名也可以這樣寫(xiě):

          div {
              animation:  3s;
          }
          
          @keyframes  {
              0% {
                  color: #f00;
              }
              100% {
                  color: #000;
              }
          }
          

          animation-duration 設(shè)置動(dòng)畫(huà)一個(gè)周期的時(shí)長(zhǎng),上述 DEMO 中,就是設(shè)定動(dòng)畫(huà)整體持續(xù) 3s,這個(gè)也非常好理解。

          animation-delay 詳解

          animation-delay 就比較有意思了,它可以設(shè)置動(dòng)畫(huà)延時(shí),即從元素加載完成之后到動(dòng)畫(huà)序列開(kāi)始執(zhí)行的這段時(shí)間。

          簡(jiǎn)單的一個(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è)動(dòng)畫(huà),一個(gè)添加了 animation-delay,一個(gè)沒(méi)有,非常直觀:

          上述第二個(gè) div,關(guān)于 animation 屬性,也可以簡(jiǎn)寫(xiě)為 animation: move 2s 1s,第一個(gè)時(shí)間值表示持續(xù)時(shí)間,第二個(gè)時(shí)間值表示延遲時(shí)間。

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

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

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

          有幾種思路:

          1. 初始 3 個(gè)球的位置就是間隔 120°,同時(shí)開(kāi)始旋轉(zhuǎn),但是這樣代碼量會(huì)稍微多一點(diǎn)
          2. 另外一種思路,同一個(gè)動(dòng)畫(huà),3 個(gè)元素的其中兩個(gè)延遲整個(gè)動(dòng)畫(huà)的 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;
          }
          

          但是,在動(dòng)畫(huà)的前 2s,另外兩個(gè)元素是不會(huì)動(dòng)的,只有 2s 過(guò)后,整個(gè)動(dòng)畫(huà)才是我們想要的:

          此時(shí),我們可以讓第 2、3 個(gè)元素的延遲時(shí)間,改為負(fù)值,這樣可以讓動(dòng)畫(huà)延遲進(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è)元素都無(wú)需等待,直接就是運(yùn)動(dòng)狀態(tài)中的,并且元素間隔位置是我們想要的結(jié)果:

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

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

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

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

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

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

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

          我們來(lái)模擬一下,如果是使用 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)動(dòng)會(huì)是這樣的整齊劃一:

          要讓小球的運(yùn)動(dòng)顯得非常的隨機(jī),只需要讓 animation-durationanimation-delay 都在一定范圍內(nèi)浮動(dòng)即可,改造下 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ī),這樣,我們就可以得到非常自然且不同的上升動(dòng)畫(huà)效果,基本不會(huì)出現(xiàn)重復(fù)的畫(huà)面,很好的模擬了隨機(jī)效果:

          CodePen Demo -- 利用范圍隨機(jī) animation-duration 和 animation-delay 實(shí)現(xiàn)隨機(jī)動(dòng)畫(huà)效果

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

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

          緩動(dòng)主要分為兩類:

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

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

          首先先看看三次貝塞爾曲線緩動(dòng)函數(shù)。在 CSS 中,支持一些緩動(dòng)函數(shù)關(guān)鍵字。

          /* Keyword values */
          animation-timing-function: ease;  // 動(dòng)畫(huà)以低速開(kāi)始,然后加快,在結(jié)束前變慢
          animation-timing-function: ease-in;  // 動(dòng)畫(huà)以低速開(kāi)始
          animation-timing-function: ease-out; // 動(dòng)畫(huà)以低速結(jié)束
          animation-timing-function: ease-in-out; // 動(dòng)畫(huà)以低速開(kāi)始和結(jié)束
          animation-timing-function: linear; // 勻速,動(dòng)畫(huà)從頭到尾的速度是相同的
          

          關(guān)于它們之間的效果對(duì)比:

          除了 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ù)。

          三次貝塞爾曲線緩動(dòng)對(duì)動(dòng)畫(huà)的影響

          關(guān)于緩動(dòng)函數(shù)對(duì)動(dòng)畫(huà)的影響,這里有一個(gè)非常好的示例。這里我們使用了純 CSS 實(shí)現(xiàn)了一個(gè)鐘的效果,對(duì)于其中的動(dòng)畫(huà)的運(yùn)動(dòng),如果是 animation-timing-function: linear,效果如下:

          而如果我們我把緩動(dòng)函數(shù)替換一下,變成 animation-timing-function: cubic-bezier(1,-0.21,.85,1.29),它的曲線對(duì)應(yīng)如下:

          整個(gè)鐘的動(dòng)畫(huà)律動(dòng)效果將變成這樣,完全不一樣的感覺(jué):

          CodePen Demo - 緩動(dòng)不同效果不同

          對(duì)于許多精益求精的動(dòng)畫(huà),在設(shè)計(jì)中其實(shí)都考慮到了緩動(dòng)函數(shù)。我很久之前看到過(guò)一篇《基于物理學(xué)的動(dòng)畫(huà)用戶體驗(yàn)設(shè)計(jì)》,可惜如今已經(jīng)無(wú)法找到原文。其中傳達(dá)出的一些概念是,動(dòng)畫(huà)的設(shè)計(jì)依據(jù)實(shí)際在生活中的表現(xiàn)去考量。

          譬如 linear 這個(gè)緩動(dòng),實(shí)際應(yīng)用于某些動(dòng)畫(huà)中會(huì)顯得很不自然,因?yàn)橛捎诳諝庾枇Φ拇嬖冢绦蚰M的勻速直線運(yùn)動(dòng)在現(xiàn)實(shí)生活中是很難實(shí)現(xiàn)的。因此對(duì)于這樣一個(gè)用戶平時(shí)很少感知到的運(yùn)動(dòng)是很難建立信任感的。這樣的勻速直線運(yùn)動(dòng)也是我們?cè)谶M(jìn)行動(dòng)效設(shè)計(jì)時(shí)需要極力避免的。

          步驟緩動(dòng)函數(shù)

          接下來(lái)再講講步驟緩動(dòng)函數(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 中,使用步驟緩動(dòng)函數(shù)最多的,就是利用其來(lái)實(shí)現(xiàn)逐幀動(dòng)畫(huà)。假設(shè)我們有這樣一張圖(圖片大小為 1536 x 256,圖片來(lái)源于網(wǎng)絡(luò)):

          可以發(fā)現(xiàn)它其實(shí)是一個(gè)人物行進(jìn)過(guò)程中的 6 種狀態(tài),或者可以為 6 幀,我們利用 animation-timing-function: steps(6) 可以將其用一個(gè) CSS 動(dòng)畫(huà)串聯(lián)起來(lái),代碼非常的簡(jiǎ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;
            }
          }
          

          簡(jiǎn)單解釋一下上述代碼,首先要知道,剛好 256 x 6 = 1536,所以上述圖片其實(shí)可以剛好均分為 6 段:

          1. 我們?cè)O(shè)定了一個(gè)大小都為 256px 的 div,給這個(gè) div 賦予了一個(gè) animation: sprite .6s steps(6) infinite 動(dòng)畫(huà);
          2. 其中 steps(6) 的意思就是將設(shè)定的 @keyframes 動(dòng)畫(huà)分為 6 次(6幀)執(zhí)行,而整體的動(dòng)畫(huà)時(shí)間是 0.6s,所以每一幀的停頓時(shí)長(zhǎng)為 0.1s
          3. 動(dòng)畫(huà)效果是由 background-position: 0 0background-position: -1536px 0,由于上述的 CSS 代碼沒(méi)有設(shè)置 background-repeat,所以其實(shí) background-position: 0 0 是等價(jià)于 background-position: -1536px 0,就是圖片在整個(gè)動(dòng)畫(huà)過(guò)程中推進(jìn)了一輪,只不過(guò)每一幀停在了特點(diǎn)的地方,一共 6 幀;

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

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

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

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

          animation-duration 動(dòng)畫(huà)長(zhǎng)短對(duì)動(dòng)畫(huà)的影響

          在這里再插入一個(gè)小章節(jié),animation-duration 動(dòng)畫(huà)長(zhǎng)短對(duì)動(dòng)畫(huà)的影響也是非常明顯的。

          在上述代碼的基礎(chǔ)上,我們?cè)傩薷?animation-duration,縮短每一幀的時(shí)間就可以讓步行的效果變成跑步的效果,同理,也可以增加每一幀的停留時(shí)間。讓每一步變得緩慢,就像是在步行一樣。

          需要提出的是,上文說(shuō)的每一幀,和瀏覽器渲染過(guò)程中的 FPS 的每一幀不是同一個(gè)概念。

          看看效果,設(shè)置不同的 animation-duration 的效果(這里是 0.6s -> 0.2s),GIF 錄屏丟失了一些關(guān)鍵幀,實(shí)際效果會(huì)更好點(diǎn):

          當(dāng)然,在 steps() 中,還有 steps(6, start)steps(6, end) 的差異,也就是其中關(guān)鍵字 startend 的差異。對(duì)于上述的無(wú)限動(dòng)畫(huà)而言,其實(shí)基本是可以忽略不計(jì)的,它主要是控制動(dòng)畫(huà)第一幀的開(kāi)始和持續(xù)時(shí)長(zhǎng),比較小的一個(gè)知識(shí)點(diǎn)但是想講明白需要比較長(zhǎng)的篇幅,限于本文的內(nèi)容,在這里不做展開(kāi),讀者可以自行了解。

          同個(gè)動(dòng)畫(huà)效果的補(bǔ)間動(dòng)畫(huà)和逐幀動(dòng)畫(huà)演繹對(duì)比

          上述的三次貝塞爾曲線緩動(dòng)和步驟緩動(dòng),其實(shí)就是對(duì)應(yīng)的補(bǔ)間動(dòng)畫(huà)和逐幀動(dòng)畫(huà)。

          對(duì)于同個(gè)動(dòng)畫(huà)而言,有的時(shí)候兩種緩動(dòng)都是適用的。我們?cè)诰唧w使用的時(shí)候需要具體分析選取。

          假設(shè)我們用 CSS 實(shí)現(xiàn)了這樣一個(gè)圖形:

          現(xiàn)在想利用這個(gè)圖形制作一個(gè) Loading 效果,如果利用補(bǔ)間動(dòng)畫(huà),也就是三次貝塞爾曲線緩動(dòng)的話,讓它旋轉(zhuǎn)起來(lái),得到的效果非常的一般:

          .g-container{
              animation: rotate 2s linear infinite;
          }
          @keyframes rotate {
              0% {
                  transform: rotate(0);
              }
              100% {
                  transform: rotate(360deg);
              }
          }
          

          動(dòng)畫(huà)效果如下:

          但是如果這里,我們將補(bǔ)間動(dòng)畫(huà)換成逐幀動(dòng)畫(huà),因?yàn)橛?20 個(gè)點(diǎn),所以設(shè)置成 steps(20),再看看效果,會(huì)得到完全不一樣的感覺(jué):

          .g-container{
              animation: rotate 2s steps(20) infinite;
          }
          @keyframes rotate {
              0% {
                  transform: rotate(0);
              }
              100% {
                  transform: rotate(360deg);
              }
          }
          

          動(dòng)畫(huà)效果如下:

          整個(gè) loading 的圈圈看上去好像也在旋轉(zhuǎn),實(shí)際上只是 20 幀關(guān)鍵幀在切換,整體的效果感覺(jué)更適合 Loading 的效果。

          因此,兩種動(dòng)畫(huà)效果都是很有必要掌握的,在實(shí)際使用的時(shí)候靈活嘗試,選擇更適合的。

          上述 DEMO 效果完整的代碼:CodePen Demo -- Scale Loading steps vs linear

          animation-play-state

          接下來(lái),我們講講 animation-play-state,顧名思義,它可以控制動(dòng)畫(huà)的狀態(tài) -- 運(yùn)行或者暫停。類似于視頻播放器的開(kāi)始和暫停。是 CSS 動(dòng)畫(huà)中有限的控制動(dòng)畫(huà)狀態(tài)的手段之一。

          它的取值只有兩個(gè)(默認(rèn)為 running):

          {
              animation-play-state: paused | running;
          }
          

          使用起來(lái)也非常簡(jiǎn)單,看下面這個(gè)例子,我們?cè)?hover 按鈕的時(shí)候,實(shí)現(xiàn)動(dòng)畫(huà)的暫停:

          <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è)簡(jiǎn)單的 CSS 動(dòng)畫(huà),但是當(dāng)我們 hover 按鈕的時(shí)候,給動(dòng)畫(huà)元素添加上 animation-play-state: paused

          animation-play-state 小技巧,默認(rèn)暫停,點(diǎn)擊運(yùn)行

          正常而言,按照正常思路使用 animation-play-state: paused 是非常簡(jiǎn)單的。

          但是,如果我們想創(chuàng)造一些有意思的 CSS 動(dòng)畫(huà)效果,不如反其道而行之。

          我們都知道,正常情況下,動(dòng)畫(huà)應(yīng)該是運(yùn)行狀態(tài),那如果我們將一些動(dòng)畫(huà)的默認(rèn)狀態(tài)設(shè)置為暫停,只有當(dāng)鼠標(biāo)點(diǎn)擊或者 hover 的時(shí)候,才設(shè)置其 animation-play-state: running,這樣就可以得到很多有趣的 CSS 效果。

          看個(gè)倒酒的例子,這是一個(gè)純 CSS 動(dòng)畫(huà),但是默認(rèn)狀態(tài)下,動(dòng)畫(huà)處于 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)意交互動(dòng)畫(huà)我們都可以看到這個(gè)技巧的身影。

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

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

          下一個(gè)屬性 animation-fill-mode,很多人會(huì)誤認(rèn)為它只是用于控制元素在動(dòng)畫(huà)結(jié)束后是否復(fù)位。這個(gè)其實(shí)是不準(zhǔn)確的,不全面的。

          看看它的取值:

          {
              // 默認(rèn)值,當(dāng)動(dòng)畫(huà)未執(zhí)行時(shí),動(dòng)畫(huà)將不會(huì)將任何樣式應(yīng)用于目標(biāo),而是使用賦予給該元素的 CSS 規(guī)則來(lái)顯示該元素的狀態(tài)
              animation-fill-mode: none;
              // 動(dòng)畫(huà)將在應(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;    
              // 動(dòng)畫(huà)將遵循 `forwards` 和 `backwards` 的規(guī)則,從而在兩個(gè)方向上擴(kuò)展動(dòng)畫(huà)屬性
              animation-fill-mode: both; 
          }
          

          對(duì)于 animation-fill-mode 的解讀,我在 Segment Fault 上的一個(gè)問(wèn)答中(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)}
          }
          

          使用圖片來(lái)表示 translateY 的值與 時(shí)間 的關(guān)系:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          動(dòng)畫(huà)的最后一幀,也就是動(dòng)畫(huà)運(yùn)行的最終狀態(tài),并且我們可以利用 animation-fill-mode: forwards 讓動(dòng)畫(huà)在結(jié)束后停留在這一幀,這個(gè)還是比較好理解的,但是 animation-fill-mode: backwardsanimation-direction 的關(guān)系很容易弄不清楚,這里簡(jiǎ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 作用下,動(dòng)畫(huà)的初始和結(jié)束狀態(tài)都不一樣。

          如果設(shè)置了 animation-fill-mode: backwards,則元素在動(dòng)畫(huà)未開(kāi)始前的狀態(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ī)則中,元素沒(méi)有設(shè)置位移 transform: translate(0, 0),而在動(dòng)畫(huà)中,第一個(gè)關(guān)鍵幀和最后一個(gè)關(guān)鍵的 translateX 分別是 100px300px,配合不同的 animation-direction 初始狀態(tài)如下。

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

          動(dòng)畫(huà)的分治與復(fù)用

          講完了每一個(gè)屬性,我們?cè)賮?lái)看看一些動(dòng)畫(huà)使用過(guò)程中的細(xì)節(jié)。

          看這樣一個(gè)動(dòng)畫(huà):

          <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 塊下落動(dòng)畫(huà),下落的同時(shí)產(chǎn)生透明度的變化:

          對(duì)于這樣一個(gè)多個(gè)屬性變化的動(dòng)畫(huà),它其實(shí)等價(jià)于:

          div {
              animation: falldown 2s, fadeIn 2s;
          }
          
          @keyframes falldown {
              100% {
                  transform: translate(0, 150px);
              }
          }
          @keyframes fadeIn {
              100% {
                  opacity: 0;
              }
          }
          

          在 CSS 動(dòng)畫(huà)規(guī)則中,animation 是可以接收多個(gè)動(dòng)畫(huà)的,這樣做的目的不僅僅只是為了復(fù)用,同時(shí)也是為了分治,我們對(duì)每一個(gè)屬性層面的動(dòng)畫(huà)能夠有著更為精確的控制。

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

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

          1. 使用百分比
          @keyframes fadeIn {
              0% {
                  opacity: 1;
              }
              100% {
                  opacity: 0;
              }
          }
          
          1. 使用 fromto
          @keyframes fadeIn {
              from {
                  opacity: 1;
              }
              to {
                  opacity: 0;
              }
          }
          

          在 CSS 動(dòng)畫(huà) @keyframes 的定義中,from 等同于 0%,而 to 等同于 100%

          當(dāng)然,當(dāng)我們的關(guān)鍵幀不止 2 幀的時(shí),更推薦使用百分比定義的方式。

          除此之外,當(dāng)動(dòng)畫(huà)的起始幀等同于 CSS 規(guī)則中賦予的值并且沒(méi)有設(shè)定 animation-fill-mode0%from 這一幀是可以刪除的。

          動(dòng)畫(huà)狀態(tài)的高優(yōu)先級(jí)性

          我曾經(jīng)在這篇文章中 -- 深入理解 CSS(Cascading Style Sheets)中的層疊(Cascading) 講過(guò)一個(gè)很有意思的 CSS 現(xiàn)象。

          這也是很多人對(duì) CSS 優(yōu)先級(jí)的一個(gè)認(rèn)知誤區(qū),在 CSS 中,優(yōu)先級(jí)還需要考慮選擇器的層疊(級(jí)聯(lián))順序

          只有在層疊順序相等時(shí),使用哪個(gè)值才取決于樣式的優(yōu)先級(jí)。

          那什么是層疊順序呢?

          根據(jù) CSS Cascading 4 最新標(biāo)準(zhǔn):

          CSS Cascading and Inheritance Level 5(Current Work)

          定義的當(dāng)前規(guī)范下申明的層疊順序優(yōu)先級(jí)如下(越往下的優(yōu)先級(jí)越高,下面的規(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

          簡(jiǎn)單翻譯一下:

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

          過(guò)渡動(dòng)畫(huà)過(guò)程中每一幀的樣式 > 用戶代理、用戶、頁(yè)面作者設(shè)置的!important樣式 > 動(dòng)畫(huà)過(guò)程中每一幀的樣式優(yōu)先級(jí) > 頁(yè)面作者、用戶、用戶代理普通樣式。

          然而,經(jīng)過(guò)多個(gè)瀏覽器的測(cè)試,實(shí)際上并不是這樣。(尷尬了)

          舉個(gè)例子,我們可以通過(guò)這個(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 文本的顏色為綠色,也就是說(shuō),處于動(dòng)畫(huà)狀態(tài)中的樣式,能夠覆蓋掉行內(nèi)樣式中的 !important 樣式,屬于最最高優(yōu)先級(jí)的一種樣式,我們可以通過(guò)無(wú)限動(dòng)畫(huà)、或者 animation-fill-mode: forwards,利用這個(gè)技巧,覆蓋掉本來(lái)應(yīng)該是優(yōu)先級(jí)非常非常高的行內(nèi)樣式中的 !important 樣式。

          我在早兩年的 Chrome 中也能得到同樣的結(jié)果,但是到今天(2022-01-10),最新版的 Chrome 已經(jīng)不支持動(dòng)畫(huà)過(guò)程中關(guān)鍵幀樣式優(yōu)先級(jí)覆蓋行內(nèi)樣式 !important 的特性。

          對(duì)于不同瀏覽器,感興趣的同學(xué)可以利用我這個(gè) DEMO 自行嘗試,CodePen Demo - the priority of CSS Animation

          CSS 動(dòng)畫(huà)的優(yōu)化

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

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

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

          CSS 動(dòng)畫(huà)很卡,其實(shí)是一個(gè)現(xiàn)象描述,它的本質(zhì)其實(shí)是在動(dòng)畫(huà)過(guò)程中,瀏覽器刷新渲染頁(yè)面的幀率過(guò)低。通常而言,目前大多數(shù)瀏覽器刷新率為 60 次/秒,所以通常來(lái)講 FPS 為 60 frame/s 時(shí)動(dòng)畫(huà)效果較好,也就是每幀的消耗時(shí)間為 16.67ms。

          頁(yè)面處于動(dòng)畫(huà)變化時(shí),當(dāng)幀率低于一定數(shù)值時(shí),我們就感覺(jué)到頁(yè)面的卡頓。

          而造成幀率低的原因就是瀏覽器在一幀之間處理的事情太多了,超過(guò)了 16.67ms,要優(yōu)化每一幀的時(shí)間,又需要完整地知道瀏覽器在每一幀干了什么,這個(gè)就又涉及到了老生常談的瀏覽器渲染頁(yè)面。

          到今天,雖然不同瀏覽器的渲染過(guò)程不完全相同,但是基本上大同小異,基本上都是:

          簡(jiǎn)化一下也就是這個(gè)圖:

          這兩張圖,你可以在非常多不同的文章中看到。

          回歸本文的重點(diǎn),Web 動(dòng)畫(huà)很大一部分開(kāi)銷在于層的重繪,以層為基礎(chǔ)的復(fù)合模型對(duì)渲染性能有著深遠(yuǎn)的影響。當(dāng)不需要繪制時(shí),復(fù)合操作的開(kāi)銷可以忽略不計(jì),因此在試著調(diào)試渲染性能問(wèn)題時(shí),首要目標(biāo)就是要避免層的重繪。那么這就給動(dòng)畫(huà)的性能優(yōu)化提供了方向,減少元素的重繪與回流

          這其中,如何減少頁(yè)面的回流與重繪呢,這里就會(huì)運(yùn)用到我們常說(shuō)的** GPU 加速**。

          GPU 加速的本質(zhì)其實(shí)是減少瀏覽器渲染頁(yè)面每一幀過(guò)程中的 reflow 和 repaint,其根本,就是讓需要進(jìn)行動(dòng)畫(huà)的元素,生成自己的 GraphicsLayer

          瀏覽器渲染一個(gè)頁(yè)面時(shí),它使用了許多沒(méi)有暴露給開(kāi)發(fā)者的中間表現(xiàn)形式,其中最重要的結(jié)構(gòu)便是層(layer)。

          在 Chrome 中,存在有不同類型的層: RenderLayer(負(fù)責(zé) DOM 子樹(shù)),GraphicsLayer(負(fù)責(zé) RenderLayer 的子樹(shù))。

          GraphicsLayer ,它對(duì)于我們的 Web 動(dòng)畫(huà)而言非常重要,通常,Chrome 會(huì)將一個(gè)層的內(nèi)容在作為紋理上傳到 GPU 前先繪制(paint)進(jìn)一個(gè)位圖中。如果內(nèi)容不會(huì)改變,那么就沒(méi)有必要重繪(repaint)層。

          而當(dāng)元素生成了自己的 GraphicsLayer 之后,在動(dòng)畫(huà)過(guò)程中,Chrome 并不會(huì)始終重繪整個(gè)層,它會(huì)嘗試智能地去重繪 DOM 中失效的部分,也就是發(fā)生動(dòng)畫(huà)的部分,在 Composite 之前,頁(yè)面是處于一種分層狀態(tài),借助 GPU,瀏覽器僅僅在每一幀對(duì)生成了自己獨(dú)立 GraphicsLayer 元素層進(jìn)行重繪,如此,大大的降低了整個(gè)頁(yè)面重排重繪的開(kāi)銷,提升了頁(yè)面渲染的效率。

          因此,CSS 動(dòng)畫(huà)(Web 動(dòng)畫(huà)同理)優(yōu)化的第一條準(zhǔn)則就是讓需要?jiǎng)赢?huà)的元素生成了自己獨(dú)立的 GraphicsLayer,強(qiáng)制開(kāi)始 GPU 加速,而我們需要知道是,GPU 加速的本質(zhì)是利用讓元素生成了自己獨(dú)立的 GraphicsLayer,降低了頁(yè)面在渲染過(guò)程中重繪重排的開(kāi)銷。

          當(dāng)然,生成自己的獨(dú)立的 GraphicsLayer,不僅僅只有 transform3d api,還有非常多的方式。對(duì)于上述一大段非常繞的內(nèi)容,你可以再看看這幾篇文章:

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

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

          減少使用耗性能樣式

          不同樣式在消耗性能方面是不同的,改變一些屬性的開(kāi)銷比改變其他屬性要多,因此更可能使動(dòng)畫(huà)卡頓。

          例如,與改變?cè)氐奈谋绢伾啾龋淖冊(cè)氐?box-shadow 將需要開(kāi)銷大很多的繪圖操作。box-shadow 屬性,從渲染角度來(lái)講十分耗性能,原因就是與其他樣式相比,它們的繪制代碼執(zhí)行時(shí)間過(guò)長(zhǎng)。這就是說(shuō),如果一個(gè)耗性能嚴(yán)重的樣式經(jīng)常需要重繪,那么你就會(huì)遇到性能問(wèn)題。

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

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

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

          使用 will-change 提高頁(yè)面滾動(dòng)、動(dòng)畫(huà)等渲染性能

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

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

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

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

          最后

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

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


          主站蜘蛛池模板: 国产精品无码AV一区二区三区| 免费高清av一区二区三区| 久久精品国产一区二区三区不卡 | 亚洲乱色熟女一区二区三区丝袜| 成人区精品一区二区不卡亚洲| 亚洲永久无码3D动漫一区| 成人国产一区二区三区| 久久精品免费一区二区| 无码国产精品一区二区免费式芒果 | 国产麻豆精品一区二区三区v视界| 午夜爽爽性刺激一区二区视频| 大香伊人久久精品一区二区| 农村乱人伦一区二区| 国产亚洲情侣一区二区无码AV| 后入内射国产一区二区| 国产成人精品一区二区A片带套| 无码人妻精品一区二区蜜桃网站 | 日韩内射美女人妻一区二区三区 | 国产精品制服丝袜一区| 国产成人无码AV一区二区在线观看| 日韩一区二区三区在线精品| 国产精品日韩一区二区三区| 日本一区中文字幕日本一二三区视频 | 亚洲一区二区在线视频| 大伊香蕉精品一区视频在线 | 九九无码人妻一区二区三区| 精品无码综合一区二区三区| 亚洲愉拍一区二区三区| 亚洲AV一区二区三区四区| 国产精品亚洲一区二区在线观看 | 免费一区二区无码东京热| 天堂Aⅴ无码一区二区三区| 国产裸体歌舞一区二区| 色婷婷一区二区三区四区成人网| 精品国产一区二区三区色欲| 亚洲av无码一区二区三区天堂| 国产第一区二区三区在线观看| 一区二区高清在线| 久久久久一区二区三区| 亚洲高清一区二区三区| 国产伦精品一区二区三区无广告 |