CSS Houdini:用瀏覽器引擎實(shí)現(xiàn)高級CSS效果
作者 | vivo 互聯(lián)網(wǎng)前端團(tuán)隊(duì)-Wei Xing
Houdini被稱之為Magic of styling and layout on the web,看起來十分神秘,但實(shí)際上,Houdini并非什么神秘組織或者神奇魔法,它是一系列與CSS引擎相關(guān)的瀏覽器API的總稱。
一、Houdini 是什么
在了解之前,先來看一些Houdini能實(shí)現(xiàn)的效果吧:
反向的圓角效果(Border-radius):
動(dòng)態(tài)的球形背景(Backgrond):
彩色邊框(Border):
神奇吧,要實(shí)現(xiàn)這些效果使用常規(guī)的CSS可沒那么容易,但對CSS Houdini來說,卻很easy,這些效果只是冰山一角,CSS Houdini能做的有更多。(這些案例均來自Google Chrome Labs,更多案例可以通過 Houdini Samples 查看)。
看完效果,再來說說Houdini到底是什么。
首先,Houdini 的出現(xiàn)最直接的目的是為了解決瀏覽器對新的CSS特性支持較差以及Cross-Browser的問題。我們知道有很多新的CSS特性雖然很棒,但它們由于不被主流瀏覽器廣泛支持而很少有人去使用。
隨著CSS規(guī)范在不斷地更新迭代,越來越多有益的特性被納入進(jìn)來,但是一個(gè)新的CSS特性從被提出到成為一個(gè)穩(wěn)定的CSS特性,需要經(jīng)過漫長地等待,直到被大部分瀏覽器支持時(shí),才能被開發(fā)者廣泛地使用。
而 Houdini 的出現(xiàn)正是洞察和解決了這一痛點(diǎn),它將一系列CSS引擎API開放出來,讓開發(fā)者可以通過JavasScript創(chuàng)造或者擴(kuò)展現(xiàn)有的CSS特性,甚至創(chuàng)造自己的CSS渲染規(guī)則,給開發(fā)者更高的CSS開發(fā)自由度,實(shí)現(xiàn)更多復(fù)雜的效果。
二、JS Polyfill vs Houdini
有人會問,實(shí)際上很多新的CSS特性在被瀏覽器支持之前,也有可替代的JavaScript Polyfill可以使用,為什么我們?nèi)匀恍枰狧oudini呢?這些Polyfill不是同樣可以解決我們的問題嗎?
要回答這個(gè)問題也很簡單,JavaScript Polyfill相對于Houdini有三個(gè)明顯的缺陷:
不一定能實(shí)現(xiàn)或?qū)崿F(xiàn)困難。CSSOM開放給JavaScript的API很少,這意味著開發(fā)者能做的很有限,只能簡單地操縱DOM并對樣式做動(dòng)態(tài)計(jì)算和調(diào)整,光是去實(shí)現(xiàn)一些復(fù)雜的CSS新特性的Polyfill就已經(jīng)很難了,對于更深層次的Layout、Paint、Composite等渲染規(guī)則更是無能為力。所以當(dāng)一個(gè)新的CSS特性被推出時(shí),通過JavaScript Polyfill不一定能夠完整地實(shí)現(xiàn)它。
實(shí)現(xiàn)效果差或有使用限制。JavaScript Polyfill是通過JavaScript來模擬CSS特性的,而不是直接通過CSS引擎進(jìn)行渲染,通常它們都會有一定的限制和缺陷。例如,大家熟知的css-scroll-snap-polyfill就是針對新的CSS特性Scroll Snap產(chǎn)生的Polyfill,但它在使用時(shí)就存在使用限制或者原生CSS表現(xiàn)不一致的問題。
性能較差。JavaScript Polyfill可能造成一定程度的性能損耗。JavaScript Polyfill的執(zhí)行時(shí)機(jī)是在DOM和CSSOM都構(gòu)建完成并且完成渲染后,通常JavaScript Polyfill是通過給DOM元素設(shè)置內(nèi)聯(lián)樣式來模擬CSS特性,這會導(dǎo)致頁面的重新渲染或回流。尤其是當(dāng)這些Polyfill和滾動(dòng)事件綁定時(shí),會造成更加明顯的性能損耗。
Houdini的誕生讓CSS新特性不再依賴于瀏覽器,開發(fā)者通過直接操作CSS引擎,具有更高的自由度和性能優(yōu)勢,并且它的瀏覽器支持度在不斷提升,越來越多的API被支持,未來Houdini必然會加速走進(jìn)web開發(fā)者的世界,所以現(xiàn)在對它做一些了解也是必要的。
在本文,我們會介紹Houdini的APIs以及它們的使用方法,看看這些API當(dāng)前的支持情況,并給出一些在生產(chǎn)環(huán)境中使用它們的建議。
Houdini的名稱與一位著名美國逃脫魔術(shù)師Harry Houdini的名稱一樣,也許正是取逃脫之意,讓CSS新特性逃離瀏覽器的掌控。
三、Houdini APIs
上文提到CSS Houdini提供了很多CSS引擎相關(guān)的API,根據(jù)Houdini提供的規(guī)范說明文件,API共分為兩種類型:high-level APIs 和 low-level APIs 。
high-level APIs:顧名思義是高層次的API,這些API與瀏覽器的渲染流程相關(guān)。
Paint API
Animation API
- 提供了一組與合成(composite)渲染相關(guān)的API,我們可以通過它調(diào)整繪制層級和自定義動(dòng)畫。
Layout API
- 提供了一組與布局(Layout)過程相關(guān)的API,我們可以通過它自定義的布局規(guī)則,類似于實(shí)現(xiàn)諸如flex、grid等布局,自定義元素或子元素的對齊(alignment)、位置(position)等布局規(guī)則。
low-level APIs:低層次的API,這些API是high-level APIs的實(shí)現(xiàn)基礎(chǔ)。
- Typed Object Model API
- CSS Properties & Values API
- Worklets
- Font Metrics API
- CSS Parser API
這些APIs的支持情況在不斷更新中,可以看到當(dāng)前最新的一次更新時(shí)間是在2021年5月份,還是比較活躍的。(注:圖片來源于Is Houdini ready yet? )
對比下圖2018年底的情況,Houdini目前得到了更廣泛的支持,我們也期待圖里更多綠色的板塊被逐漸點(diǎn)亮。
大家可以訪問 Is Houdini ready yet? 看到Houdini的最新支持情況。
下文中,我們會著重介紹Typed Object Model API、CSS Properties & Values API、Worklets和Paint API、Animation API,因?yàn)樗鼈兡壳熬哂斜绕渌鸄PI更好的支持度,且它們的特性已經(jīng)趨于穩(wěn)定,在未來不會有很大的變更,大家也能在了解它們之后直接將它們使用在項(xiàng)目中。
四、 Typed Object Model API
在Houdini出現(xiàn)以前,我們通過JavaScript操作CSS Style的方式很簡單,先看看一段大家熟悉的代碼。
// Before Houdini
const size = 30
target.style.fontSize = size + 'px' // "20px"
const imgUrl = 'https://www.exampe.com/sample.png'
target.style.background = 'url(' + imgUrl + ')' // "url(https://www.exampe.com/sample.png)"
target.style.cssText = 'font-size:' + size + 'px; background: url('+ imgUrl +')'
// "font-size:30px; background: url(https://www.exampe.com/sample.png)"
我們可以看到CSS樣式在被訪問時(shí)被解析為字符串返回,設(shè)置CSS樣式時(shí)也必須以字符串的形式傳入。開發(fā)者需要手動(dòng)拼接數(shù)值、單位、格式等信息,這種方式非常原始和落后,很多開發(fā)者為了節(jié)省性能損耗,會選擇將一長串的CSS Style字符串傳入cssText,可讀性很差,而且很容易產(chǎn)生隱蔽的語法錯(cuò)誤。
Typed Object Model與TypeScript的命名類似,都增加了Type這個(gè)前綴,如果你使用過TypeScript就會了解到,TypeScript增強(qiáng)了類型檢查,讓代碼更穩(wěn)定也更易維護(hù),Typed Object Model也是如此。
相比于上面晦澀的傳統(tǒng)方法,Typed Object Model將CSS屬性值包裝為Typed JavaScript Object,讓每個(gè)屬性值都有自己的類型,簡化了CSS屬性的操作,并且?guī)砹诵阅苌系奶嵘?。通過JavaScript對象來描述CSS值比字符串具有更好的可讀性和可維護(hù)性,通常也更快,因?yàn)榭梢灾苯硬僮髦?,然后廉價(jià)地將其轉(zhuǎn)換回底層值,而無需構(gòu)建和解析 CSS 字符串。
在Typed Object Model中CSSStyleValue是所有CSS屬性值的基類,在它之下的子類用于描述各種CSS屬性值,例如:
- CSSUnitValue
- CSSImageValue
- CSSKeywordValue
- CSSMathValue
- CSSNumericValue
- CSSPositionValue
- CSSTransformValue
- CSSUnparsedValue
- 其它
通過它們的命名就可以看出這些不同的子類分別用于表示哪種類型的CSS屬性值,以CSSUnitValue為例,它可以用于表示帶有單位的CSS屬性值,例如font-size、width、height,它的結(jié)構(gòu)很簡單,由value和unit組成。
{
value: 30,
unit: "px"
}
可以看到,通過對象來描述CSS屬性值確實(shí)比傳統(tǒng)的字符串更易讀了。
要訪問和操作CSSStyleValue還需要借助兩個(gè)工具,分別是attributeStyleMap和computedStyleMap(),前者用于處理內(nèi)聯(lián)樣式,可以進(jìn)行讀寫操作,后者用于處理非內(nèi)聯(lián)樣式(stylesheet),只有讀操作。
// 獲取stylesheet樣式
target.computedStyleMap().get("font-size"); // { value: 30, unit: "px"}
// 設(shè)置內(nèi)聯(lián)樣式
target.attributeStyleMap.set("font-size", CSS.em(5));
// stylesheet樣式仍然返回20px
target.computedStyleMap().get("font-size"); // { value: 30, unit: "px"}
// 內(nèi)聯(lián)樣式已經(jīng)被改變
target.attributeStyleMap.get("font-size"); // { value: 5, unit: "em"}
當(dāng)然attributeStyleMap和computedStyleMap()還有更多可用的方法,例如clear、has、delete、append等,這些方法都為開發(fā)者提供了更便捷和清晰的CSS操作方式。
五、CSS Properties & Values API
根據(jù)MDN的定義,CSS Properties & Values API也是Houdini開放的一部分API,它的作用是讓開發(fā)者顯式地聲明自定義屬性(css custom properties),并且定義這些屬性的類型、默認(rèn)值、初始值和繼承方法。
--my-color: red;
--my-margin-left: 100px;
--my-box-shadow: 3px 6px rgb(20, 32, 54);
在被聲明之后,這些自定義屬性可以通過var()來引用,例如:
// 在:root下可聲明全局自定義屬性
:root {
--my-color: red;
}
#container {
background-color: var(--my-color)
}
了解了自定義屬性的基本概念和使用方式后,我們來考慮一個(gè)問題,我們能否通過自定義屬性來幫助我們完成一些過渡效果呢?
例如,我們希望為一個(gè)div容器設(shè)置背景色的transition動(dòng)畫,我們知道CSS是無法直接對background-color做transition過渡動(dòng)畫的,那我們考慮將transition設(shè)置在我們自定義的屬性--my-color上,通過自定義屬性的漸變來間接完成背景的漸變效果,是否能做到呢?根據(jù)剛才的自定義屬性簡介,也許你會嘗試這么做:
// DOM
<div id="container">container</div>
// Style
:root {
--my-color: red;
}
#container {
transition: --my-color 1s;
background-color: var(--my-color)
}
#container:hover {
--my-color: blue;
}
這看起來是個(gè)符合邏輯的寫法,但實(shí)際上由于瀏覽器不知道該如何去解析--my-color這個(gè)變量(因?yàn)樗]有明確的類型,只是被當(dāng)做字符串處理),所以也無法對它采用transition的效果,因此我們并不能得到一個(gè)漸變的背景色動(dòng)畫。
但是,通過CSS Properties & Values API提供的CSS.registerProperty()方法就可以做到,就像這樣:
// DOM
<div id="container">container</div>
// JavaScript
CSS.registerProperty({
name: '--my-color',
syntax: '<color>',
inherits: false,
initialValue: '#c0ffee',
});
// Style
#container {
transition: --my-color 1s;
background-color: var(--my-color)
}
#container:hover {
--my-color: blue;
}
與上面的不同之處在于,CSS.registerProperty()顯式定義了--my-color的類型syntax,這個(gè)syntax告訴瀏覽器把--my-color當(dāng)做color去解析,因此當(dāng)我們設(shè)置transition: --my-color 1s時(shí),瀏覽器由于提前被告知了該屬性的類型和解析方式,因此能夠正確地為其添加過渡效果,得到的效果如下圖所示。
CSS.registerProperty()接受一個(gè)參數(shù)對象,參數(shù)中包含下面幾個(gè)選項(xiàng):
- name: 變量的名字,不允許重復(fù)聲明或者覆蓋相同名稱的變量,否則瀏覽器會給出相應(yīng)的報(bào)錯(cuò)。
- syntax: 告訴瀏覽器如何解析這個(gè)變量。它的可選項(xiàng)包含了一些預(yù)定義的值等。
- inherits: 告訴瀏覽器這個(gè)變量是否繼承它的父元素。
- initialValue: 設(shè)置該變量的初始值,并且將該初始值作為fallback。
在未來,開發(fā)者不僅可以在JavaScript中顯式聲明CSS變量,也可以直接在CSS中直接聲明:
@property --my-color{
syntax: '<color>',
inherits: false,
initialValue: '#c0ffee',
}
六、Font Metrics API
目前 Font Metrics API 還處于早期的草案階段,它的規(guī)范在未來可能會有較大的變更。在當(dāng)前的specification文件中,說明了 Font Metrics API 將會提供一系列API,允許開發(fā)者干預(yù)文字的渲染過程,創(chuàng)建文字或者動(dòng)態(tài)修改文字的渲染效果等。期待它能在未來被采納和支持,為開發(fā)者提供更多的可能。
七、CSS Parser API
目前 Font Metrics API 也處于早期的草案階段,當(dāng)前的specification文件中說明了它將會提供更多CSS解析器相關(guān)的API,用于解析任意形式的CSS描述。
八、Worklets
Worklets是輕量級的 Web Workers,它提供了讓開發(fā)者接觸底層渲染機(jī)制的API,Worklets的工作線程獨(dú)立于主線程之外,適用于做一些高性能的圖形渲染工作。并且它只能被使用在HTTPS協(xié)議中(生產(chǎn)環(huán)境)或通過localhost來啟用(開發(fā)調(diào)試)。
Worklets不像Web Workers,我們不能將任何計(jì)算操作都放在Worklets中執(zhí)行,Worklets開放了特定的屬性和方法,讓我們能處理圖形渲染相關(guān)的操作。我們能使用的Worklet類型暫時(shí)有如下幾種:
- PaintWorklet - Paint API
- LayoutWorklet - Animation API
- AnimationWorklet - Layout API
- AudioWorklet - Audio API(處于草案階段,暫不介紹)
Worklets提供了唯一的方法Worklet.addModule(),這個(gè)方法用于向Worklet添加執(zhí)行模塊,具體的使用方法,我們在后續(xù)的Paint API、Layout API、Animation API中介紹。
九、Paint API
Paint API允許開發(fā)者通過Canvas 2d的方法來繪制元素的背景、邊框、內(nèi)容等圖形,這在原始的CSS規(guī)則中是無法做到的。
Paint API需要結(jié)合上述提到的PaintWorklet一起使用,簡單來說就是開發(fā)者構(gòu)建一個(gè)PaintWorklet,再將它傳入Paint API就可以繪制相應(yīng)的Canvas圖形。如果你熟悉Canvas,那Paint API對你來說也不會陌生。
使用Paint API的過程簡述如下:
- 使用registerPaint()方法創(chuàng)建一個(gè)PaintWorklet。
- 將它添加到Worklet模塊中,CSS.paintWorklet.addModule()。
- 在CSS中通過paint()方法使用它。
其中registerPaint()方法用于創(chuàng)建一個(gè)PaintWorklet,在這個(gè)方法中開發(fā)者可以利用Canvas 2d自定義圖形繪制。
可以通過Google Chrome Labs給出的一個(gè)paint API案例checkboardWorklet來直觀看看它的具體使用方法,案例中利用Paint API為textarea繪制彩色的網(wǎng)格背景,它的代碼組成很簡單:
/* checkboardWorklet.js */
class CheckerboardPainter {
paint(ctx, geom, properties) {
const colors = ['red', 'green', 'blue'];
const size = 32;
for(let y = 0; y < geom.height/size; y++) {
for(let x = 0; x < geom.width/size; x++) {
const color = colors[(x + y) % colors.length];
ctx.beginPath();
ctx.fillStyle = color;
ctx.rect(x * size, y * size, size, size);
ctx.fill();
}
}
}
}
// 注冊checkerboard
registerPaint('checkerboard', CheckerboardPainter);
/* index.html */
<script>
CSS.paintWorklet.addModule('path/to/checkboardWorklet.js') // 添加checkboardWorklet到paintWorklet
</script>
/* index.html */
<!doctype html>
<textarea></textarea>
<style>
textarea {
background-image: paint(checkerboard); // 使用paint()方法調(diào)用checkboard繪制背景
}
</style>
通過上述三個(gè)步驟,最終生成的textarea背景效果如圖所示:
感興趣的同學(xué)可以訪問 houdini-samples查看更多官方樣例。
十、Animation API
在過去,當(dāng)我們想要對DOM元素執(zhí)行動(dòng)畫時(shí),通常只有兩個(gè)選擇:CSS Transitions和CSS Animations。這兩者在使用上雖然簡單,也能滿足大部分的動(dòng)畫需求,但是它們有兩個(gè)共同的缺點(diǎn):
- 僅僅依賴時(shí)間來執(zhí)行動(dòng)畫(time-driven):動(dòng)畫的執(zhí)行僅和時(shí)間有關(guān)。
- 無狀態(tài)(stateless):開發(fā)者無法干預(yù)動(dòng)畫的執(zhí)行過程,獲取不到動(dòng)畫執(zhí)行的中間狀態(tài)。
但是在一些場景下,我們想要開發(fā)一個(gè)非時(shí)間驅(qū)動(dòng)的動(dòng)畫或者想要控制動(dòng)畫的執(zhí)行狀態(tài),就很難做到。比如視差滾動(dòng)(Parallax Scrolling),它是根據(jù)滾動(dòng)的情況來執(zhí)行動(dòng)畫的,并且每個(gè)元素根據(jù)滾動(dòng)情況作出不一致的動(dòng)畫效果,下面是個(gè)簡單的視差滾動(dòng)效果示例,在通常情況下要實(shí)現(xiàn)更加復(fù)雜的視差滾動(dòng)效果(例如beckett頁面的效果)是比較困難的。
Animation API卻可以幫助我們輕松做到。
在功能方面,它是CSS Transitions和CSS Animations的擴(kuò)展,它允許用戶干預(yù)動(dòng)畫執(zhí)行的過程,例如結(jié)合用戶的scroll、hover、click事件來控制動(dòng)畫執(zhí)行,像是為動(dòng)畫增加了進(jìn)度條,通過進(jìn)度條控制動(dòng)畫進(jìn)程,從而實(shí)現(xiàn)一些更加復(fù)雜的動(dòng)畫場景。
在性能方面,它依賴于AnimationWorklet,運(yùn)行在單獨(dú)的Worklet線程,因此具有更高的動(dòng)畫幀率和流暢度,這在低端機(jī)型中尤為明顯(當(dāng)然,通常低端機(jī)型中的瀏覽器內(nèi)核還不支持該特性,這里只是說明Animation API對動(dòng)畫的視覺體驗(yàn)優(yōu)化是很友好的)。
Animation API的使用和Paint API一樣,也同樣遵循Worklet的創(chuàng)建和使用流程,分為三個(gè)步驟,簡述如下:
- 使用registerAnimator()方法創(chuàng)建一個(gè)AnimationWorklet。
- 將它添加到Worklet模塊中,CSS.animationWorklet.addModule()。
- 使用new WorkletAnimation(name, KeyframeEffect)創(chuàng)建和執(zhí)行動(dòng)畫。
/* myAnimationWorklet.js */
registerAnimator("myAnimationWorklet", class {
constructor(options) {
/* 構(gòu)造函數(shù),動(dòng)畫示例被創(chuàng)建時(shí)調(diào)用,可用于做一些初始化 */
}
//
animate(currentTime, effect) {
/* 干預(yù)動(dòng)畫的執(zhí)行 */
}
});
/* index.html */
await CSS.animationWorklet.addModule("path/to/myAnimationWorklet.js");;
/* index.html */
/* 傳入myAnimationWorklet,創(chuàng)建WorkletAnimation */
new WorkletAnimation(
'myAnimationWorklet', // 動(dòng)畫名稱
new KeyframeEffect( // 動(dòng)畫timeline(對應(yīng)于步驟一中animate(currentTime, effect)中的effect參數(shù))
document.querySelector('#target'),
[
{
transform: 'translateX(0)'
},
{
transform: 'translateX(200px)'
}
],
{
duration: 2000, // 動(dòng)畫執(zhí)行時(shí)長
iterations: Number.POSITIVE_INFINITY // 動(dòng)畫執(zhí)行次數(shù)
}
),
document.timeline // 控制動(dòng)畫執(zhí)行進(jìn)程的數(shù)值(對應(yīng)于步驟一中animate(currentTime, effect)中的currentTime參數(shù))
).play();
可以看到步驟一的animate(currentTime, effect)方法有兩個(gè)參數(shù),就是它們讓開發(fā)者能夠干預(yù)動(dòng)畫執(zhí)行過程。
currentTime:
用于控制動(dòng)畫執(zhí)行的數(shù)值,對應(yīng)于步驟3例子中傳入的document.timeline參數(shù),通常根據(jù)它的數(shù)值來動(dòng)態(tài)修改另一個(gè)參數(shù)effect,從而影響動(dòng)畫執(zhí)行。例如我們可以傳入document.timeline或者傳入element.scrollTop作為這個(gè)動(dòng)態(tài)數(shù)值,傳入前者表明我們只是想用時(shí)間變化來控制動(dòng)畫的執(zhí)行,傳入后者表明我們想通過滾動(dòng)距離來控制動(dòng)畫執(zhí)行。
document.timeline是每個(gè)頁面被打開后從0開始遞增的時(shí)間數(shù)值,可以簡單理解為頁面被打開的時(shí)長,初始時(shí)document.timeline === 0,隨著時(shí)間不斷遞增。
effect:
對應(yīng)于步驟3中傳入的new KeyframeEffect(),可通過修改它來影響動(dòng)畫執(zhí)行。一個(gè)很常見的做法是,通過修改effect.localTime控制動(dòng)畫的執(zhí)行,effect.localTime的作用相當(dāng)于控制動(dòng)畫播放的進(jìn)度條,修改它的數(shù)值就相當(dāng)于拖動(dòng)動(dòng)畫播放的進(jìn)度。
如果不修改effect.localTime或者設(shè)置effect.localTime = currentTime,那么動(dòng)畫會隨著document.timeline正常勻速執(zhí)行,線性動(dòng)畫。但是如果將effect.localTime設(shè)置為某個(gè)固定值,例如effect.localTime = 1000ms,那么動(dòng)畫將會定格在1000ms時(shí)對應(yīng)的幀,不會繼續(xù)執(zhí)行。
為了更好理解effect.localTime,可以來看看effect.localTime和動(dòng)畫執(zhí)行之間的關(guān)系,假設(shè)我們創(chuàng)建了一個(gè)2000ms時(shí)長的動(dòng)畫,并且動(dòng)畫沒有設(shè)置delay時(shí)間。
通過上面的描述,大家應(yīng)該get到如何做一個(gè)簡單的滾動(dòng)驅(qū)動(dòng)(scroll-driven)的動(dòng)畫了,實(shí)際上有個(gè)專門用于生成滾動(dòng)動(dòng)畫的類:ScrollTimeline,它的用法也很簡單:
/* myWorkletAnimation.js */
new WorkletAnimation(
'myWorkletAnimation',
new KeyframeEffect(
document.querySelector('#target'),
[
{
transform: 'translateX(0)'
},
{
transform: 'translateX(500px)'
}
],
{
duration: 2000,
fill: 'both'
}
),
new ScrollTimeline({
scrollSource: document.querySelector('.scroll-area'), // 監(jiān)聽的滾動(dòng)元素
orientation: "vertical", // 監(jiān)聽的滾動(dòng)方向"horizontal"或"vertical"
timeRange: 2000 // 根據(jù)scroll的高度,傳入0 - timeRage之間的數(shù)值,當(dāng)滾動(dòng)到頂端時(shí),傳入0,當(dāng)滾動(dòng)到底端時(shí),傳入2000
})
).play();
這樣一來,通過簡單的幾行代碼,一個(gè)簡單的滾動(dòng)驅(qū)動(dòng)的動(dòng)畫就做好了,它比任何CSS Animations或CSS Transitions都要順暢。
接下來再看看最后一個(gè)同樣有潛力的API:Layout API 。
十一、Layout API
Layout API允許用戶自定義新的布局規(guī)則,創(chuàng)造類似flex、grid之外的布局。
但創(chuàng)建一個(gè)完備的布局規(guī)則并不簡單,官方的flex、grid布局是充分考慮了各種邊界情況,才能確保使用時(shí)不會出錯(cuò)。同時(shí)Layout API使用起來也比其它API更為復(fù)雜,受限于篇幅,本文僅簡單展示相關(guān)的API和使用方式,具體細(xì)節(jié)可參考官方描述。
Layout API和其它兩個(gè)API相似,使用步驟同樣分為三個(gè)步驟,簡述如下:
- 通過registerLayout()創(chuàng)建一個(gè)LayoutWorklet。
- 將它添加到Worklet模塊中,CSS.layoutWorklet.addModule()。
- 通過display: layout(exampleLayout)使用它。
/* myLayoutWorklet.js */
registerLayout('myLayoutWorklet', class {
static get inputProperties() { return ['--my-prop']; }
static get childrenInputProperties() { return ['--my-child-prop']; }
static get layoutOptions() {
return {
childDisplay: 'normal',
sizing: 'block-like'
};
}
intrinsicSizes(children, edges, styleMap) {
/* ... */
}
layout(children, edges, constraints, styleMap, breakToken) {
/* ... */
}
});
CSS.layoutWorklet.addModule('path/to/myLayoutWorklet.js');
.my-layout {
display: layout(myLayoutWorklet);
}
一個(gè)Google Chrome Labs案例如下所示,通過Layout API實(shí)現(xiàn)了一個(gè)瀑布流布局。
雖然通過Layout API自定義布局較為困難,但是我們依然可以引入別人的優(yōu)秀開源Worklet,幫助自己實(shí)現(xiàn)復(fù)雜的布局。
十二、新特性檢測
鑒于當(dāng)前Houdini APIs的瀏覽器支持度仍然不是很完美,在使用這些API時(shí)需要先做特性檢測,再考慮使用它們。
/* 特性檢測 */
if (CSS.paintWorklet) {
/* ... */
}
if (CSS.animationWorklet) {
/* ... */
}
if (CSS.layoutWorklet) {
/* ... */
}
想要在chrome中調(diào)試,可以在地址欄輸入chrome://flags/#enable-experimental-web-platform-features,并勾選啟用Experimental Web Platform features。
十三、總結(jié)
Houdini APIs讓開發(fā)者有辦法接觸到CSS渲染引擎,通過各種API實(shí)現(xiàn)更高性能和更復(fù)雜的CSS渲染效果。雖然它還沒有完全準(zhǔn)備好,很多API甚至還處于草案階段,但它給我們帶來了更多可能性,并且諸如paint API、Typed OM、Properties & Values API這些新特性也都被廣泛支持了,可以直接用于增強(qiáng)我們的頁面效果。未來Houdini APIs一定會慢慢走進(jìn)開發(fā)者的世界,大家可以期待并做好準(zhǔn)備迎接它。
參考文獻(xiàn):
- W3C Houdini Specification Drafts
- State of Houdini (Chrome Dev Summit 2018)
- Houdini’s Animation Worklet - Google Developers
- Interactive Introduction to CSS Houdini
- CSS Houdini Experiments
- Interactive Introduction to CSS Houdini
- Houdini Samples by Google Chrome Labs