CSS Houdini: Properties, Values, and the Paint API

Allan91發表於2021-12-28
本文首發於掘金,未經許可嚴禁轉載

前言

最近研究了下 Houdini,它是 CSS 領域的一個重大變革,它的終極目標是實現 CSS 屬性的完全相容,其中最受關注的特性之一就是它能正確地 polyfill CSS!這麼說比較抽象,它到底是做什麼的呢,如何使用呢,本文來講解一二。

基本概念

Houdini 是一組底層 api,它公開了 CSS 引擎的部分內容,使開發人員能夠通過 hook 到瀏覽器渲染引擎的樣式和佈局過程來擴充套件 CSS。它可以讓開發者直接訪問 CSS 物件模型(CSSOM) ,使開發者能夠編寫瀏覽器可以解析為 CSS 的程式碼,從而建立新的 CSS 特性,而無需等待這些特性在瀏覽器中自行實現。

此外,它還能用於創造一些自定義的,帶有型別檢查和預設值的 CSS 屬性。

Houdini API 介紹

一、CSS property 和值 API

CSS 其實已經有自定義屬性了,這能解鎖太多新玩法。而 CSS Properties and Values API 的出現進一步推動自定義屬性,還允許自定義屬性新增不同的型別,允許屬性型別檢查、設定預設值以及定義屬性是否可以繼承值,大大增加自定義屬效能力。

這個 API 最大賣點是開發者可以在自定義屬性上做動畫,這是僅憑藉現在技術是做不到的。

先來看一個案例,CSS 變數可能很多人都有使用過,它之所以神奇,是因為它是動態的。但它的弱點之一是無法轉換,如果你嘗試為變數設定動畫,它只會從一個屬性翻轉到另一個屬性,之間沒有過渡效果。
demo2.gif

image.png

Codepen Demo

這是因為 CSS 變了沒有任何意義,它沒有任何型別,因此瀏覽器不知道特定變數是否是顏色、百分比、數字等。

CSS Houdini 提供了為變數分配型別的能力。如果你為變數分配了錯誤型別的值,瀏覽器將忽略它並選擇預設值。

CSS.registerProperty({
 name: '--start',
 syntax: '',
 inherits: true,
 initialValue: 'purple'
})

CSS Houdini's Properties 和 Values API 允許我們為 CSS 變數指定型別。使用強型別的 CSS 變數,它最終可以被轉換。最流行的用例之一是動畫漸變。
demo3.gif

image.png

Codepen Demo

使用方式有2種

1、CSS 中定義 CSS 屬性 @property

@property --my-color {
  syntax: '<color>';
  inherits: false;
  initial-value: #c0ffee;
}

// 使用:
div {
  color: var(--my-color);
}

@property --property-name 中的 --property-name 就是自定義屬性的名稱,定義後可在 CSS 中通過 var(--property-name) 進行引用

  • syntax:該自定義屬性的語法規則,也可以理解為表示定義的自定義屬性的型別
  • inherits:是否允許繼承
  • initial-value:初始值

2、JS 中定義 CSS 屬性 CSS.registerProperty

window.CSS.registerProperty({
  name: '--my-color',
  syntax: '<color>',
  inherits: false,
  initialValue: '#c0ffee',
})

// css中使用
div {
  color: var(--my-color);
}

二、CSS Paint API(Worklets)

該 API 使我們能夠通過 Canvas 以程式設計方式為需要影像的任何 CSS 屬性建立影像。此類屬性的示例是 background-imageborder-image

CSS Paint API 可以簡單理解為把 Canvas 作為普通元素的背景圖,也就是說 CSS 的 background-image 就是一個 Canvas,可以利用這個特性為很多元素繪製背景特效。

小示例

下面我們為 textarea 寫一個棋盤背景,效果如下:

image.png

<!-- index.html -->
<!doctype html>
<style>
  textarea {
    background-image: paint(checkerboard);
  }
</style>
<textarea></textarea>
<script>
  CSS.paintWorklet.addModule('checkerboard.js');
</script>
// checkerboard.js
class CheckerboardPainter {
  paint(ctx, geom, properties) {
    // Use `ctx` as if it was a normal canvas
    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();
      }
    }          
  }
}

// Register our class under a specific name
registerPaint('checkerboard', CheckerboardPainter);

Codepen Demo

它的固定用法套路分三步走:

  1. CSS 中 paint(abc);
  2. JS 新增模組 CSS.paintWorklet.addModule('xxx.js');
  3. xxx.js 中程式碼套路固定,在下面註釋位置寫繪製程式碼即可;

    registerPaint('abc', class {
    paint(context, size, properties, args) {
       // 繪製程式碼在這裡....
    }
    });

    registerPaint 方法註冊了一個 Paint 類 abc 以供呼叫,這個類的核心在於它的 paint 方法。paint 方法用於描述自定義的繪製邏輯,它接收四個引數:

  4. context:繪圖的上下文,API 全部都是來自 Canvas 的 CanvasRenderingContext2D,不過為了安全限制,有些 Canvas 中的 API 是不能使用的。
  5. size:節點的尺寸資訊,同時也是 canvas 可繪製範圍(畫板)的尺寸資訊。
  6. properties:包含節點的 CSS 屬性,需要呼叫靜態方法 inputProperties 宣告注入。
  7. args:CSS 中呼叫 Paint 類時傳入的引數,需要呼叫靜態方法 inputArguments 宣告注入。

三、CSS Typed OM

以前我們修改 DOM 元素樣式,實際上我們操作的是 CSS 的物件模型 CSSOM。而 CSSOM 簡單說就是能讓 JS 操作元素樣式的 API:

const el = document.getElementById('el');
el.style.opacity = 0.3;

這會存在一個問題 el.style.opacity 的型別並非一個數字,而是一個字串。如果要像將其進行數學計算,那就需要先進行型別轉換,所以這就是 Typed OM 要解決的問題。

將 CSSOM 值字串轉換為有意義的型別化 JavaScript 表示並返回可能會導致顯著的效能開銷。該規範將 CSS 值公開為型別化的 JavaScript 物件,以使操作它們更容易且效能更高。

它的賣點有:

  1. 更好的效能,由於減少了字串操作,對於 CSSOM 的操作效能得到了更進一步的提升,由 Tab Akins(github 使用者)提供的測試表明,操作 Typed OM 比直接操作 CSSOM 字串帶來了大約 30% 的速度提升;
  2. 錯誤處理,對於錯誤的 CSS 值,將會丟擲錯誤;
  3. 在數值物件上呼叫簡單的算術運算方法,絕對單位之間還能方便得盡興單位轉換;

讀取和賦值用法

在 Typed OM 中,數值和數值的單位是分開的,所獲取的是一個 CSSUnitValue 物件,內建數值 value 和單位 unit 兩個鍵。

// 要對一個元素的樣式賦值,除了可以使用 CSS.px 構建之外,還能接受字串
el.attributeStyleMap.set('height', CSS.px(10));
el.attributeStyleMap.set('height', '10px');

// 對於獲取,返回 CSSUnitValue 物件,訪問其 value 屬性即可得到數字型別的值
el.attributeStyleMap.get('height').value; // 10
el.attributeStyleMap.get('height').unit; // 'px'

其它用法自性探索。

小結

Typed OM 對於其它 Houdini API 的意義:Typed OM 的使用在為往後更高效地發展各個 Houdini 標準打下了基礎,包括自定義屬性,佈局以及繪製相關標準。

CSS Typed OM 解決了開發時修改數值的問題,同時通過減少字串操作增加了總體的操作效能,使得我們在操作 CSSOM 不僅方便還高效,配合 requestAnimationFrame 還能製作出效能更優的自定義動畫。

四、CSS Layout API

CSS Layout API 的招牌就是讓開發者自定義佈局方式,比如瀑布流等。讓 Web 佈局有更多的想象空間,由於瀏覽器還沒有完全開放給開發者,並且學習成本較高,需要CSS和JS同時具有一定造詣才能駕馭,因此本文不做展開。

Reference

相關文章