CSS Paint API

Chrome 65 的新功能

从 Chrome 65 开始,CSS Paint API(也称为“CSS Custom Paint”或“Houdini’s Paint worklet”)默认处于启用状态。Google 优惠是什么?您可以对它执行哪些操作?运作方式是怎样的?那么,继续,您是吗...

只要 CSS 属性需要图片,您便能通过 CSS Paint API 以编程方式生成图片。background-imageborder-image 等属性通常与 url() 一起使用来加载图片文件,或与 linear-gradient() 等 CSS 内置函数一起使用。您现在可以使用 paint(myPainter) 来引用绘制 Worklet,而不是使用它们。

编写 Paintlet

如需定义一个名为 myPainter 的绘制 Worklet,我们需要使用 CSS.paintWorklet.addModule('my-paint-worklet.js') 加载 CSS 绘制 Worklet 文件。在该文件中,我们可以使用 registerPaint 函数注册一个绘制 worklet 类:

class MyPainter {
  paint(ctx, geometry, properties) {
    // ...
  }
}

registerPaint('myPainter', MyPainter);

paint() 回调内,我们使用 ctx 的方式与使用 CanvasRenderingContext2D 的方式相同,就像从 <canvas> 中得知的一样。如果您知道如何在 <canvas> 中绘制,则可以在绘制 Worklet 中进行绘制!geometry 告诉我们可以使用的画布的宽度和高度。properties。我将在稍后的文章中对此进行解释。

作为入门示例,我们将编写一个棋盘格绘制 Worklet 并将其用作 <textarea> 的背景图片。(我之所以使用文本区域,是因为它默认可调整大小。):

<!-- 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);

如果您以前使用过 <canvas>,应该很熟悉此代码。如需查看实时演示,请点击此处。

带有棋盘图案的文本区域作为背景图片
以棋盘图案作为背景图片的 Textarea。

此处使用通用背景图片的不同之处在于,每当用户调整文本区域的大小时,图案都会按需重新绘制。这意味着背景图片始终与实际大小完全一样,包括针对高密度显示屏进行补偿。

这很酷,但它也很静态。每当我们想要相同的图案,但正方形大小不同时,是否都需要编写一个新的 Worklet?答案是否定的!

将 Worklet 参数化

幸运的是,绘制 Worklet 可以访问其他 CSS 属性,这正是额外的参数 properties 的用武之地。通过为该类提供静态 inputProperties 属性,您可以订阅对任何 CSS 属性(包括自定义属性)的更改。这些值将通过 properties 参数提供给您。

<!-- index.html -->
<!doctype html>
<style>
  textarea {
    /* The paint worklet subscribes to changes of these custom properties. */
    --checkerboard-spacing: 10;
    --checkerboard-size: 32;
    background-image: paint(checkerboard);
  }
</style>
<textarea></textarea>
<script>
  CSS.paintWorklet.addModule('checkerboard.js');
</script>
// checkerboard.js
class CheckerboardPainter {
  // inputProperties returns a list of CSS properties that this paint function gets access to
  static get inputProperties() { return ['--checkerboard-spacing', '--checkerboard-size']; }

  paint(ctx, geom, properties) {
    // Paint worklet uses CSS Typed OM to model the input values.
    // As of now, they are mostly wrappers around strings,
    // but will be augmented to hold more accessible data over time.
    const size = parseInt(properties.get('--checkerboard-size').toString());
    const spacing = parseInt(properties.get('--checkerboard-spacing').toString());
    const colors = ['red', 'green', 'blue'];
    for(let y = 0; y < geom.height/size; y++) {
      for(let x = 0; x < geom.width/size; x++) {
        ctx.fillStyle = colors[(x + y) % colors.length];
        ctx.beginPath();
        ctx.rect(x*(size + spacing), y*(size + spacing), size, size);
        ctx.fill();
      }
    }
  }
}

registerPaint('checkerboard', CheckerboardPainter);

现在,我们可以将相同的代码用于所有不同类型的棋盘。但更棒的是,我们现在可以在开发者工具中修改值,直到找到合适的外观。

不支持绘制 Worklet 的浏览器

在撰写本文时,只有 Chrome 实现了绘制 Worklet。虽然所有其他浏览器供应商都提供了积极的信号,但进展不大。如需了解最新动态,请定期查看 Is Houdini Ready?。在此期间,即使不支持绘制 Worklet,也请务必使用渐进式增强功能来保持代码运行。为了确保一切按预期运行,您必须在以下两个位置调整代码:CSS 和 JS。

您可以通过检查 CSS 对象来检测对 JS 中绘制 Worklet 的支持: js if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('mystuff.js'); } 对于 CSS 端,您有两种选择。您可以使用 @supports

@supports (background: paint(id)) {
  /* ... */
}

一种更为紧凑的技巧是利用 CSS 失效并随后忽略整个属性声明(如果其中存在未知函数)这一事实。如果您指定了两次属性 - 第一次不带绘制 worklet,第二次指定绘制 worklet,您会获得渐进式增强:

textarea {
  background-image: linear-gradient(0, red, blue);
  background-image: paint(myGradient, red, blue);
}

在支持绘制 worklet 的浏览器中,background-image 的第二个声明会覆盖第一个声明。在不支持绘制 worklet 的浏览器中,第二个声明无效并将被舍弃,因此第一个声明仍有效。

CSS 绘制 Polyfill

对于多种用途,您还可以使用 CSS Paint Polyfill,它可以为新型浏览器添加 CSS Custom Paint 和 Paint Worklet 支持。

用例

绘制 Worklet 的用例有很多,其中一些用例比其他用例更明显。其中一种比较明显的方法是使用绘制 worklet 来缩减 DOM 的大小。通常,添加元素纯粹是为了使用 CSS 进行装饰。例如,在 Material Design Lite 中,具有涟漪效果的按钮包含 2 个额外的 <span> 元素,用于实现涟漪本身。如果您有大量的按钮,这样可能会添加大量 DOM 元素,并可能导致在移动设备上的性能下降。如果您改为使用绘制 worklet 实现涟漪效果,则最终不会产生任何额外的元素,而只会有一个绘制 Worklet。此外,您还可以更轻松地对某些元素进行自定义和参数化。

使用绘制 Worklet 的另一个好处是,在大多数情况下,使用绘制 Worklet 的解决方案就字节数来说很小。当然,这需要权衡操作:只要画布的大小或任何参数发生更改,绘制代码就会运行。因此,如果您的代码很复杂并且需要很长时间,则可能会引入卡顿。Chrome 正在努力将绘制 Worklet 移出主线程,这样即使是长时间运行的绘制 Worklet 也不会影响主线程的响应速度。

对我来说,最激动人心的前景是绘制 Worklet 能够对 CSS 功能进行高效的 polyfill,而浏览器尚未实现。例如,以 polyfill 填充圆锥渐变,直到它们以原生方式到达 Chrome。再比如,在 CSS 会议中,我们决定现在可设置多种边框颜色。在这场会议进行期间,我的同事 Ian Kilpatrick 利用 Paint worklet 为这项新的 CSS 行为编写了 polyfill

跳出“条条框框”思维模式

大多数人在学习绘制 Worklet 后,就会开始考虑背景图片和边框图片。绘制 worklet 的一个不太直观的用例是 mask-image,它使 DOM 元素具有任意形状。例如 diamond

一个菱形的 DOM 元素。
一个菱形的 DOM 元素。

mask-image 会拍摄一张与元素大小相同的图片。遮罩图片透明的区域,即元素透明的区域。遮罩图片不透明(元素不透明)的区域。

现已支持 Chrome

Paint Worklet 在 Chrome Canary 中已有一段时间。在 Chrome 65 中,此功能默认处于启用状态。快试试这些新的可能性 让绘制工作界面就能打开,让我们看看你构建了什么!如需更多灵感,请参阅 Vincent De Oliveira 的收藏