早期在Web頁(yè)面或Web應(yīng)用中實(shí)現(xiàn) Web動(dòng)畫(huà) 通常是使用JavaScript來(lái)完成。愛(ài)掏網(wǎng) - it200.com使用JavaScript創(chuàng)建動(dòng)畫(huà)非常靈活,但不能輕易地讓瀏覽器通過(guò)硬件加速來(lái)優(yōu)化動(dòng)畫(huà),也不能將其連接到 布局 和 渲染 管道中。愛(ài)掏網(wǎng) - it200.com值得慶幸的是,自2007年Webkit團(tuán)隊(duì)引入的 CSS Animation 和 CSS Transition 克服了早期JavaScript動(dòng)畫(huà)實(shí)現(xiàn)的挑戰(zhàn)。愛(ài)掏網(wǎng) - it200.com但是,CSS Animation和Transition也有很多的限制,特別是在 動(dòng)態(tài)創(chuàng)建動(dòng)畫(huà)、控制動(dòng)畫(huà)的回放和監(jiān)視動(dòng)畫(huà)生命周期方面等。愛(ài)掏網(wǎng) - it200.com不過(guò),Web Animation API的出現(xiàn),讓開(kāi)發(fā)者(特別是Web動(dòng)畫(huà)方面的開(kāi)發(fā)者)看到了曙光。愛(ài)掏網(wǎng) - it200.com因?yàn)椋琖eb Animation API引入了一種新的解決方案,它提供了CSS Animation和Transition的優(yōu)化能力,同時(shí)還提供了早期基于JavaScript制作動(dòng)畫(huà)的API的靈活性。愛(ài)掏網(wǎng) - it200.comWeb Animation API通過(guò) 計(jì)時(shí)模型(Timing Mode)和動(dòng)畫(huà)模型(Animation Model)提供對(duì)Web動(dòng)畫(huà)開(kāi)發(fā)和控制。愛(ài)掏網(wǎng) - it200.com
隨著iOS 13.4、iPadOS 13.4和Safari 13.1在macOS Catalina 10.15.4中的發(fā)布,Web Animation API得到了所有主流瀏覽器的支持,也就是說(shuō),我們可以在Web動(dòng)畫(huà)的開(kāi)發(fā)中大膽的使用該技術(shù)了。愛(ài)掏網(wǎng) - it200.com
只不過(guò)現(xiàn)在前端開(kāi)發(fā)都依賴于主流的JavaScript框架進(jìn)行開(kāi)發(fā),比如React、Vue等。愛(ài)掏網(wǎng) - it200.com如果你在React或Vue中開(kāi)發(fā)Web動(dòng)畫(huà)的話,你會(huì)發(fā)現(xiàn)處理動(dòng)畫(huà)的方式也會(huì)有所不同。愛(ài)掏網(wǎng) - it200.com比如說(shuō),在Vue中有內(nèi)置的
和
組件,允許使用CSS和JavaScript鉤子處理動(dòng)畫(huà);如果使用React,那么對(duì)于ReactCSSTransitionGroup
一定不會(huì)感到陌生,而且在React中還有很多優(yōu)秀的庫(kù)用來(lái)實(shí)現(xiàn)Web動(dòng)畫(huà),比如 React-Motion 和 React-Gsap-Enhancer。愛(ài)掏網(wǎng) - it200.com那么在這篇文章中,將和大家一起探討一下在React中如何使用React的鉤子函數(shù)和Web Animation API結(jié)合起來(lái)創(chuàng)建一個(gè)高性能的動(dòng)效。愛(ài)掏網(wǎng) - it200.com
因?yàn)槲覀兒竺娴膬?nèi)容會(huì)涉及到React的Hooks相關(guān)的知識(shí),如果你從未接觸過(guò)的話,建議你花點(diǎn)時(shí)間閱讀:
- React Hooks官方文檔
- The Guide to Learning React Hooks
- What are React Hooks?
Webkit團(tuán)隊(duì)早在2007年就提出了CSS Animation 和 Transition的原始提案,經(jīng)過(guò)多年發(fā)展,這些規(guī)范已經(jīng)成熟并成為W3C標(biāo)準(zhǔn)和Web平臺(tái)不可或缺的一部分。愛(ài)掏網(wǎng) - it200.com
有了這些技術(shù),在Web開(kāi)發(fā)中集成動(dòng)畫(huà)變得很簡(jiǎn)單,開(kāi)發(fā)人員不再需要編寫(xiě)JavaScript,同時(shí)允許瀏覽器在渲染動(dòng)畫(huà)時(shí)啟動(dòng)硬件加速(3D加速),并在布局和渲染管道中集成動(dòng)畫(huà),從而提供更好的性能。愛(ài)掏網(wǎng) - it200.com
雖然CSS Animation和Transition都能實(shí)現(xiàn)Web動(dòng)效,但他們有著明顯的區(qū)別:
簡(jiǎn)單地說(shuō):
- CSS的
transition
只有兩個(gè)狀態(tài):開(kāi)始狀態(tài) 和 結(jié)束狀態(tài);但animation
可能是多個(gè)狀態(tài),有幀的概念 - CSS的
transition
需要借助別的方式來(lái)觸發(fā),比如CSS的狀態(tài)選擇器(如:hover
)或 借助JavaScript來(lái)觸發(fā);animation
可以自動(dòng)觸發(fā)
用一個(gè)真正的示例來(lái)展示兩者的區(qū)別:
正如前面所言,可以使用animation
和transition
制作Web動(dòng)畫(huà),不過(guò)使用animation
制作Web動(dòng)畫(huà)的場(chǎng)景更多。愛(ài)掏網(wǎng) - it200.com也正因?yàn)槿绱耍瑯I(yè)內(nèi)有一個(gè)使用animation
制作Web動(dòng)畫(huà)的庫(kù),即@Daniel Eden的 Animate.css。愛(ài)掏網(wǎng) - it200.com
這個(gè)動(dòng)畫(huà)庫(kù)內(nèi)置了很多動(dòng)畫(huà)效果。愛(ài)掏網(wǎng) - it200.com
為什么要特別提到這個(gè)動(dòng)畫(huà)庫(kù)呢?那是因?yàn)槲覀兒竺娴膬?nèi)容將會(huì)用到這個(gè)庫(kù)。愛(ài)掏網(wǎng) - it200.com
作為一名Web開(kāi)發(fā)人員,我很喜歡CSS Animation和Transition的簡(jiǎn)單性和卓越性能,而且我也一直在探討這方面的技術(shù)。愛(ài)掏網(wǎng) - it200.com在一直以來(lái)的學(xué)習(xí)和探討當(dāng)中,CSS Animation 和 Transition的這些優(yōu)勢(shì)使得Web動(dòng)畫(huà)成為Web開(kāi)發(fā)人員的強(qiáng)大工具;但在日常的學(xué)習(xí)和開(kāi)發(fā)過(guò)程中,我也發(fā)現(xiàn)了這些技術(shù)也存在一定的缺陷:動(dòng)態(tài)創(chuàng)建、回放控制和監(jiān)控動(dòng)畫(huà)的生命周期!
不過(guò)值得慶幸的是,Web Animation API(簡(jiǎn)稱 WAAPI)的出現(xiàn)可以解決上述提到的這些缺陷。愛(ài)掏網(wǎng) - it200.com
我們先來(lái)看看WAAPI的基礎(chǔ)操作。愛(ài)掏網(wǎng) - it200.com
使用CSS的animation
創(chuàng)建動(dòng)畫(huà),首先會(huì)先使用@keyframes
創(chuàng)建一個(gè)動(dòng)畫(huà),然后在需要使用這個(gè)動(dòng)畫(huà)的元素(對(duì)象)上通過(guò)animation
屬性來(lái)調(diào)用,比如上面的示例:
@keyframes boxScale {
to {
transform: scale(1.5, 1.5);
}
}
.box {
transform-origin: center;
animation: boxScale 2s linear infinite alternate;
}
.box
元素是先放大,再回到初始大小,再放大,再回到初始大小,一直重復(fù)這樣的過(guò)程:
對(duì)于這樣的一個(gè)效果,如果我們使用WAAPI來(lái)實(shí)現(xiàn)的話,將會(huì)像下面這樣:
const aniElement = document.querySelector(".waapi");
const keyframes = [
{ transform: "scale(1, 1)" },
{ transform: "scale(1.5, 1.5)" }
];
const options = {
duration: 2000,
iterations: Infinity,
easing: "linear",
direction: "alternate"
};
aniElement.animate(keyframes, options);
效果如下:
從效果上來(lái)看,他們是相同的。愛(ài)掏網(wǎng) - it200.com但熟悉CSS Animation的開(kāi)發(fā)者都知道,CSS允許你很容易地將狀態(tài)變化(比如上示中的圓變大變大)動(dòng)畫(huà)化,但如果給定動(dòng)畫(huà)的開(kāi)始值和結(jié)束值事先不知道,那么就會(huì)非常棘手。愛(ài)掏網(wǎng) - it200.com針對(duì)于這種情況,Web開(kāi)發(fā)者會(huì)用CSS Transition來(lái)處理這些情況:
// 設(shè)置transition屬性的初始值
aniElement.style.transitionProperty = 'transform'
aniElement.style.transitionDuration = '2s'
aniElement.style.transform = 'scale(1, 1)'
// 現(xiàn)在,設(shè)置transition屬性的結(jié)束值
aniElement.style.transform = 'scale(1.5, 1.5)'
我們可能通過(guò)事件的操作,即將最終值放到對(duì)應(yīng)的事件中:
play.addEventListener("click", () => {
aniElement.style.transform = "scale(1.5, 1.5)";
});
reset.addEventListener("click", () => {
aniElement.style.transform = "scale(1, 1)";
});
效果如下:
雖然說(shuō),這樣能讓元素動(dòng)起來(lái)。愛(ài)掏網(wǎng) - it200.com但瀏覽器不會(huì)在它認(rèn)為最合適的時(shí)候讓元素動(dòng)起來(lái)。愛(ài)掏網(wǎng) - it200.com比如說(shuō),如果頁(yè)面的另一部分也需要?jiǎng)?chuàng)建一個(gè)類(lèi)似的動(dòng)畫(huà),那么我們將要不斷的重復(fù)這樣的代碼,這將增加了代碼量而且也有可能降低Web性能。愛(ài)掏網(wǎng) - it200.com當(dāng)然,你也可有會(huì)考慮使用CSS Animation來(lái)替代(即,首先@keyframes
創(chuàng)建一個(gè)動(dòng)畫(huà)),并將其插入到或
.css
中,從而無(wú)法封裝單個(gè)元素的真正目標(biāo)樣式更改,并導(dǎo)致昂貴的樣式無(wú)效。愛(ài)掏網(wǎng) - it200.com
不過(guò),我們改用Web Animation API,就能輕易讓瀏覽器引擎高效地運(yùn)行動(dòng)畫(huà),同時(shí)還能更好的控制動(dòng)畫(huà)。愛(ài)掏網(wǎng) - it200.com正如上面的示例所示,我們可以使用Element.animate()
調(diào)用一個(gè)方法來(lái)重寫(xiě)上面的代碼:
element.animate({
transform: [
'scale(1, 1)',
'scale(1.5, 1.5)'
]
}, 2000)
這是一個(gè)很簡(jiǎn)單的示例,但可以說(shuō)Element.animate()
方法是名副其實(shí)的瑞士軍刀,它具有更高級(jí)的特性。愛(ài)掏網(wǎng) - it200.comElement.animate()
方法接受兩個(gè)參數(shù),第一個(gè)參數(shù)指定是類(lèi)似于@keyframes
動(dòng)畫(huà)值,第二個(gè)參數(shù)指定的指定動(dòng)畫(huà)的特性的相關(guān)參數(shù)(類(lèi)似于animation-timing-function
、animation-duration
、animation-fill-mode
等)。愛(ài)掏網(wǎng) - it200.com這樣一來(lái),我們可以添加更多的參數(shù),讓上面的動(dòng)畫(huà)變得更強(qiáng)大:
const aniElement = document.querySelector(".box");
const play = document.getElementById("play");
const reset = document.getElementById("reset");
play.addEventListener("click", () => {
aniElement.animate(
{
transform: ["scale(1, 1)", "scale(1.5, 1.5)"]
},
{
duration: 2000,
fill: "both"
}
);
});
reset.addEventListener("click", () => {
aniElement.animate(
{
transform: ["scale(1.5, 1.5)", "scale(1, 1)"]
},
{
duration: 2000,
fill: "both"
}
);
});
效果如下:
包月會(huì)員查看