<canvas> 是HTML中的一個元素,它可被用來通過 JavaScript(Canvas API 或 WebGL API)繪制圖形及圖形動畫。
Canvas API 提供了一個通過 JavaScript 和 HTML 的 <canvas> 元素來繪制圖形的方式。它可以用于動畫、游戲畫面、數據可視化、圖片編輯以及實時視頻處理等方面。
<canvas>標簽本身沒有繪圖能力,它僅僅是圖形的容器。在HTML,一般通過Javascript語言來完成實際的操作。
本文通過Javascript操作Canvas制作一個簡單的顯示當前時間的動畫時鐘,了解和學習簡單的canvas用法,僅以拋磚引玉。
首先創建一個HTML文件,為了方便管理,使用一個div標簽包裹兩個canvas標簽,并加上一些簡單的css樣式。
<!doctype html>
<html lang="zh-cn">
<head><title>Canvas繪制動畫時鐘</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>
本示例中使用了兩個canvas標簽(為什么使用兩個,一個不是更簡單嗎?),一個用于繪制鐘面,一個根據當前時間實時顯示和更新時針、分針和秒針的動態指向。好了,話不多說,開干。
一個簡單的時鐘,可以分為鐘面上的刻度和指針。其中刻度和12個數字是固定的,我們可以將它們繪制在當作背景的canvas上(示例中id為clock-plate的canvas)。
(1)要使用canvas,首先必須通過容器獲取渲染上下文:
var $=function(id){return document.querySelector(id);}//這個函數只是為了方便獲取dom元素
const canvasbg=$("#clock-plate"),
canvas=$("#clock-ui"),
ctx = canvasbg.getContext("2d"),//背景容器上下文
ctxUI = canvas.getContext("2d");//指針容器上下文,后面代碼要用
//定義畫布寬度和高度,時鐘圓直徑,并設置畫布大小
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)畫鐘的邊框,為了好看,這里畫兩個圈:
//畫出時鐘外圓框
ctx.lineWidth = 12;
ctx.beginPath();
ctx.arc(oX, oY, r+14, 0, 2 * Math.PI);
ctx.stroke();
ctx.closePath();
ctx.lineWidth = 8;
//畫出時鐘內圓框(刻度圈)
ctx.beginPath();
ctx.arc(oX, oY, r, 0, 2 * Math.PI);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
邊框效果圖
(3)繪制刻度線和數字,可以利用三角函數計算出每個刻度的坐標:
利用三角函數計算刻度線的坐標位置
鐘面上有12個大格,從正上方12開始,它們的度數分別是270,300,330,0,30,60,90,120,150,180,210,240。然后利用JS的Math.sin和Math.cos分別計算出各大格的坐標。注意:js中Math.sin()和Math.cos()的參數不是角度數是弧度。可以使用Math.PI/180*角度來轉化,比如將30度轉換成弧度=Math.PI/180*30
//繪制鐘表中心點
ctx.beginPath();
ctx.arc(oX, oY, 8, 0, 2 * Math.PI);//圓心
ctx.fill();
ctx.closePath();
//設置刻度線粗細度
ctx.lineWidth = 3;
//設置鐘面12個數字的字體、大小和對齊方式
ctx.font = "30px serif";
ctx.textAlign="center";
ctx.textBaseline="middle";
var kdx,kdy;
//繪制12個大刻度和12個數字
//為方便計算,先定義了0-11這12個刻度對應的度數,也可以直接定義對應的弧度。
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);//畫圓形大刻度
ctx.fill();
//繪制刻度對應的數字
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;//跳過與刻度重疊的刻度
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); //畫短刻度線
ctx.stroke();
ctx.closePath();
}
效果如圖:
鐘面效果圖
(4)根據當前時間繪制指針
習慣上,時針粗短,分針略粗而長,秒針細長。為加大區別,示例中秒針細長并且繪制成紅色。
function drawHp(i){//繪制時針
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");
}
//抽取出繪制三種指針時共同的部分,注意指針繪制在渲染上下文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();
}
現在已經有了繪制三種指針的方法,參數是當前時間的時、分和秒,將根據它們的值確定指針的坐標。不過,因為使用的是默認的convas坐標體系,0值實際指向3的位置,需要小小的修正一下。
window.requestAnimationFrame(function fn(){
var d = new Date();
ctxUI.clearRect(0,0,oW,oH);
//度數從0開始,而0在3刻度(15分/秒位置),修正為全值減15,如果小于0則修正回來
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);
});
接下來,調用window.requestAnimationFrame,在其中繪制并更新指標的位置。看看效果如何:
指針繪制情況與實際時間相符
貌似效果有了,截圖時電腦上的時間是10時17分,指針繪制上,時針指向10時,分針指向17。嗯,感覺有點別扭?對了,時針和分針怎么是端端正正地指向它們的整時整分刻度上呢?實際鐘表上時針和分針是展示動態進度的,此時時針應該越過10時的位置才對。沒關系,我們在繪制時針和分針時別點東西,讓它的角度值加上分針和秒針的值試試。
//修改后的繪制三種指針的方法
function drawHp(i,f,m){//繪制時針,參數:時,分,秒
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){//繪制分針,參數,分,秒
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");
}
再來看看效果,嗯,立竿見影呀:
指針指向更合理了
到此為止,canvas繪制一個簡易時鐘就完成了。下面繼續優化一下。剛才使用requestAnimationFrame方法即時更新繪制情況。這個方法與刷新率有關,看看mdn上面怎么說的:
window.requestAnimationFrame() 方法會告訴瀏覽器你希望執行一個動畫。它要求瀏覽器在下一次重繪之前,調用用戶提供的回調函數。
對回調函數的調用頻率通常與顯示器的刷新率相匹配。雖然 75hz、120hz 和 144hz 也被廣泛使用,但是最常見的刷新率還是 60hz(每秒 60 個周期/幀)。為了提高性能和電池壽命,大多數瀏覽器都會暫停在后臺選項卡或者隱藏的 <iframe> 中運行的 requestAnimationFrame()。
本示例中,更新指針的位置并不需要很高的刷新頻率,可以通過節流進行一下優化:
var fps = 5, fpsInterval = 1000 / fps,lastTime = new Date().getTime(); //記錄上次執行的時間
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);
//度數從0開始,而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();
當然,實現時鐘的方法是很多,比如可以使用畫布的旋轉(rotate方法)來實現指針的動態轉動等等。
完整HTML+JS源碼:
CSS 還原拉斯維加斯球數字動畫 一文中,我們利用純 CSS,實現了一個非常 Amazing 的動畫效果:
其中一個核心點就是,我們利用了如下的代碼,在一個 DIV 平面內,實現了單個平面下的隨機文字隨機顏色效果。
效果如下:
其中的 HTML 代碼大致如下:
<div class="g-container">
<div></div>
// ... 一個 32 個子 div
<div></div>
</div>
這里為了實現上述效果,其實是用了 32 列,每列是一個 DIV。
emmm,對于追求極致的我們,32 個 DIV 確實不太優雅了。那么,CSS 有沒有什么方式,能夠單個標簽實現多列多格子,每個格子顏色不一致呢?像是這樣:
答案當然是可以。本文,我們就將一起來探尋,使用 CSS 如何實現單標簽下多色塊,及單標簽下隨機文字隨機顏色動畫效果。
iCSS前端趣聞
不止于 CSS,不止于前端。關注回復 “css”,入群參與大前端技術討論,答疑解惑,共同成長進步。
218篇原創內容
公眾號
思考一下,單個 DIV,我們如何能夠實現下述效果呢,譬如一個 DIV 內,有 36 種不同的顏色:
這里的核心,其實就是需要借助多重背景。
正常而言,我們的 DIV 只能有一個 background,設置一種顏色,像是這樣:
<div></div>
div {
width: 300px;
height: 300px;
background: #000;
}
效果如下:
但是,合理利用漸變語法的規則,利用多重漸變,我們就可以實現多重背景,我們改造一些上述代碼:
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;
}
利用多重背景的能力,我們就得到了黑色和紅色兩個色塊:
我們還可以繼續拆分,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;
}
效果如下:
它其實是這么個意思,看下面這張圖就能很好的理解:
這里我們只標識出了黑色色塊和紅色色塊,另外兩個色塊的原理也是一樣的。
理解了這一點之后,我們要實現如下這個圖形就非常輕松了:
當然,這里有個問題,我們手動去寫那么多重漸變的代碼,工作量是非常之大的,因此,我們可以嘗試封裝一個 SCSS 函數或者 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 封裝了一個 randomLinear 的 mixin,它接收兩個參數,分別表示行數和列數,基于上面的 background 拆分,實現了多重漸變,如此一來,我們就能在單個 DIV 下得到這樣一個隨機的多色塊格子圖:
審查元素,SCSS 編譯后的 CSS 代碼其實就是這樣的:
好,在此基礎上要實現顏色的隨機變化也非常簡單,我們只需要配合 filter: hue-rotate() 變換即可。
代碼如下:
div {
@include randomLinear(6, 6);
animation: colorChange 1s infinite steps(10);
}
@keyframes colorChange {
100% {
filter: hue-rotate(360deg);
}
}
這里巧妙的利用了 steps(10),讓整個圖形在 1s 內進行 10 次 hue-rotate() 變化。
這里的核心點有兩個:
如此一來,我們就能得到如下效果,實現了單個標簽內多個不同色塊,并且可以實現動畫變換:
接下來,我們需要實現單個標簽下的隨機文字、隨機顏色的動畫效果。也就是下圖右邊的效果:
有了上面的鋪墊,其實整個效果就剩下兩步:
首先,與 CSS 還原拉斯維加斯球數字動畫 一文中一樣,借助 SCSS 函數,編寫一個隨機字符的函數,通過元素的偽元素 content 進行設置,并且,我們把背景色,也設置給元素的偽元素:
$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;
}
}
這里,有幾個細節點再簡單講解一下:
如此一來,我們就能得到這么一個效果:
此時,我們只需要再給元素的偽元素設置一個 background-clip: text 配合文字顏色 transparent,即可得到色塊裁剪到只剩下文字部分的效果:
div {
// ...
&::before {
//...
color: transparent;
background-clip: text;
}
}
效果如下:
好,那如何再讓整個文字隨機變換起來呢?我們只需提前生成多個不同的隨機文字 content,然后進行動畫切換即可,像是這樣:
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);
}
}
這樣,文字也能隨機動起來了(當然,此處其實是偽隨機):
最后,把上面的 hue-rotate 動畫重新打開,就能讓文字顏色也隨機變換!
至此,完整的代碼如下:
<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]
我們繼續擴展延伸一下,本效果的核心還是如何在一個 DIV 下實現多種不同的顏色。
那么,除了上述的技巧,還有其他方式能夠在一個 DIV 下實現多種不同顏色嗎?
這里,我們還可以利用內聯元素的 background 展示特性來實現。
什么意思呢?其實 background 的展示,在 塊級元素狀態 和 內聯元素狀態 下的展示規則是不一樣的。
表現為 display: inline 內聯元素的 background 展現形式與 display: block 塊級元素(或者 inline-block、flex、grid)不一致。
簡單看個例子:
<p>Lorem .....</p><a>Lorem .....</a>
這里需要注意,<p> 元素是塊級元素,而 <a> 是內聯元素。
我們給它們統一添加上一個從綠色到藍色的漸變背景色:
p, a {
background: linear-gradient(90deg, blue, green);
}
看看效果:
什么意思呢?區別很明顯:
基于這一點,我們同樣可以實現單個 DIV 下的多重背景。
舉個例子:
<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;
}
此時,我們只設置了一個背景 background: #fc0,效果如下:
基于上面說的技巧,我們改造一下 background: #fc0,拆分成多段漸變背景:
span{
//...
background: linear-gradient(
90deg,
#fc0 0 25%,
#0f0 0 50%,
#00f 0 75%,
#f00 0 100%
);
}
這里,我們每隔 25%,設置了一段不同的顏色,如此一來,整個背景色就變成了 4 塊:
基于這個技巧,我們同樣可以封裝一個 SCSS 函數,用于在單個 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);
}
上面的代碼,我們實現了一個 randomLinear($count, $width) 的 SCSS 函數,其中:
如此一來,在一個 300px x 300px 的內聯元素內,我們同樣可以實現多個不同的隨機顏色。利用這個技巧,一樣可以實現單個平面下的隨機文字隨機顏色效果:
剩余的技巧都是相同的,這里就不再贅述,此技巧的完整代碼,你可以戳這里:CodePen Demo -- Single Div Random Text And Random Color[4]
本文到此結束,希望對你有幫助 :)
更多精彩 CSS 技術文章匯總在我的 Github -- iCSS[5] ,持續更新,歡迎點個 star 訂閱收藏。
如果還有什么疑問或者建議,可以多多交流,原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。
[1]
CSS 還原拉斯維加斯球數字動畫 - 掘金: https://juejin.cn/post/7290968251911356473
[2]
CSS 還原拉斯維加斯球數字動畫 - 掘金: 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
來源:微信公眾號:iCSS前端趣聞
出處:https://mp.weixin.qq.com/s/gNhhE7q1hbAmHC_1bk1oHg
文將比較全面細致的梳理一下 CSS 動畫的方方面面,針對每個屬性用法的講解及進階用法的示意,希望能成為一個比較好的從入門到進階的教程。
首先,我們來簡單介紹一下 CSS 動畫。
最新版本的 CSS 動畫由規范 -- CSS Animations Level 1 定義。
CSS 動畫用于實現元素從一個 CSS 樣式配置轉換到另一個 CSS 樣式配置。
動畫包括兩個部分: 描述動畫的樣式規則和用于指定動畫開始、結束以及中間點樣式的關鍵幀。
簡單來說,看下面的例子:
div {
animation: change 3s;
}
@keyframes change {
0% {
color: #f00;
}
100% {
color: #000;
}
}
一個 CSS 動畫一定要由上述兩部分組成。
接下來,我們簡單看看 CSS 動畫的語法。
創建動畫序列,需要使用 animation 屬性或其子屬性,該屬性允許配置動畫時間、時長以及其他動畫細節,但該屬性不能配置動畫的實際表現,動畫的實際表現是由 @keyframes 規則實現。
animation 的子屬性有:
其中,對于一個動畫:
上面已經給了一個簡單的 DEMO, 就用上述的 DEMO,看看結果:
這就是一個最基本的 CSS 動畫,本文將從 animation 的各個子屬性入手,探究 CSS 動畫的方方面面。
整體而言,單個的 animation-name 和 animation-duration 沒有太多的技巧,非常好理解,放在一起。
首先介紹一下 animation-name,通過 animation-name,CSS 引擎將會找到對應的 @keyframes 規則。
當然,它和 CSS 規則命名一樣,也存在一些騷操作。譬如,他是支持 emoji 表情的,所以代碼中的 animation-name 命名也可以這樣寫:
div {
animation: 3s;
}
@keyframes {
0% {
color: #f00;
}
100% {
color: #000;
}
}
而 animation-duration 設置動畫一個周期的時長,上述 DEMO 中,就是設定動畫整體持續 3s,這個也非常好理解。
animation-delay 就比較有意思了,它可以設置動畫延時,即從元素加載完成之后到動畫序列開始執行的這段時間。
簡單的一個 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);
}
}
比較下列兩個動畫,一個添加了 animation-delay,一個沒有,非常直觀:
上述第二個 div,關于 animation 屬性,也可以簡寫為 animation: move 2s 1s,第一個時間值表示持續時間,第二個時間值表示延遲時間。
關于 animation-delay,最有意思的技巧在于,它可以是負數。也就是說,雖然屬性名是動畫延遲時間,但是運用了負數之后,動畫可以提前進行。
假設我們要實現這樣一個 loading 動畫效果:
有幾種思路:
方案 2 的核心偽代碼如下:
.item:nth-child(1) {
animation: rotate 3s infinite linear;
}
.item:nth-child(2) {
animation: rotate 3s infinite 1s linear;
}
.item:nth-child(3) {
animation: rotate 3s infinite 2s linear;
}
但是,在動畫的前 2s,另外兩個元素是不會動的,只有 2s 過后,整個動畫才是我們想要的:
此時,我們可以讓第 2、3 個元素的延遲時間,改為負值,這樣可以讓動畫延遲進行 -1s、-2s,也就是提前進行 1s、2s:
.item:nth-child(1) {
animation: rotate 3s infinite linear;
}
.item:nth-child(2) {
animation: rotate 3s infinite -1s linear;
}
.item:nth-child(3) {
animation: rotate 3s infinite -2s linear;
}
這樣,每個元素都無需等待,直接就是運動狀態中的,并且元素間隔位置是我們想要的結果:
還有一個有意思的小技巧。
同一個動畫,我們利用一定范圍內隨機的 animation-duration 和一定范圍內隨機的 animation-delay,可以有效的構建更為隨機的動畫效果,讓動畫更加的自然。
我在下述兩個純 CSS 動畫中,都使用了這樣的技巧:
以純 CSS 實現華為充電動畫為例子,簡單講解一下。
仔細觀察這一部分,上升的一個一個圓球,拋去這里的一些融合效果,只關注不斷上升的圓球,看著像是沒有什么規律可言:
我們來模擬一下,如果是使用 10 個 animation-duration 和 animation-delay 都一致的圓的話,核心偽代碼:
<ul>
<li></li>
<!--共 10 個...-->
<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);
}
}
這樣,小球的運動會是這樣的整齊劃一:
要讓小球的運動顯得非常的隨機,只需要讓 animation-duration 和 animation-delay 都在一定范圍內浮動即可,改造下 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 的循環和 random() 函數,讓 animation-duration 在 2-4 秒范圍內隨機,讓 animation-delay 在 1-2 秒范圍內隨機,這樣,我們就可以得到非常自然且不同的上升動畫效果,基本不會出現重復的畫面,很好的模擬了隨機效果:
CodePen Demo -- 利用范圍隨機 animation-duration 和 animation-delay 實現隨機動畫效果
緩動函數在動畫中非常重要,它定義了動畫在每一動畫周期中執行的節奏。
緩動主要分為兩類:
首先先看看三次貝塞爾曲線緩動函數。在 CSS 中,支持一些緩動函數關鍵字。
/* Keyword values */
animation-timing-function: ease; // 動畫以低速開始,然后加快,在結束前變慢
animation-timing-function: ease-in; // 動畫以低速開始
animation-timing-function: ease-out; // 動畫以低速結束
animation-timing-function: ease-in-out; // 動畫以低速開始和結束
animation-timing-function: linear; // 勻速,動畫從頭到尾的速度是相同的
關于它們之間的效果對比:
除了 CSS 支持的這 5 個關鍵字,我們還可以使用 cubic-bezier() 方法自定義三次貝塞爾曲線:
animation-timing-function: cubic-bezier(0.1, 0.7, 1.0, 0.1);
這里有個非常好用的網站 -- cubic-bezier 用于創建和調試生成不同的貝塞爾曲線參數。
關于緩動函數對動畫的影響,這里有一個非常好的示例。這里我們使用了純 CSS 實現了一個鐘的效果,對于其中的動畫的運動,如果是 animation-timing-function: linear,效果如下:
而如果我們我把緩動函數替換一下,變成 animation-timing-function: cubic-bezier(1,-0.21,.85,1.29),它的曲線對應如下:
整個鐘的動畫律動效果將變成這樣,完全不一樣的感覺:
CodePen Demo - 緩動不同效果不同
對于許多精益求精的動畫,在設計中其實都考慮到了緩動函數。我很久之前看到過一篇《基于物理學的動畫用戶體驗設計》,可惜如今已經無法找到原文。其中傳達出的一些概念是,動畫的設計依據實際在生活中的表現去考量。
譬如 linear 這個緩動,實際應用于某些動畫中會顯得很不自然,因為由于空氣阻力的存在,程序模擬的勻速直線運動在現實生活中是很難實現的。因此對于這樣一個用戶平時很少感知到的運動是很難建立信任感的。這樣的勻速直線運動也是我們在進行動效設計時需要極力避免的。
接下來再講講步驟緩動函數。在 CSS 的 animation-timing-function 中,它有如下幾種表現形態:
{
/* 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 中,使用步驟緩動函數最多的,就是利用其來實現逐幀動畫。假設我們有這樣一張圖(圖片大小為 1536 x 256,圖片來源于網絡):
可以發現它其實是一個人物行進過程中的 6 種狀態,或者可以為 6 幀,我們利用 animation-timing-function: steps(6) 可以將其用一個 CSS 動畫串聯起來,代碼非常的簡單:
<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,所以上述圖片其實可以剛好均分為 6 段:
將上述 1、2、3,3 個步驟畫在圖上簡單示意:
從上圖可知,其實在動畫過程中,background-position 的取值其實只有 background-position: 0 0,background-position: -256px 0,background-position: -512px 0 依次類推一直到 background-position: -1536px 0,由于背景的 repeat 的特性,其實剛好回到原點,由此又重新開始新一輪同樣的動畫。
所以,整個動畫就會是這樣,每一幀停留 0.1s 后切換到下一幀(注意這里是個無限循環動畫),:
完整的代碼你可以戳這里 -- CodePen Demo -- Sprite Animation with steps()
在這里再插入一個小章節,animation-duration 動畫長短對動畫的影響也是非常明顯的。
在上述代碼的基礎上,我們再修改 animation-duration,縮短每一幀的時間就可以讓步行的效果變成跑步的效果,同理,也可以增加每一幀的停留時間。讓每一步變得緩慢,就像是在步行一樣。
需要提出的是,上文說的每一幀,和瀏覽器渲染過程中的 FPS 的每一幀不是同一個概念。
看看效果,設置不同的 animation-duration 的效果(這里是 0.6s -> 0.2s),GIF 錄屏丟失了一些關鍵幀,實際效果會更好點:
當然,在 steps() 中,還有 steps(6, start) 和 steps(6, end) 的差異,也就是其中關鍵字 start 和 end 的差異。對于上述的無限動畫而言,其實基本是可以忽略不計的,它主要是控制動畫第一幀的開始和持續時長,比較小的一個知識點但是想講明白需要比較長的篇幅,限于本文的內容,在這里不做展開,讀者可以自行了解。
上述的三次貝塞爾曲線緩動和步驟緩動,其實就是對應的補間動畫和逐幀動畫。
對于同個動畫而言,有的時候兩種緩動都是適用的。我們在具體使用的時候需要具體分析選取。
假設我們用 CSS 實現了這樣一個圖形:
現在想利用這個圖形制作一個 Loading 效果,如果利用補間動畫,也就是三次貝塞爾曲線緩動的話,讓它旋轉起來,得到的效果非常的一般:
.g-container{
animation: rotate 2s linear infinite;
}
@keyframes rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
動畫效果如下:
但是如果這里,我們將補間動畫換成逐幀動畫,因為有 20 個點,所以設置成 steps(20),再看看效果,會得到完全不一樣的感覺:
.g-container{
animation: rotate 2s steps(20) infinite;
}
@keyframes rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
動畫效果如下:
整個 loading 的圈圈看上去好像也在旋轉,實際上只是 20 幀關鍵幀在切換,整體的效果感覺更適合 Loading 的效果。
因此,兩種動畫效果都是很有必要掌握的,在實際使用的時候靈活嘗試,選擇更適合的。
上述 DEMO 效果完整的代碼:CodePen Demo -- Scale Loading steps vs linear
接下來,我們講講 animation-play-state,顧名思義,它可以控制動畫的狀態 -- 運行或者暫停。類似于視頻播放器的開始和暫停。是 CSS 動畫中有限的控制動畫狀態的手段之一。
它的取值只有兩個(默認為 running):
{
animation-play-state: paused | running;
}
使用起來也非常簡單,看下面這個例子,我們在 hover 按鈕的時候,實現動畫的暫停:
<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;
}
一個簡單的 CSS 動畫,但是當我們 hover 按鈕的時候,給動畫元素添加上 animation-play-state: paused:
正常而言,按照正常思路使用 animation-play-state: paused 是非常簡單的。
但是,如果我們想創造一些有意思的 CSS 動畫效果,不如反其道而行之。
我們都知道,正常情況下,動畫應該是運行狀態,那如果我們將一些動畫的默認狀態設置為暫停,只有當鼠標點擊或者 hover 的時候,才設置其 animation-play-state: running,這樣就可以得到很多有趣的 CSS 效果。
看個倒酒的例子,這是一個純 CSS 動畫,但是默認狀態下,動畫處于 animation-play-state: paused,也就是暫停狀態,只有當鼠標點擊杯子的時,才設置 animation-play-state: running,讓酒倒下,利用 animation-play-state 實現了一個非常有意思的交互效果:
完整的 DEMO 你可以戳這里:CodePen Demo -- CSS Beer!
在非常多 Web 創意交互動畫我們都可以看到這個技巧的身影。
下一個屬性 animation-fill-mode,很多人會誤認為它只是用于控制元素在動畫結束后是否復位。這個其實是不準確的,不全面的。
看看它的取值:
{
// 默認值,當動畫未執行時,動畫將不會將任何樣式應用于目標,而是使用賦予給該元素的 CSS 規則來顯示該元素的狀態
animation-fill-mode: none;
// 動畫將在應用于目標時立即應用第一個關鍵幀中定義的值,并在 `animation-delay` 期間保留此值,
animation-fill-mode: backwards;
// 目標將保留由執行期間遇到的最后一個關鍵幀計算值。 最后一個關鍵幀取決于 `animation-direction` 和 `animation-iteration-count`
animation-fill-mode: forwards;
// 動畫將遵循 `forwards` 和 `backwards` 的規則,從而在兩個方向上擴展動畫屬性
animation-fill-mode: both;
}
對于 animation-fill-mode 的解讀,我在 Segment Fault 上的一個問答中(SF - 如何理解 animation-fill-mode)看到了 4 副很好的解讀圖,這里借用一下:
假設 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 的值與 時間 的關系:
一句話總結,元素在動畫時間之外,樣式只受到它的 CSS 規則限制,與 @keyframes 內的關鍵幀定義無關。
一句話總結,元素在動畫開始之前(包含未觸發動畫階段及 animation-delay 期間)的樣式為動畫運行時的第一幀,而動畫結束后的樣式則恢復為 CSS 規則設定的樣式。
一句話總結,元素在動畫開始之前的樣式為 CSS 規則設定的樣式,而動畫結束后的樣式則表現為由執行期間遇到的最后一個關鍵幀計算值(也就是停在最后一幀)。
一句話總結,綜合了 animation-fill-mode: backwards 和 animation-fill-mode: forwards 的設定。動畫開始前的樣式為動畫運行時的第一幀,動畫結束后停在最后一幀。
講到了 animation-fill-mode,我們就可以順帶講講這個兩個比較好理解的屬性 -- animation-iteration-count 和 animation-direction
在上面講述 animation-fill-mode 時,我使用了動畫運行時的第一幀替代了@keyframes 中定義的第一幀這種說法,因為動畫運行的第一幀和最后一幀的實際狀態還會受到動畫運行方向 animation-direction 和 animation-iteration-count 的影響。
在 CSS 動畫中,由 animation-iteration-count 和 animation-direction 共同決定動畫運行時的第一幀和最后一幀的狀態。
動畫的最后一幀,也就是動畫運行的最終狀態,并且我們可以利用 animation-fill-mode: forwards 讓動畫在結束后停留在這一幀,這個還是比較好理解的,但是 animation-fill-mode: backwards 和 animation-direction 的關系很容易弄不清楚,這里簡答講解下。
設置一個 100px x 100px 的滑塊,在一個 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;
}
表現如下:
那么,加入 animation 之后,在不同的 animation-iteration-count 和 animation-direction 作用下,動畫的初始和結束狀態都不一樣。
如果設置了 animation-fill-mode: backwards,則元素在動畫未開始前的狀態由 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 規則中,元素沒有設置位移 transform: translate(0, 0),而在動畫中,第一個關鍵幀和最后一個關鍵的 translateX 分別是 100px、300px,配合不同的 animation-direction 初始狀態如下。
下圖假設我們設置了動畫默認是暫停的 -- animation-play-state: paused,那么動畫在開始前的狀態為:
講完了每一個屬性,我們再來看看一些動畫使用過程中的細節。
看這樣一個動畫:
<div></div>
div {
width: 100px;
height: 100px;
background: #000;
animation: combine 2s;
}
@keyframes combine {
100% {
transform: translate(0, 150px);
opacity: 0;
}
}
這里我們實現了一個 div 塊下落動畫,下落的同時產生透明度的變化:
對于這樣一個多個屬性變化的動畫,它其實等價于:
div {
animation: falldown 2s, fadeIn 2s;
}
@keyframes falldown {
100% {
transform: translate(0, 150px);
}
}
@keyframes fadeIn {
100% {
opacity: 0;
}
}
在 CSS 動畫規則中,animation 是可以接收多個動畫的,這樣做的目的不僅僅只是為了復用,同時也是為了分治,我們對每一個屬性層面的動畫能夠有著更為精確的控制。
我們經常能夠在各種不同的 CSS 代碼見到如下兩種 CSS @keyframes 的設定:
@keyframes fadeIn {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes fadeIn {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
在 CSS 動畫 @keyframes 的定義中,from 等同于 0%,而 to 等同于 100%。
當然,當我們的關鍵幀不止 2 幀的時,更推薦使用百分比定義的方式。
除此之外,當動畫的起始幀等同于 CSS 規則中賦予的值并且沒有設定 animation-fill-mode,0% 和 from 這一幀是可以刪除的。
我曾經在這篇文章中 -- 深入理解 CSS(Cascading Style Sheets)中的層疊(Cascading) 講過一個很有意思的 CSS 現象。
這也是很多人對 CSS 優先級的一個認知誤區,在 CSS 中,優先級還需要考慮選擇器的層疊(級聯)順序。
只有在層疊順序相等時,使用哪個值才取決于樣式的優先級。
那什么是層疊順序呢?
根據 CSS Cascading 4 最新標準:
CSS Cascading and Inheritance Level 5(Current Work)
定義的當前規范下申明的層疊順序優先級如下(越往下的優先級越高,下面的規則按升序排列):
簡單翻譯一下:
按照上述算法,大概是這樣:
過渡動畫過程中每一幀的樣式 > 用戶代理、用戶、頁面作者設置的!important樣式 > 動畫過程中每一幀的樣式優先級 > 頁面作者、用戶、用戶代理普通樣式。
然而,經過多個瀏覽器的測試,實際上并不是這樣。(尷尬了)
舉個例子,我們可以通過這個特性,覆蓋掉行內樣式中的 !important 樣式:
<p class="txt" style="color:red!important">123456789</p>
.txt {
animation: colorGreen 2s infinite;
}
@keyframes colorGreen {
0%,
100% {
color: green;
}
}
在 Safari 瀏覽器下,上述 DEMO 文本的顏色為綠色,也就是說,處于動畫狀態中的樣式,能夠覆蓋掉行內樣式中的 !important 樣式,屬于最最高優先級的一種樣式,我們可以通過無限動畫、或者 animation-fill-mode: forwards,利用這個技巧,覆蓋掉本來應該是優先級非常非常高的行內樣式中的 !important 樣式。
我在早兩年的 Chrome 中也能得到同樣的結果,但是到今天(2022-01-10),最新版的 Chrome 已經不支持動畫過程中關鍵幀樣式優先級覆蓋行內樣式 !important 的特性。
對于不同瀏覽器,感興趣的同學可以利用我這個 DEMO 自行嘗試,CodePen Demo - the priority of CSS Animation
這也是非常多人非常關心的一個重點。
我的 CSS 動畫很卡,我應該如何去優化它?
CSS 動畫很卡,其實是一個現象描述,它的本質其實是在動畫過程中,瀏覽器刷新渲染頁面的幀率過低。通常而言,目前大多數瀏覽器刷新率為 60 次/秒,所以通常來講 FPS 為 60 frame/s 時動畫效果較好,也就是每幀的消耗時間為 16.67ms。
頁面處于動畫變化時,當幀率低于一定數值時,我們就感覺到頁面的卡頓。
而造成幀率低的原因就是瀏覽器在一幀之間處理的事情太多了,超過了 16.67ms,要優化每一幀的時間,又需要完整地知道瀏覽器在每一幀干了什么,這個就又涉及到了老生常談的瀏覽器渲染頁面。
到今天,雖然不同瀏覽器的渲染過程不完全相同,但是基本上大同小異,基本上都是:
簡化一下也就是這個圖:
這兩張圖,你可以在非常多不同的文章中看到。
回歸本文的重點,Web 動畫很大一部分開銷在于層的重繪,以層為基礎的復合模型對渲染性能有著深遠的影響。當不需要繪制時,復合操作的開銷可以忽略不計,因此在試著調試渲染性能問題時,首要目標就是要避免層的重繪。那么這就給動畫的性能優化提供了方向,減少元素的重繪與回流。
這其中,如何減少頁面的回流與重繪呢,這里就會運用到我們常說的** GPU 加速**。
GPU 加速的本質其實是減少瀏覽器渲染頁面每一幀過程中的 reflow 和 repaint,其根本,就是讓需要進行動畫的元素,生成自己的 GraphicsLayer。
瀏覽器渲染一個頁面時,它使用了許多沒有暴露給開發者的中間表現形式,其中最重要的結構便是層(layer)。
在 Chrome 中,存在有不同類型的層: RenderLayer(負責 DOM 子樹),GraphicsLayer(負責 RenderLayer 的子樹)。
GraphicsLayer ,它對于我們的 Web 動畫而言非常重要,通常,Chrome 會將一個層的內容在作為紋理上傳到 GPU 前先繪制(paint)進一個位圖中。如果內容不會改變,那么就沒有必要重繪(repaint)層。
而當元素生成了自己的 GraphicsLayer 之后,在動畫過程中,Chrome 并不會始終重繪整個層,它會嘗試智能地去重繪 DOM 中失效的部分,也就是發生動畫的部分,在 Composite 之前,頁面是處于一種分層狀態,借助 GPU,瀏覽器僅僅在每一幀對生成了自己獨立 GraphicsLayer 元素層進行重繪,如此,大大的降低了整個頁面重排重繪的開銷,提升了頁面渲染的效率。
因此,CSS 動畫(Web 動畫同理)優化的第一條準則就是讓需要動畫的元素生成了自己獨立的 GraphicsLayer,強制開始 GPU 加速,而我們需要知道是,GPU 加速的本質是利用讓元素生成了自己獨立的 GraphicsLayer,降低了頁面在渲染過程中重繪重排的開銷。
當然,生成自己的獨立的 GraphicsLayer,不僅僅只有 transform3d api,還有非常多的方式。對于上述一大段非常繞的內容,你可以再看看這幾篇文章:
除了上述準則之外,還有一些提升 CSS 動畫性能的建議:
不同樣式在消耗性能方面是不同的,改變一些屬性的開銷比改變其他屬性要多,因此更可能使動畫卡頓。
例如,與改變元素的文本顏色相比,改變元素的 box-shadow 將需要開銷大很多的繪圖操作。box-shadow 屬性,從渲染角度來講十分耗性能,原因就是與其他樣式相比,它們的繪制代碼執行時間過長。這就是說,如果一個耗性能嚴重的樣式經常需要重繪,那么你就會遇到性能問題。
類似的還有 CSS 3D 變換、mix-blend-mode、filter,這些樣式相比其他一些簡單的操作,會更加的消耗性能。我們應該盡可能的在動畫過程中降低其使用的頻率或者尋找替代方案。
當然,沒有不變的事情,在今天性能很差的樣式,可能明天就被優化,并且瀏覽器之間也存在差異。
因此關鍵在于,我們需要針對每一起卡頓的例子,借助開發工具來分辨出性能瓶頸所在,然后設法減少瀏覽器的工作量。學會 Chrome 開發者工具的 Performance 面板及其他渲染相關的面板非常重要,當然這不是本文的重點。大家可以自行探索。
will-change 為 Web 開發者提供了一種告知瀏覽器該元素會有哪些變化的方法,這樣瀏覽器可以在元素屬性真正發生變化之前提前做好對應的優化準備工作。 這種優化可以將一部分復雜的計算工作提前準備好,使頁面的反應更為快速靈敏。
值得注意的是,用好這個屬性并不是很容易:
有人說 will-change 是良藥,也有人說是毒藥,在具體使用的時候,可以多測試一下。
好了,本文從多個方面,由淺入深地描述了 CSS 動畫我認為的一些比較重要、值得一講、需要注意的點。當然很多地方點到即止,或者限于篇幅沒有完全展開,很多細節還需要讀者進一步閱讀規范或者自行嘗試驗證,實踐出真知,紙上得來終覺淺。
OK,本文到此結束,希望本文對你有所幫助 :)
*請認真填寫需求信息,我們會在24小時內與您取得聯系。