CSS 動畫讓你可以完全不使用 JavaScript 就製作簡單的動畫。
JavaScript 可用於控制 CSS 動畫,並使用少量程式碼讓它們變得更好。
CSS 轉場
CSS 轉場的概念很簡單。我們描述一個屬性,以及它的變更應如何產生動畫。當屬性變更時,瀏覽器會繪製動畫。
也就是說,我們只需要變更屬性,而流暢的轉場會由瀏覽器完成。
例如,以下 CSS 會為 background-color
的變更產生 3 秒鐘的動畫
.animated {
transition-property: background-color;
transition-duration: 3s;
}
現在,如果一個元素有 .animated
類別,background-color
的任何變更都會在 3 秒鐘內產生動畫。
按一下下面的按鈕,為背景產生動畫
<button id="color">Click me</button>
<style>
#color {
transition-property: background-color;
transition-duration: 3s;
}
</style>
<script>
color.onclick = function() {
this.style.backgroundColor = 'red';
};
</script>
描述 CSS 轉場有 4 個屬性
transition-property
transition-duration
transition-timing-function
transition-delay
我們稍後會介紹它們,現在請注意常見的 transition
屬性允許按順序同時宣告它們:property duration timing-function delay
,以及一次動畫化多個屬性。
例如,這個按鈕同時動畫化 color
和 font-size
<button id="growing">Click me</button>
<style>
#growing {
transition: font-size 3s, color 2s;
}
</style>
<script>
growing.onclick = function() {
this.style.fontSize = '36px';
this.style.color = 'red';
};
</script>
現在,讓我們逐一介紹動畫屬性。
transition-property
在 transition-property
中,我們寫入要動畫化的屬性清單,例如:left
、margin-left
、height
、color
。或者我們可以寫入 all
,表示「動畫化所有屬性」。
請注意,有些屬性無法動畫化。然而,大多數一般使用的屬性都可以動畫化。
transition-duration
在 transition-duration
中,我們可以指定動畫應持續多久。時間應採用 CSS 時間格式:秒數 s
或毫秒 ms
。
transition-delay
在 transition-delay
中,我們可以在動畫 之前 指定延遲。例如,如果 transition-delay
為 1s
,而 transition-duration
為 2s
,則動畫在屬性變更後 1 秒開始,總持續時間為 2 秒。
負值也是可能的。然後動畫會立即顯示,但動畫的起點會在給定的值(時間)之後。例如,如果 transition-delay
為 -1s
,而 transition-duration
為 2s
,則動畫從中點開始,總持續時間為 1 秒。
這裡動畫使用 CSS translate
屬性將數字從 0
轉移到 9
stripe.onclick = function() {
stripe.classList.add('animate');
};
#digit {
width: .5em;
overflow: hidden;
font: 32px monospace;
cursor: pointer;
}
#stripe {
display: inline-block
}
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
transition-timing-function: linear;
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
Click below to animate:
<div id="digit"><div id="stripe">0123456789</div></div>
<script src="script.js"></script>
</body>
</html>
transform
屬性以這種方式動畫化
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
}
在上面的範例中,JavaScript 會將類別 .animate
新增到元素,然後動畫開始
stripe.classList.add('animate');
我們也可以從轉場的某個中間點開始,從一個確切的數字開始,例如對應於目前的秒數,使用負的 transition-delay
。
這裡如果您按數字,它會從目前的秒數開始動畫
stripe.onclick = function() {
let sec = new Date().getSeconds() % 10;
stripe.style.transitionDelay = '-' + sec + 's';
stripe.classList.add('animate');
};
#digit {
width: .5em;
overflow: hidden;
font: 32px monospace;
cursor: pointer;
}
#stripe {
display: inline-block
}
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
transition-timing-function: linear;
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
Click below to animate:
<div id="digit"><div id="stripe">0123456789</div></div>
<script src="script.js"></script>
</body>
</html>
JavaScript 使用額外一行來執行此操作
stripe.onclick = function() {
let sec = new Date().getSeconds() % 10;
// for instance, -3s here starts the animation from the 3rd second
stripe.style.transitionDelay = '-' + sec + 's';
stripe.classList.add('animate');
};
transition-timing-function
計時函數描述動畫流程如何沿著其時間軸分佈。它會從慢開始然後變快,還是相反。
它一開始看起來是最複雜的屬性。但如果我們花點時間研究它,它就會變得非常簡單。
該屬性接受兩種類型的值:貝茲曲線或步驟。讓我們從曲線開始,因為它使用得更頻繁。
貝茲曲線
計時函數可以設定為具有 4 個控制點的貝茲曲線,這些控制點滿足條件
- 第一個控制點:
(0,0)
。 - 最後一個控制點:
(1,1)
。 - 對於中間點,
x
的值必須在區間0..1
中,y
可以是任何值。
CSS 中貝茲曲線的語法:cubic-bezier(x2, y2, x3, y3)
。這裡我們只需要指定第 2 個和第 3 個控制點,因為第 1 個固定為 (0,0)
,第 4 個為 (1,1)
。
計時函數描述動畫過程進行的速度。
x
軸是時間:0
– 開始,1
–transition-duration
的結束。y
軸指定過程的完成度:0
– 屬性的起始值,1
– 最終值。
最簡單的變體是動畫以相同的線性速度均勻進行。這可以用曲線 cubic-bezier(0, 0, 1, 1)
指定。
以下是該曲線的樣子
…正如我們所見,它只是一條直線。隨著時間(x
)的流逝,動畫的完成度(y
)穩步從 0
到 1
。
以下範例中的火車以恆定的速度從左到右行駛(按一下它)
.train {
position: relative;
cursor: pointer;
width: 177px;
height: 160px;
left: 0;
transition: left 5s cubic-bezier(0, 0, 1, 1);
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='450px'">
</body>
</html>
CSS transition
基於該曲線
.train {
left: 0;
transition: left 5s cubic-bezier(0, 0, 1, 1);
/* click on a train sets left to 450px, thus triggering the animation */
}
…我們如何顯示火車減速?
我們可以使用另一個貝茲曲線:cubic-bezier(0.0, 0.5, 0.5 ,1.0)
。
圖表
正如我們所見,這個過程開始得很快:曲線高高飆升,然後越來越慢。
以下是計時函數的實際運作(按一下火車)
.train {
position: relative;
cursor: pointer;
width: 177px;
height: 160px;
left: 0px;
transition: left 5s cubic-bezier(0.0, 0.5, 0.5, 1.0);
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='450px'">
</body>
</html>
CSS
.train {
left: 0;
transition: left 5s cubic-bezier(0, .5, .5, 1);
/* click on a train sets left to 450px, thus triggering the animation */
}
有幾個內建曲線:linear
、ease
、ease-in
、ease-out
和 ease-in-out
。
linear
是 cubic-bezier(0, 0, 1, 1)
的簡寫,也就是我們上面描述的直線。
其他名稱是以下 cubic-bezier
的簡寫
ease * |
ease-in |
ease-out |
ease-in-out |
---|---|---|---|
(0.25, 0.1, 0.25, 1.0) |
(0.42, 0, 1.0, 1.0) |
(0, 0, 0.58, 1.0) |
(0.42, 0, 0.58, 1.0) |
*
– 預設情況下,如果沒有計時函數,則使用 ease
。
因此,我們可以使用 ease-out
來減慢火車的速度
.train {
left: 0;
transition: left 5s ease-out;
/* same as transition: left 5s cubic-bezier(0, .5, .5, 1); */
}
但它看起來有點不同。
貝茲曲線會讓動畫超出其範圍。
曲線上的控制點可以有任意 y
座標:甚至是負值或巨大值。然後貝茲曲線也會延伸得很低或很高,使動畫超出其正常範圍。
在以下範例中,動畫程式碼為
.train {
left: 100px;
transition: left 5s cubic-bezier(.5, -1, .5, 2);
/* click on a train sets left to 450px */
}
屬性 left
應從 100px
動畫到 400px
。
但如果你按一下火車,你會看到
- 首先,火車往後移動:
left
變小於100px
。 - 然後往前移動,比
400px
再遠一點。 - 然後再往後移動 – 到
400px
。
.train {
position: relative;
cursor: pointer;
width: 177px;
height: 160px;
left: 100px;
transition: left 5s cubic-bezier(.5, -1, .5, 2);
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='400px'">
</body>
</html>
如果我們查看給定貝茲曲線的圖形,就會很明顯為什麼會發生這種情況
我們將第 2 個點的 y
座標移到零以下,而第 3 個點則移到 1
以上,因此曲線超出「常規」象限。y
超出「標準」範圍 0..1
。
如我們所知,y
測量「動畫過程的完成度」。值 y = 0
對應於起始屬性值,而 y = 1
對應於結束值。因此,值 y<0
將屬性移到起始 left
之外,而 y>1
則移到最終 left
之外。
這肯定是一個「軟」變體。如果我們將 y
值設為 -99
和 99
,那麼火車將跳出範圍更多。
但是我們如何為特定任務建立貝茲曲線?有很多工具。
- 例如,我們可以在網站 https://cubic-bezier.com 上進行。
- 瀏覽器開發人員工具也特別支援 CSS 中的貝茲曲線
- 使用 F12(Mac:Cmd+Opt+I)開啟開發人員工具。
- 選取
Elements
標籤,然後注意右側的Styles
子面板。 - 包含字詞
cubic-bezier
的 CSS 屬性會在這個字詞之前顯示一個圖示。 - 按一下這個圖示以編輯曲線。
步驟
計時函數 steps(步驟數目[, 起始/結束])
允許將轉場拆分為多個步驟。
讓我們在數字範例中看看這個功能。
以下是數字清單,沒有任何動畫,僅作為來源
#digit {
border: 1px solid red;
width: 1.2em;
}
#stripe {
display: inline-block;
font: 32px monospace;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="digit"><div id="stripe">0123456789</div></div>
</body>
</html>
在 HTML 中,數字條紋被封閉在固定長度的 <div id="digits">
中
<div id="digit">
<div id="stripe">0123456789</div>
</div>
#digit
div 有固定寬度和邊框,因此它看起來像一個紅色的視窗。
我們將製作一個計時器:數字將一個接一個地以離散的方式出現。
為實現此目的,我們將使用 overflow: hidden
將 #stripe
隱藏在 #digit
外部,然後逐步將 #stripe
向左移動。
將有 9 個步驟,每個數字一個步驟移動
#stripe.animate {
transform: translate(-90%);
transition: transform 9s steps(9, start);
}
steps(9, start)
的第一個參數是步驟數。變換將分為 9 個部分(每個 10%)。時間間隔也自動分為 9 個部分,因此 transition: 9s
為我們提供了整個動畫 9 秒的時間——每個數字 1 秒。
第二個參數是兩個單詞之一:start
或 end
。
start
表示在動畫開始時,我們需要立即執行第一步。
在行動中
#digit {
width: .5em;
overflow: hidden;
font: 32px monospace;
cursor: pointer;
}
#stripe {
display: inline-block
}
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
transition-timing-function: steps(9, start);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
Click below to animate:
<div id="digit"><div id="stripe">0123456789</div></div>
<script>
digit.onclick = function() {
stripe.classList.add('animate');
}
</script>
</body>
</html>
點擊數字會立即將其更改為 1
(第一步),然後在下一秒開始時更改。
過程如下進行
0s
–-10%
(在第 1 秒開始時立即進行第一次更改)1s
–-20%
- …
8s
–-90%
- (最後一秒顯示最終值)。
這裡,第一次更改是立即進行的,因為 steps
中的 start
。
備用值 end
表示更改不應在開始時應用,而應在每秒結束時應用。
因此,steps(9, end)
的過程將如下所示
0s
–0
(在第一秒內沒有任何變化)1s
–-10%
(在第 1 秒結束時進行第一次更改)2s
–-20%
- …
9s
–-90%
以下是 steps(9, end)
的實際操作(注意第一次數字更改前的暫停)
#digit {
width: .5em;
overflow: hidden;
font: 32px monospace;
cursor: pointer;
}
#stripe {
display: inline-block
}
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
transition-timing-function: steps(9, end);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
Click below to animate:
<div id="digit"><div id="stripe">0123456789</div></div>
<script>
digit.onclick = function() {
stripe.classList.add('animate');
}
</script>
</body>
</html>
steps(...)
還有一些預定義的簡寫
step-start
– 與steps(1, start)
相同。也就是說,動畫立即開始並執行 1 步。因此,它立即開始和結束,就好像沒有動畫一樣。step-end
– 與steps(1, end)
相同:在transition-duration
結束時以單步執行動畫。
這些值很少使用,因為它們表示的不是真正的動畫,而是一個單步更改。我們在這裡提到它們是為了完整性。
事件:“transitionend”
當 CSS 動畫結束時,transitionend
事件觸發。
它廣泛用於在動畫完成後執行操作。我們還可以加入動畫。
例如,以下範例中的船隻在被點擊時開始在那裡和回來航行,每次都向右航行得越來越遠
動畫由函數 go
初始化,該函數在每次變換結束時重新執行,並翻轉方向
boat.onclick = function() {
//...
let times = 1;
function go() {
if (times % 2) {
// sail to the right
boat.classList.remove('back');
boat.style.marginLeft = 100 * times + 200 + 'px';
} else {
// sail to the left
boat.classList.add('back');
boat.style.marginLeft = 100 * times - 200 + 'px';
}
}
go();
boat.addEventListener('transitionend', function() {
times++;
go();
});
};
transitionend
的事件物件有幾個特定屬性
event.propertyName
- 完成動畫屬性。如果我們同時動畫多個屬性,這會很有用。
event.elapsedTime
- 動畫花費的時間(以秒為單位),不包含
transition-delay
。
關鍵影格
我們可以使用 @keyframes
CSS 規則將多個簡單動畫結合在一起。
它指定動畫的「名稱」和規則,包括動畫的內容、時間和位置。然後使用 animation
屬性,我們可以將動畫附加到元素並為其指定其他參數。
以下是一個帶有說明的範例
<div class="progress"></div>
<style>
@keyframes go-left-right { /* give it a name: "go-left-right" */
from { left: 0px; } /* animate from left: 0px */
to { left: calc(100% - 50px); } /* animate to left: 100%-50px */
}
.progress {
animation: go-left-right 3s infinite alternate;
/* apply the animation "go-left-right" to the element
duration 3 seconds
number of times: infinite
alternate direction every time
*/
position: relative;
border: 2px solid green;
width: 50px;
height: 20px;
background: lime;
}
</style>
有許多關於 @keyframes
的文章和 詳細規格。
除非網站上的所有內容都在不斷移動,否則您可能不需要經常使用 @keyframes
。
效能
大多數 CSS 屬性都可以動畫化,因為它們大多數都是數值。例如,width
、color
、font-size
都是數字。當您對它們進行動畫化時,瀏覽器會逐幀逐漸更改這些數字,創造出平滑的效果。
然而,並非所有動畫都能像您希望的那麼平滑,因為不同的 CSS 屬性更改成本不同。
在更技術性的細節中,當有樣式變更時,瀏覽器會執行 3 個步驟來呈現新的外觀
- 配置:重新計算每個元素的幾何形狀和位置,然後
- 繪製:重新計算所有內容在它們的位置上應如何顯示,包括背景、顏色,
- 合成:將最終結果渲染成螢幕上的像素,如果存在,套用 CSS 轉換。
在 CSS 動畫期間,此程序會在每個影格中重複。但是,從不影響幾何形狀或位置的 CSS 屬性,例如 color
,可能會略過配置步驟。如果 color
發生變更,瀏覽器不會計算任何新的幾何形狀,它會轉到繪製 → 合成。而且有少數屬性會直接轉到合成。您可以在 https://csstriggers.com 找到 CSS 屬性及其觸發階段的較長清單。
計算可能需要時間,特別是在具有許多元素和複雜配置的頁面上。而且延遲實際上會在大多數裝置上可見,導致動畫「不穩定」、流暢度較低。
略過配置步驟的屬性動畫速度較快。如果繪製也略過,那就更好了。
transform
屬性是一個很好的選擇,因為
- CSS 轉換影響目標元素框整體(旋轉、翻轉、伸展、移動它)。
- CSS 轉換從不影響鄰近元素。
…因此,瀏覽器在合成階段,在現有的配置和繪製計算「之上」套用 transform
。
換句話說,瀏覽器在配置階段計算配置(大小、位置),在繪製階段使用顏色、背景等繪製它,然後將 transform
套用在需要的元素框上。
transform
屬性的變更(動畫)永遠不會觸發 Layout 和 Paint 步驟。除此之外,瀏覽器會利用圖形加速器(CPU 或顯示卡上的特殊晶片)來執行 CSS 變形,因此效率非常高。
幸運的是,transform
屬性非常強大。透過對元素使用 transform
,你可以旋轉和翻轉它、伸展和縮小它、移動它,以及 更多。因此,我們可以使用 transform: translateX(…)
來取代 left/margin-left
屬性,使用 transform: scale
來增加元素大小,等等。
opacity
屬性也不會觸發 Layout(在 Mozilla Gecko 中也會略過 Paint)。我們可以使用它來顯示/隱藏或淡入/淡出效果。
將 transform
與 opacity
配對通常可以滿足我們的大部分需求,提供流暢、美觀的動畫。
例如,在這裡,按一下 #boat
元素會新增具有 transform: translateX(300px)
和 opacity: 0
的類別,因此會讓它向右移動 300px
並消失
<img src="https://js.cx/clipart/boat.png" id="boat">
<style>
#boat {
cursor: pointer;
transition: transform 2s ease-in-out, opacity 2s ease-in-out;
}
.move {
transform: translateX(300px);
opacity: 0;
}
</style>
<script>
boat.onclick = () => boat.classList.add('move');
</script>
以下是更複雜的範例,包含 @keyframes
<h2 onclick="this.classList.toggle('animated')">click me to start / stop</h2>
<style>
.animated {
animation: hello-goodbye 1.8s infinite;
width: fit-content;
}
@keyframes hello-goodbye {
0% {
transform: translateY(-60px) rotateX(0.7turn);
opacity: 0;
}
50% {
transform: none;
opacity: 1;
}
100% {
transform: translateX(230px) rotateZ(90deg) scale(0.5);
opacity: 0;
}
}
</style>
摘要
CSS 動畫允許平滑(或逐步)對一個或多個 CSS 屬性進行動畫變更。
它們適用於大多數動畫任務。我們也可以使用 JavaScript 進行動畫,下一章將專門探討這個主題。
與 JavaScript 動畫相比,CSS 動畫的限制
- 簡單的事情簡單做。
- 快速且對 CPU 負擔輕。
- JavaScript 動畫很靈活。它們可以實作任何動畫邏輯,例如元素的「爆炸」。
- 不只是屬性變更。我們可以在 JavaScript 中建立新元素作為動畫的一部分。
在本章節的早期範例中,我們會對 font-size
、left
、width
、height
等進行動畫處理。在實際專案中,我們應該使用 transform: scale()
和 transform: translate()
來獲得更好的效能。
大多數動畫都可以使用 CSS 來實作,如本章所述。而且 transitionend
事件允許在動畫之後執行 JavaScript,因此它可以與程式碼良好整合。
但在下一章,我們將執行一些 JavaScript 動畫來涵蓋更複雜的情況。
留言
<code>
標籤;若要插入多行程式碼,請用<pre>
標籤將其包起來;若要插入超過 10 行的程式碼,請使用沙盒 (plnkr、jsbin、codepen…)