Houdini - 揭開 CSS 的神秘面紗

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

進入 Houdini!

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

當其他人談論「Houdini」時,這些草稿的集合通常代表著作。在本文撰寫時,草稿清單不完整,部分草稿只是預留位置。

規格

Worklet (spec)

Worklet 本身並沒有太大用處。這項概念的目的,是為了讓後續草稿能夠順利進行。如果您在閱讀「worklet」時想到 Web Workers,那麼您並沒有錯。它們在概念上有很多重疊之處。那麼在工作人員時 為什麼要推出新的東西呢?

Houdini 的目標是公開新的 API,讓網頁開發人員將自己的程式碼連結至 CSS 引擎和周圍系統。假設部分程式碼片段必須執行 Every.Single. frame 是非常實用的特性,其中有些是必須的。引述 Web Worker 規格

也就是說,Houdini 的計畫無法使用網頁工作站。工作程式使用 ES2015 類別來定義方法的集合,也就是由工作程式類型預先定義的方法簽名。這些 cookie 體積輕巧,且壽命短暫。

CSS Paint API (規格)

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

合成器小程式

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

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

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

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

合成器 Worklet。

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

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

以下是使用合成器工作區塊,針對視差捲動功能的完整實作方式。

// 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)

我們已提出第一個實際規格草稿。實作並不容易。

再次強調,這個做法的規格幾乎是空的,但這個概念很有趣,那就是:編寫自己的版面配置!版面配置 Worklet 應可讓您執行 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 物件模型或階層式樣式試算表物件模型) 則解決了我們所有人剛剛遇到的問題,以及所學到的技巧。我將以一行 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"
    });
});

字型指標

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

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

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

示範

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