Houdini - 揭開 CSS 的神秘面紗

你是否曾想過 CSS 的工作量?變更單一屬性後,整個網站的版面配置就會突然變更。這有點像魔法。到目前為止,我們 (網頁開發人員社群) 只能見證和觀察這項奇蹟。如果我們想自行開發魔法,該怎麼做?如果我們想成為魔術師呢?

進入 Houdini!

Houdini 特別工作小組由 Mozilla、Apple、Opera、Microsoft、HP、Intel 和 Google 的工程師組成,他們共同合作,向網路開發人員公開 CSS 引擎的特定部分。工作小組正在處理草稿集合,目標是讓草稿獲得 W3C 的認可,成為實際的網路標準。他們設定了幾個高層目標,並將這些目標轉換為規格草稿,進而產生一組支援的低階規格草稿。

當有人提到「Houdini」時,通常指的就是這些草稿集合。在撰寫本文時,草稿清單不完整,部分草稿只是預留位置。

規格

Worklet (spec)

Worklet 本身並沒有太大用處。這些概念是為了讓後續草稿能夠實現。如果您在閱讀「worklet」時想到 Web Workers,那麼您並沒有錯。它們在概念上有很多重疊之處。既然已經有 worker,為什麼還要推出新功能?

Houdini 的目標是公開新的 API,讓網頁開發人員將自己的程式碼連結至 CSS 引擎和周圍系統。假設這些程式碼片段必須在每個畫面執行,這可能並非不切實際的假設。其中有些是必須的。引述 Web Worker 規格

也就是說,Houdini 計畫執行的工作無法由網頁工作站執行。因此,我們發明瞭工作元件。Worklet 會使用 ES2015 類別定義一組方法,這些方法的簽名會由 Worklet 的類型預先定義。這些 cookie 體積輕巧,且壽命短暫。

CSS Paint API (規格)

根據預設,Chrome 65 會啟用 Paint API。請參閱詳細介紹

合成器小程式

這裡所述的 API 已淘汰。我們已重新設計合成器工作區,並將其命名為「動畫工作區」。進一步瞭解 API 的目前版本

雖然合成器工作區規格已移至 WICG,並將進行疊代,但這項規格是我最感興奮的規格。不過,CSS 引擎會將部分作業外包給電腦的顯示卡,但這取決於顯示卡和裝置的一般情況。

瀏覽器通常會使用 DOM 樹狀結構,並根據特定條件決定為某些分支和子樹狀結構建立專屬的圖層。這些子樹會將自己繪製到畫布上 (未來可能會使用繪圖工作區)。最後,所有已繪製的個別圖層會堆疊並彼此堆疊,並遵循 z 索引、3D 轉換等,產生可在螢幕上顯示的最終圖片。這個程序稱為「合成」,由合成器執行。

合成程序的優點是,當頁面稍微捲動時,您不必讓所有元素重新繪製。您可以改為重複使用先前影格中的圖層,並以更新後的捲動位置重新執行合成器。這樣一來,處理速度就會加快。這有助於達到 60fps。

合成器 Worklet。

如其名稱所示,合成器工作區可讓您鉤掛至合成器,並影響已繪製的元素層位於其他層的頂端的方式。

如要進一步指定,您可以告訴瀏覽器,您想將特定 DOM 節點連結至合成程序,並要求存取特定屬性,例如捲動位置、transformopacity。這會強制將此元素置於其專屬圖層,並在每個影格呼叫程式碼。您可以透過操控圖層轉換並變更其屬性 (例如 opacity) 來移動圖層,讓您以 60 fps 的速度執行精緻的操作。

以下是使用合成器 worklet 的視差捲動效果完整實作方式。

// main.js
window.compositorWorklet.import('worklet.js')
    .then(function() {
    var animator = new CompositorAnimator('parallax');
    animator.postMessage([
        new CompositorProxy($('.scroller'), ['scrollTop']),
        new CompositorProxy($('.parallax'), ['transform']),
    ]);
    });

// worklet.js
registerCompositorAnimator('parallax', class {
    tick(timestamp) {
    var t = self.parallax.transform;
    t.m42 = -0.1 * self.scroller.scrollTop;
    self.parallax.transform = t;
    }

    onmessage(e) {
    self.scroller = e.data[0];
    self.parallax = e.data[1];
    };
});

Robert Flack 已為合成器工作區編寫polyfill,歡迎試試看,但請注意,這會對效能造成更大的影響。

版面配置工作表 (spec)

我們已提出第一個實際規格草稿。實施這項功能還需要一段時間。

同樣地,這項功能的規格幾乎是空白,但概念很有趣:您可以自行編寫版面配置!版面配置工作區塊應可讓您執行 display: layout('myLayout') 並執行 JavaScript,以便在節點的方塊中排列節點的子項。

當然,執行 CSS flex-box 版面的完整 JavaScript 實作項目會比執行等效的原生實作項目慢,但您不難想像,在某些情況下,省略某些步驟就能提升效能。想像一個網站,只包含方塊,例如 Windows 10 或磚塊式版面配置。不使用絕對和固定定位,也不使用 z-index,元素也不會重疊,也不會有任何邊框或溢位。能夠略過重新版面配置的所有檢查,可能會帶來效能提升。

registerLayout('random-layout', class {
    static get inputProperties() {
        return [];
    }
    static get childrenInputProperties() {
        return [];
    }
    layout(children, constraintSpace, styleMap) {
        const width = constraintSpace.width;
        const height = constraintSpace.height;
        for (let child of children) {
            const x = Math.random()*width;
            const y = Math.random()*height;
            const constraintSubSpace = new ConstraintSpace();
            constraintSubSpace.width = width-x;
            constraintSubSpace.height = height-y;
            const childFragment = child.doLayout(constraintSubSpace);
            childFragment.x = x;
            childFragment.y = y;
        }

        return {
            minContent: 0,
            maxContent: 0,
            width: width,
            height: height,
            fragments: [],
            unPositionedChildren: [],
            breakToken: null
        };
    }
});

類型 CSSOM (spec)

類型 CSSOM (CSS 物件模型或 CSS 樣式表物件模型) 解決了我們可能都遇到過的問題,而我們只學會了如何忍受這個問題。我將以一行 JavaScript 程式碼說明:

    $('#someDiv').style.height = getRandomInt() + 'px';

我們會執行數學運算,將數字轉換為字串,再附加單位,讓瀏覽器剖析該字串,並將其轉換回 CSS 引擎的數字。如果您使用 JavaScript 操控轉換作業,情況會更糟。別再這樣了!CSS 即將開始輸入內容。

這個草稿是較成熟的草稿之一,且polyfill 也已在開發中。(免責聲明:使用 polyfill 顯然會增加更多運算開銷。重點是說明 API 有多方便。)

您將處理元素的 StylePropertyMap,而非字串,其中每個 CSS 屬性都有其專屬的鍵和對應的值類型。width 等屬性具有 LengthValue 值類型。LengthValue 是所有 CSS 單位的字典,例如 emrempxpercent 等。設定 height: calc(5px + 5%) 會產生 LengthValue{px: 5, percent: 5}box-sizing 等部分屬性只接受特定關鍵字,因此具有 KeywordValue 值類型。接著,系統可以在執行階段檢查這些屬性的有效性。

<div style="width: 200px;" id="div1"></div>
<div style="width: 300px;" id="div2"></div>
<div id="div3"></div>
<div style="margin-left: calc(5em + 50%);" id="div4"></div>
var w1 = $('#div1').styleMap.get('width');
var w2 = $('#div2').styleMap.get('width');
$('#div3').styleMap.set('background-size',
    [new SimpleLength(200, 'px'), w1.add(w2)])
$('#div4')).styleMap.get('margin-left')
    // => {em: 5, percent: 50}

屬性和值

(spec)

您是否知道 CSS 自訂屬性 (或其非官方別名「CSS 變數」)? 這就是類型,到目前為止,變數只能包含字串值,並使用簡單的搜尋及取代方法。這份草稿不僅可讓您為變數指定類型,還可定義預設值,並使用 JavaScript API 影響繼承行為。從技術層面來說,這也能讓自訂屬性透過標準 CSS 轉場和動畫產生動畫效果,而我們也正在考慮這項功能。

["--scale-x", "--scale-y"].forEach(function(name) {
document.registerProperty({
    name: name,
    syntax: "<number>",
    inherits: false,
    initialValue: "1"
    });
});

字型指標

字型指標顧名思義,當我以字型 Y 和大小 Z 轉譯字串 X 時,定界框 (或定界框) 為何?如果我使用Ruby 註解,會發生什麼情況?許多人提出這項要求,Houdini 終於實現這個願望。

等一下,好處可不只這些!

Houdini 的草稿清單中還有更多規格,但這些規格的未來發展相當不明確,也只不過是想法占位符。例如自訂溢位行為、CSS 語法擴充 API、原生捲動行為擴充功能,以及其他類似的擴充功能,這些功能可在網路平台上提供先前無法實現的功能。

示範

我已將示範程式碼 (使用 polyfill 的即時示範) 開放原始碼。