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