Houdini - 建立自定義的button樣式

SuperChe發表於2018-04-29

Summary

Houdini是CSS的發展方向之一,讓開發者擁有操作CSS引擎的能力,建立自定義的CSS。讓我們從自定義button樣式開始,快速上手Houdini。

Houdini是什麼?

為什麼需要它 - CSS的polyfill

相比保守的CSS,JS則可以更加自由地定義各種屬性和方法,實現各種抽象的邏輯。在近幾年的飛速發展中,開發者可以激進地使用最新的JS特性,並通過一系列ployfill解決瀏覽器的相容問題。CSS則與之相反,一直沒有開放CSS引擎的API。這使得開發者不得不十分謹慎地使用CSS,避免各種瀏覽器相容性問題。

為了加速CSS在社群的演進,Houdini作為一個新的W3C工作組,他們希望提供一套API,來讓開發者擁有操作CSS引擎的能力,進而建立自定義的CSS。Houdini工作組眾星雲集,Mozilla,Apple,Opera,Microsoft,HP,Intel和Google的工程師都參與其中。目前,Houdini還是一份草案,這裡展示了詳細內容。

Houdini的核心概念

從W3C的草案中,我們能夠發現Houdini計劃提供這幾種API/Class:Layout API,Painting API,Properties & Values API,Worklets,Typed OM,Box Tree API和Parser API。他們的相容性可以在ishoudini檢視。目前為止,Chrome (Canary)是相容性最好的瀏覽器。

本文以目前支援度最好的CSS Paint API為例,著重介紹兩個核心概念:Worklets和CSS Paint API。

Worklets

Worklets是一個在瀏覽器rendering pipeline執行指令碼的Class,它獨立於JS主執行緒。這聽起來和Web Workers很像,但它們的目標和區別在於

  • Web Workers作為一個後臺執行的執行緒,完成一些長時間的工作而且不會阻塞主執行緒。它應該長時間存活,並消耗相對較多的CPU、記憶體。
  • Worklets是個受渲染引擎控制的輕量化、短生命週期執行緒,開發者並不知道這個執行緒的具體情況,並且同一類Worklets可能有多個例項,並且直接註冊在Global scope。

可以認為Worklets是個抽象類,開發者需要實現它的子類,例如Paint Worklet。同時,僅定義Worklets是沒有任何效果的,需要把它註冊到Global Scope上。在下面的CSS Paint API我們能看到一個具體的例子。

CSS Paint API

CSS Paint API用來繪製一個CSS box的background,content和highlight。首先定義一個Paint Worklet,它是個JS Class。接著用registerPaint函式註冊到Global Scope。最後,我們在Demo中使用CSS.paintWorklet.addModule函式import這個JS Module。完成這3步後,就能在CSS中使用paint函式了,例如:background: paint('my-background')。

Paint Worklet用paint函式用來繪製圖案,它有4個引數

  • ctx: 一個PaintRenderingContext2D例項,canvas的大部分功能都能夠使用
  • size: 一個PaintSize例項,包含元素的width, height
  • styleMap: 一個StylePropertyMapReadOnly例項,主要用於讀取DOM的style property
  • args: paint函式的輸入引數,例如在CSS中宣告:background: paint('my-background', red),red就是一個輸入引數

inputProperties函式返回自定義的CSS Property Name,例如'--my-prop',不在此宣告的Property會被渲染引擎過濾,即無法在styleMap中讀取。

inputArguments函式返回自定義的CSS Property Value的格式,例如'<color>',預設時不做檢查,可被看成string。這裡是目前支援的格式。

class Button {
  static get inputArguments() { return ['<color>'] }
  static get inputProperties() { return ['--my-prop'] }
  
  paint(ctx, size, styleMap, args) {
    // paint function do drawing on the ctx, just like canvas
  }
}
// this is the critical point
// registerPaint function is valid with CSS Paint API
registerPaint('button', Button)
複製程式碼

快速上手 - 自定義button

瞭解了Worklets和CSS Paint API後,我們就能開始自定義自己的button樣式。

定義Button Worklet

先定義Button Worklet。它定義了一個custom property: --my-bg-color,接著在paint函式中,呼叫drawBezierCurve函式畫了一條貝塞爾曲線。

class Button {
  constructor() {
    this.width = 0;
    this.height = 0;
    this.bgColor = "black";
  }

  static get inputProperties() {
    return ["--my-bg-color"];
  }

  paint(ctx, size, styleMap) {
    const { width, height } = size;
    this.width = width;
    this.height = height;
    this.bgColor = styleMap
      .get("--my-bg-color")
      .toString()
      .trim();
    this.drawBezierCurve(ctx);
  }

  drawBezierCurve(ctx) {
    ctx.fillStyle = this.bgColor;
    ctx.beginPath();
    ctx.moveTo(0, this.height);
    ctx.lineTo(this.width / 4, this.height);
    ctx.bezierCurveTo(
      this.width / 3,
      this.height,
      this.width / 2,
      this.height / 3 * 2,
      this.width / 2,
      this.height / 2
    );
    ctx.bezierCurveTo(
      this.width / 2,
      this.height / 3,
      this.width / 3 * 2,
      0,
      this.width / 4 * 3,
      0
    );
    ctx.lineTo(0, 0);
    ctx.fill();
  }
}

registerPaint("button", Button);
複製程式碼

See the Pen Button-Worklets by Shaw Che (@sche) on CodePen.

註冊這個PaintWorklet

第二步,在頁面中註冊這個PaintWorklet。

if (!CSS.paintWorklet) {
  document.querySelector('#alert').classList.add('show-alert');
} else {
  CSS.paintWorklet.addModule("https://codepen.io/sche/pen/KRadBG.js");
}
複製程式碼
<div id="alert" class="alert">
  You need support for CSS Paint API to view this demo. Go to here to find a good web browser.
  <a href="https://ishoudinireadyyet.com/">ishoudinireadyyet.com</a>
</div>
<input type="button" class="my-button" value="btn">
</input>
<input type="button" class="my-button" value="btn 2"></input>
<span class="my-button">
  span btn
</span>
複製程式碼
.my-button {
  --my-bg-color: lightblue;
  width: 70px;
  height: 20px;
  border-radius: 10px;
  color: black;
  background-image: paint(button);
}

.alert {
  display: none;
}

.show-alert {
  display: block !important;
}
複製程式碼

See the Pen Houdini - Button by Shaw Che (@sche) on CodePen.

Reference

  • https://drafts.css-houdini.org/
  • https://ishoudinireadyyet.com/
  • https://www.smashingmagazine.com/2016/03/houdini-maybe-the-most-exciting-development-in-css-youve-never-heard-of/
  • https://zhuanlan.zhihu.com/p/35479957
  • 封面圖片 - Photo by Denise Johnson on Unsplash

相關文章