CSS 绘画 API 第 1 级

W3C 候选推荐草案,

更多关于此文档的信息
此版本:
https://www.w3.org/TR/2021/CRD-css-paint-api-1-20211216/
最新发布版本:
https://www.w3.org/TR/css-paint-api-1/
编辑草案:
https://drafts.css-houdini.org/css-paint-api-1/
之前的版本:
历史记录:
https://www.w3.org/standards/history/css-paint-api-1
反馈:
public-houdini@w3.org 主题行使用 “[css-paint-api] … 消息主题 …” (存档)
GitHub
实现报告:
https://wpt.fyi/results/css/css-paint-api
编辑:
前编辑:

摘要

一个允许Web开发人员使用JavaScript定义自定义CSS <image>的API, 该API能够响应样式和尺寸的变化。 参见 解释文档。

本文档的状态

本节描述了本文档在发布时的状态。 当前W3C出版物列表及本技术报告的最新修订版 可以在 W3C技术报告索引 https://www.w3.org/TR/.中找到。

本文档由CSS工作组作为候选推荐草案发布,使用推荐规范路径。 作为候选推荐草案发布并不意味着 W3C及其成员的认可。 候选推荐草案集成了工作组打算包含在后续候选推荐快照中的上一版候选推荐的更改。

这是一个草案文档, 可能会随时更新、替换或被其他文档取代。 除了作为正在进行的工作外,引用本文档是不合适的。

请通过在GitHub中提交问题(首选)反馈, 包含规范代码“css-paint-api”在标题中,如下所示: “[css-paint-api] …评论摘要…”。 所有问题和评论均被存档。 或者,反馈可以发送至存档的公共邮件列表 www-style@w3.org。

本文档受2021年11月2日W3C流程文档的管理。

本文档由遵循W3C专利政策的工作组制作。 W3C维护了任何与工作组交付物相关的公开专利列表; 该页面还包含披露专利的说明。 如果个人掌握任何专利,并且该专利中包含必要声明, 则必须根据W3C专利政策第6节披露相关信息。

1. 介绍

CSS的绘画阶段负责根据盒子的大小(由布局阶段生成)和计算样式绘制盒子的背景、内容和高亮部分。

本规范描述了一个API,允许开发人员通过额外的<image>函数 来响应大小/计算样式的变化以绘制盒子的部分内容。

注意:在未来的规范版本中,可能会增加支持定义盒子部分(例如背景层)的裁剪、全局透明度和滤镜功能。

2. 绘画 Worklet

CSS/paintWorklet

仅在一个现有引擎中可用。

Firefox无Safari无Chrome65+
Opera52+Edge79+
Edge (旧版)无IE无
Firefox for Android无iOS Safari无Chrome for Android65+Android WebView65+Samsung Internet9.0+Opera Mobile47+

paintWorklet 属性允许访问负责所有与绘画相关类的Worklet。

paintWorklet 的Worklet全局作用域类型是PaintWorkletGlobalScope。

paintWorklet 的Worklet目标类型是"paintworklet"。

partial namespace CSS {
    [SameObject] readonly attribute Worklet paintWorklet;
};

PaintWorkletGlobalScope 是paintWorklet 的全局执行上下文。

PaintWorklet/devicePixelRatio

仅在一个现有引擎中可用。

Firefox无Safari无Chrome65+
Opera?Edge79+
Edge (旧版)无IE无
Firefox for Android无iOS Safari无Chrome for Android65+Android WebView65+Samsung Internet9.0+Opera Mobile?

PaintWorkletGlobalScope 有一个与 Window.devicePixelRatio 属性相同的devicePixelRatio属性。

PaintWorklet

仅在一个现有引擎中可用。

Firefox无Safari无Chrome65+
Opera?Edge79+
Edge (旧版)无IE无
Firefox for Android无iOS Safari无Chrome for Android65+Android WebView65+Samsung Internet9.0+Opera Mobile?
[Global=(Worklet,PaintWorklet),Exposed=PaintWorklet]
interface PaintWorkletGlobalScope : WorkletGlobalScope {
    undefined registerPaint(DOMString name, VoidFunction paintCtor);
    readonly attribute unrestricted double devicePixelRatio;
};

PaintRenderingContext2DSettings 包含与绘画画布相关的渲染上下文的设置。PaintRenderingContext2DSettings 提供了画布渲染上下文2D设置的支持子集。将来,它可能会扩展以支持绘画画布中的颜色管理。

dictionary PaintRenderingContext2DSettings {
    boolean alpha = true;
};
注意:类的结构应如下所示:
class MyPaint {
    static get inputProperties() { return ['--foo']; }
    static get inputArguments() { return ['<color>']; }
    static get contextOptions() { return {alpha: true}; }

    paint(ctx, size, styleMap) {
        // 在此编写绘画代码。
    }
}

3. 概念

绘画定义是一个 结构体,用于描述 PaintWorkletGlobalScope 所需的有关作者定义的<image>的 信息(可以通过<paint()>函数引用)。它包括以下内容:

文档绘画定义是一个 结构体,用于描述 文档所需的有关作者定义的<image>函数的信息(可以通过绘画函数引用)。它包括以下内容:

4. 注册自定义绘画

文档包含一个文档绘画定义的映射。最初 该映射为空;当调用registerPaint(name, paintCtor) 时会填充该映射。

PaintWorkletGlobalScope 包含一个绘画定义的映射。最初该映射为空;当调用registerPaint(name, paintCtor) 时会填充该映射。

PaintWorkletGlobalScope 包含一个绘画类实例的映射。最初该映射为空;当用户代理调用绘制绘画图像时会填充该映射。

用户代理可以随时处置并从映射中删除绘画类实例映射中的实例。当某个<paint()>函数不再被使用时, 或者用户代理需要回收内存时,可以执行此操作。

PaintWorklet/registerPaint

仅在一个现有引擎中可用。

Firefox无Safari无Chrome65+
Opera?Edge79+
Edge (旧版)无IE无
Firefox for Android无iOS Safari无Chrome for Android65+Android WebView65+Samsung Internet9.0+Opera Mobile?
当 registerPaint(name, paintCtor) 方法被 调用时,用户代理必须运行以下步骤:
  1. 如果 name 是空字符串,则抛出一个 TypeError 并中止 所有这些步骤。

  2. 令 paintDefinitionMap 为 PaintWorkletGlobalScope 的 绘制 定义映射。

  3. 如果 paintDefinitionMap[name] 存在,则抛出一个 "InvalidModificationError" DOMException 并中止所有这些步骤。

  4. 令 inputProperties 为空的 sequence<DOMString>。

  5. 令 inputPropertiesIterable 为 Get(paintCtor, "inputProperties") 的结果。

  6. 如果 inputPropertiesIterable 不是 undefined,则将 inputProperties 设置为 将 inputPropertiesIterable 转换 为 sequence<DOMString> 的结果。如果 抛出异常,则重新抛出该异常并中止所有这些步骤。

  7. 过滤 inputProperties,使其仅包含受支持的 CSS 属性和自定义属性。

注: 输入属性 getter 提供的 CSS 属性列表既可以是自定义 CSS 属性,也可以是 原生 CSS 属性。

注: CSS 属性列表可以包含简写属性。

注: 为使绘制图像类具有向前兼容性, CSS 属性列表 还可以包含当前对用户代理无效的属性。例如 margin-bikeshed-property。

  1. 令 inputArguments 为空的 sequence<DOMString>。

  2. 令 inputArgumentsIterable 为 Get(paintCtor, "inputArguments") 的结果。

  3. 如果 inputArgumentsIterable 不是 undefined,则将 inputArguments 设置为 将 inputArgumentsIterable 转换 为 sequence<DOMString> 的结果。如果 抛出异常,则重新抛出该异常并中止所有这些步骤。

  4. 令 inputArgumentSyntaxes 为一个空列表。

  5. 对 inputArguments 中的每个 item 执行 以下子步骤:

    1. 尝试从 item 中消费一个语法定义。 如果返回失败,则抛出一个 TypeError 并中止所有这些步骤。 否则,令 parsedSyntax 为返回的语法定义。

    2. 将 parsedSyntax 追加到 inputArgumentSyntaxes。

  6. 令 contextOptionsValue 为 Get(paintCtor, "contextOptions") 的结果。

  7. 令 paintRenderingContext2DSettings 为将 contextOptionsValue 转换 为 PaintRenderingContext2DSettings 的结果。 如果抛出异常,则重新抛出该异常并中止所有这些步骤。

    注: 将 paintRenderingContext2DSettings.alpha 设置为 false 允许用户代理 除执行“可见性”优化之外还对文本进行抗锯齿处理,例如,由于绘制图像是不透明的而不 绘制位于绘制图像后面的图像。

  8. 如果 IsConstructor(paintCtor) 的结果为 false,则抛出一个 TypeError 并中止 所有这些步骤。

  9. 令 prototype 为 Get(paintCtor, "prototype") 的结果。

  10. 如果 Type(prototype) 的结果不是 Object,则抛出一个 TypeError 并 中止所有这些步骤。

  11. 令 paintValue 为 Get(prototype, "paint") 的结果。

  12. 令 paint 为将 paintValue 转换为 Function 回调函数类型的结果。重新抛出 转换过程中产生的任何异常。

  13. 令 definition 为一个新的绘制定义,其:

  14. 将 paintDefinitionMap[name] 设置为 definition。

  15. 将一个任务加入队列以运行以下步骤:

    1. 令 documentPaintDefinitionMap 为关联文档的文档 绘制 定义映射。

    2. 令 documentDefinition 为一个新的文档 绘制定义,其:

    3. 如果 documentPaintDefinitionMap[name] 存在,则运行以下步骤:

      1. 令 existingDocumentDefinition 为获取 documentPaintDefinitionMap[name] 的结果。

      2. 如果 existingDocumentDefinition 为 "invalid",则中止所有 这些步骤。

      3. 如果 existingDocumentDefinition 和 documentDefinition 不等价(即输入 属性、输入 参数语法和PaintRenderingContext2DSettings 对象不同),则:

        将 documentPaintDefinitionMap[name] 设置为 "invalid"。

        向调试控制台记录一条错误,说明同一个类使用了不同的 inputProperties、inputArguments 或 paintRenderingContext2DSettings 进行注册。

    4. 否则,将 documentPaintDefinitionMap[name] 设置 为 documentDefinition。

注: 输入属性列表应仅查找一次, 该类没有 动态更改其输入属性的机会。

注: 在本规范的未来版本中,作者可能能够 接收不同类型 的 RenderingContext。特别是,作者可能希望使用 WebGL 渲染上下文来渲染 3D 效果。设置 WebGL 渲染上下文以将 PaintSize 和 StylePropertyMap 作为输入存在一些复杂性。

5. 绘画符号

image/paint()

仅在一个现有引擎中可用。

Firefox无Safari无Chrome65+
Opera52+Edge79+
Edge (旧版)无IE无
Firefox for Android无iOS Safari无Chrome for Android65+Android WebView65+Samsung Internet9.2+Opera Mobile47+
paint() = paint( <ident>, <declaration-value>? )

<paint()>函数是对<image>类型的额外支持符号。

<style>
    .logo { background-image: paint(company-logo); }
    .chat-bubble { background-image: paint(chat-bubble, blue); }
</style>

对于光标属性,<paint()>函数应被视为无效图像,并回退到下一个支持的<image>。

在计算值时间,<paint()>函数 不需要匹配由registerPaint() 注册的语法。 相反,当在绘制绘画图像时进行解析时,这将导致一个无效图像。

6. 2D 渲染上下文

[Exposed=PaintWorklet]
interface PaintRenderingContext2D {
};
PaintRenderingContext2D includes CanvasState;
PaintRenderingContext2D includes CanvasTransform;
PaintRenderingContext2D includes CanvasCompositing;
PaintRenderingContext2D includes CanvasImageSmoothing;
PaintRenderingContext2D includes CanvasFillStrokeStyles;
PaintRenderingContext2D includes CanvasShadowStyles;
PaintRenderingContext2D includes CanvasRect;
PaintRenderingContext2D includes CanvasDrawPath;
PaintRenderingContext2D includes CanvasDrawImage;
PaintRenderingContext2D includes CanvasPathDrawingStyles;
PaintRenderingContext2D includes CanvasPath;

注意:PaintRenderingContext2D 实现了 CanvasRenderingContext2D API 的一个子集。 它特别没有实现 CanvasImageData、 CanvasUserInterface、 CanvasText, 或者CanvasTextDrawingStyles API。

PaintRenderingContext2D 对象有一个输出位图。 它在对象创建时被初始化。 输出位图的大小是它所渲染对象的具体对象大小。

PaintRenderingContext2D 对象还具有一个alpha标志, 该标志可以设置为 true 或 false。 在创建上下文时,最初必须将其 alpha 标志设置为 true。 当 PaintRenderingContext2D 对象的 alpha 标志设置为 false 时, 它的 alpha 通道必须对所有像素固定为 1.0(完全不透明),并且尝试更改任何像素的 alpha 分量的操作必须被静默忽略。

输出位图的大小不一定代表用户代理在内部或渲染过程中使用的实际位图大小。 例如,如果视觉视口被缩放,用户代理可能会在内部使用与坐标空间中的设备像素数量相对应的位图,以便生成高质量的渲染。

此外,用户代理可能会记录已应用于 输出位图的绘制操作顺序, 以便用户代理随后可以在设备位图上以正确的分辨率进行绘制。 这也允许用户代理在视觉视口缩放时重复使用相同的 输出位图。

在"currentColor"用作PaintRenderingContext2D API 中的颜色时,它被视为不透明的黑色。

以下代码将生成一个纯黑色的矩形。
registerPaint('currentcolor', class {
    paint(ctx, size) {
        ctx.fillStyle = 'currentColor';
        ctx.fillRect(0, 0, size.width, size.height);
    }
});
当用户代理要为给定的 width、height 和 paintRenderingContext2DSettings 创建一个 PaintRenderingContext2D 对象 时,它必须运行以下步骤:
  1. 创建一个新的 PaintRenderingContext2D。

  2. 设置位图尺寸为上下文的 输出位图,将 width 和 height 四舍五入后的值设置为该位图的尺寸。

  3. 将 PaintRenderingContext2D 的 alpha 标志设置为 paintRenderingContext2DSettings 的 alpha。

  4. 返回新的 PaintRenderingContext2D。

注意: 渲染上下文的初始状态在 设置位图尺寸算法中设置, 因为它调用了 重置渲染上下文为默认状态,并清除了 输出位图。

6.1. 绘制 CSSImageValue

CanvasImageSource typedef 被扩展以包括 CSSImageValue 类型,用作图像源。

对于使用 CanvasDrawImage 混入的接口:

注意: 这部分内容应最终移动到 HTML 规范的 canvas 部分。 请参阅 问题 819。

7. 绘制图像

如果<paint()> 函数为盒生成的图像位于可视视口内, 则用户代理必须显示通过调用绘制绘制图像算法所输出的图像。

注: 对于可视视口内的 绘制绘制图像,用户代理不必在每一帧都运行<paint()> 函数。它可以缓存结果, (可能使用额外的 失效步骤)以显示正确的图像输出。

注: 用户代理可以选择推迟绘制位于 可视视口之外的图像。

如果作者在 requestAnimationFrame 内更新样式,例如
requestAnimationFrame(function() {
    element.styleMap.set('--custom-prop-invalidates-paint', 42);
});

并且 element 位于可视视口内,则要求用户代理绘制绘制 图像并显示当前帧的结果。

用户代理在负责渲染 <image> 的对象尺寸 协商算法期间调用绘制绘制 图像函数,其中 snappedConcreteObjectSize 定义如下。 令 concreteObjectSize 为具体对象 尺寸,即盒的具体对象尺寸。snappedConcreteObjectSize 通常与 concreteObjectSize 相同。但是,用户代理可以调整尺寸,使其绘制到像素 边界。如果这样做,用户代理应按相对于其原始尺寸的比例变化来调整 snappedConcreteObjectSize, 以便 <paint()> 函数可以相应地调整绘制。

就对象尺寸协商算法而言, 绘制图像没有固有尺寸。

注: 在本规范的未来版本中,作者可能能够 指定绘制图像的固有 尺寸。这可能会作为一个回调公开,使 作者能够定义静态固有 尺寸,或者根据计算样式和尺寸变化动态更新固有 尺寸。

PaintSize 对象表示作者应绘制的图像尺寸。这是 用户代理给出的 snappedConcreteObjectSize。

注: 有关如何计算CSS Images 3 § 4.4 CSS 对象尺寸示例中的具体对象 尺寸,请参见其中的示例。

用户代理可以在任何时候推测性地调用绘制绘制 图像函数, 并使用任意 snappedConcreteObjectSize。生成的图像不会显示。

注: 用户代理可以使用任意启发式方法来推测 snappedConcreteObjectSize 未来可能的值,例如推测尺寸保持不变。

注: 尽管图像不会显示,但仍可以对其进行缓存,并且 后续对 <paint()> 的调用可以使用缓存的图像。

[Exposed=PaintWorklet]
interface PaintSize {
    readonly attribute double width;
    readonly attribute double height;
};
当用户代理希望为一个 box 绘制绘制图像,即将 <paint()> 函数 绘制到其适当的层叠层级中(由与其关联的 CSS 属性所定义),给定 snappedConcreteObjectSize,它必须运行以下步骤:
  1. 令 paintFunction 为 box 上用户代理希望 绘制的 <paint()> 函数。

  2. 令 name 为 paintFunction 的第一个参数。

  3. 令 documentPaintDefinitionMap 为关联文档的文档绘制 定义映射。

  4. 如果 documentPaintDefinitionMap[name] 不存在,则令 图像输出 为无效图像,并中止所有这些步骤。

  5. 令 documentDefinition 为获取 documentPaintDefinitionMap[name] 的结果。

  6. 如果 documentDefinition 为 "invalid",则令图像输出为无效 图像,并中止所有这些步骤。

  7. 令 inputArgumentSyntaxes 为 documentDefinition 的输入参数语法。

  8. 令 inputArguments 为 paintFunction 中“绘制名称”参数 之后的所有参数所组成的列表。

  9. 如果 inputArguments 与 inputArgumentSyntaxes 给出的已注册语法不匹配,则令 图像输出为无效 图像,并中止所有这些步骤。

    此步骤在以下情况下可能失败:
    // paint.js
    registerPaint('failing-argument-syntax', class {
        static get inputArguments() { return ['<length>']; }
        paint(ctx, size, styleMap, args) { /* 此处为绘制代码。 */ }
    });
    
    <style>
        .example-1 {
            background-image: paint(failing-argument-syntax, red);
        }
        .example-2 {
            background-image: paint(failing-argument-syntax, 1px, 2px);
        }
    </style>
    <div class=example-1></div>
    <div class=example-2></div>
    <script>
        CSS.paintWorklet.addModule('paint.js');
    </script>
    

    example-1 会产生无效图像,因为 "red" 与 已注册的语法不匹配。

    example-2 会产生无效图像,因为函数 参数过多。

  10. 令 workletGlobalScope 为绘制 Worklet 的 全局作用域中的一个 PaintWorkletGlobalScope,并遵循 § 7.1 全局作用域选择中定义的规则。

    此时,给定绘制 Worklet, 用户代理可以另外创建 worklet 全局作用域。

  11. 给定 name、 inputArguments、snappedConcreteObjectSize、workletGlobalScope, 运行调用绘制回调,并可选择并行执行。

    注: 如果用户代理在一个线程上调用绘制 回调并并行执行, 则它应选择一个可在该线程上使用的绘制 worklet 全局作用域。

当用户代理希望在给定 name、 inputArguments、snappedConcreteObjectSize 和 workletGlobalScope 的情况下调用绘制回调时,它 必须运行以下步骤:
  1. 令 paintDefinitionMap 为 workletGlobalScope 的绘制定义映射。

  2. 如果 paintDefinitionMap[name] 不存在,则运行 以下步骤:

    1. 将一个任务加入队列以运行以下步骤:

      1. 令 documentPaintDefinitionMap 为关联文档的文档 绘制定义映射。

      2. 将 documentPaintDefinitionMap[name] 设置为 "invalid"。

      3. 用户代理应向调试控制台记录一条错误,指出 某个 类并未在所有 PaintWorkletGlobalScope 中注册。

    2. 令图像输出为无效图像,并中止所有这些步骤。

    注: 这处理了可能存在某个绘制 worklet 全局作用域未 接收到针对 name 的 registerPaint(name, paintCtor) 的情况(但另一个全局作用域 接收到了)。在另一个全局作用域上调用的绘制回调可能成功,但 在后续帧调用绘制绘制图像时将不会成功。

  3. 令 definition 为获取 paintDefinitionMap[name] 的结果。

  4. 令 paintClassInstanceMap 为 workletGlobalScope 的绘制类实例 映射。

  5. 令 paintInstance 为获取 paintClassInstanceMap[|name]| 的结果。如果 paintInstance 为 null,则运行以下 步骤:

    1. 如果 definition 上的构造函数有效标志 为 false,则令图像输出为无效图像,并中止所有这些步骤。

    2. 令 paintCtor 为 definition 上的类构造函数。

    3. 令 paintInstance 为 Construct(paintCtor) 的结果。

      如果 construct 抛出异常, 则将 definition 的构造函数有效标志设置为 false, 令图像输出为无效图像,并中止所有这些 步骤。

    4. 将 paintClassInstanceMap[name] 设置为 paintInstance。

  6. 令 inputProperties 为 definition 的输入属性。

  7. 令 styleMap 为一个新的 StylePropertyMapReadOnly, 其中仅填充 inputProperties 中所列属性的计算值。

  8. 令 renderingContext 为在给定以下内容的情况下创建 PaintRenderingContext2D 对象的结果:

    • "width" - 由 snappedConcreteObjectSize 给出的宽度。

    • "height" - 由 snappedConcreteObjectSize 给出的高度。

    • "paintRenderingContext2DSettings" - 由 definition 给出的PaintRenderingContext2DSettings 对象。

    注: renderingContext 不会在各次绘制调用 之间复用。这隐含地 意味着各次调用之间 renderingContext 上不存在存储的数据或状态。 例如,你不能在上下文上设置裁剪区域,然后期望下次调用绘制方法时 应用相同的裁剪区域。

    注: 这还隐含地意味着 在绘制方法完成后,renderingContext 实际上已被“失效”。 作者代码可以保留对 renderingContext 的引用 并 调用其方法,但这不会对当前图像或后续 图像产生任何影响。

  9. 令 paintSize 为一个新的 PaintSize, 并使用 snappedConcreteObjectSize 定义的宽度和高度对其进行初始化。

  10. 在此阶段,如果 paintSize、styleMap、inputArguments 与上一次调用中的对应值等价,则用户代理可以复用上一次调用的图像。 如果是这样,则令图像 输出为该缓存图像,并中止所有这些步骤。

    在下面的示例中,div-1 和 div-2 都具有绘制 函数,并且其 JavaScript 参数等价。用户代理可以缓存一次调用的结果 并将其用于这两个元素。
    // paint.js
    registerPaint('simple', class {
        paint(ctx, size) {
            ctx.fillStyle = 'green';
            ctx.fillRect(0, 0, size.width, size.height);
        }
    });
    
    <style>
        .div-1 {
            width: 50px;
            height: 50px;
            background-image: paint(simple);
        }
        .div-2 {
            width: 100px;
            height: 100px;
    
            background-size: 50% 50%;
            background-image: paint(simple);
        }
    </style>
    <div class=div-1></div>
    <div class=div-2></div>
    <script>
        CSS.paintWorklet.addModule('paint.js');
    </script>
    
  11. 令 paintFunctionCallback 为 definition 的绘制 函数。

  12. 使用参数 «renderingContext, paintSize, styleMap, inputArguments» 调用 paintFunctionCallback,并将 paintInstance 作为回调 this 值。

    如果 paintFunctionCallback 未能在可接受的时间内完成(由 用户代理确定,即它是“长时间运行的脚本”),则用户代理可以终止 脚本,令图像输出为无效图像,并中止所有这些步骤。

    注: 用户代理可以在其 调试工具中提供工具,以向作者显示 其绘制类的开销有多大。在适当情况下,用户代理还可以显示“无响应脚本” 对话框。

  13. 图像输出将由传递给该方法的 renderingContext 生成。

    如果抛出异常,则令图像输出为无效图像。

注: 生成图像的内容并非设计为 可访问的。作者可以通过标准无障碍 API 传达 任何有用的信息。

7.1. 全局作用域选择

当用户代理需要从 paint PaintWorkletGlobalScope 的 Worklet 的 全局作用域 列表中选择一个 时,它必须:

注意: 这些规则确保作者不会依赖于在全局对象或类上存储状态,参见 关于工作线程规范中关于代码幂等性的讨论。

8. 示例

8.1. 示例 1:彩色圆形

下面的示例利用了 <paint()> 函数可以被动画化的特性。 例如,当下面的文本框获得焦点时,--circle-color 属性将从 deepskyblue 过渡到 purple。

这种功能不仅限于过渡,还适用于 CSS 动画和 Web Animations API。

<!DOCTYPE html>
<style> 
  #example { 
    --circle-color: deepskyblue; 

    background-image: paint(circle); 
    font-family: sans-serif; 
    font-size: 36px; 
    transition: --circle-color 1s; 
  } 

  #example:focus { 
    --circle-color: purple; 
  } 
</style> 

<textarea id="example"> 
  CSS is awesome. 
</textarea> 

<script> 
    CSS.registerProperty({ 
      name: '--circle-color', 
      syntax: '<color>', 
      initialValue: 'black', 
      inherits: false 
    }); 
    CSS.paintWorklet.addModule('circle.js'); 
</script> 
// circle.js 
registerPaint('circle', class { 
  static get inputProperties() { return ['--circle-color']; } 
  paint(ctx, geom, properties) { 
    // 改变填充颜色。 
    const color = properties.get('--circle-color'); 
    ctx.fillStyle = color.cssText; 

    // 确定中心点和半径。 
    const x = geom.width / 2; 
    const y = geom.height / 2; 
    const radius = Math.min(x, y); 

    // 绘制圆形 \o/ 
    ctx.beginPath();
    ctx.arc(x, y, radius, 0, 2 * Math.PI, false); 
    ctx.fill(); 
  } 
}); 

8.2. 示例 2:图片占位符

作者可以使用 paint 在图片加载时绘制一个占位符图片。

<!DOCTYPE html>
<style>
#example {
    --image: url('#someUrlWhichIsLoading');
    background-image: paint(image-with-placeholder);
}
</style>

<div id="example"></div>

<script>
    CSS.registerProperty({
        name: '--image',
        syntax: '<image> | none',
        initialValue: 'none',
    });
    CSS.paintWorklet.addModule('image-placeholder.js');
</script>
// image-placeholder.js
registerPaint('image-with-placeholder', class {
    static get inputProperties() { return ['--image']; }
    paint(ctx, geom, properties) {
        const img = properties.get('--image');

        switch (img.state) {
            case 'ready':
                // The image is loaded! Draw the image.
                ctx.drawImage(img, 0, 0, geom.width, geom.height);
                break;
            case 'pending':
                // The image is loading, draw some mountains.
                drawMountains(ctx);
                break;
            case 'invalid':
            default:
                // The image is invalid (e.g. it didn’t load), draw a sad face.
                drawSadFace(ctx);
                break;
        }
    }
});

8.3. 示例 3:弧形

<!DOCTYPE html>
<style>
#example {
  width: 200px;
  height: 200px;

  background-image:
    paint(arc, purple, 0.4turn, 0.8turn, 40px, 15px),
    paint(arc, blue, -20deg, 170deg, 30px, 20px),
    paint(arc, red, 45deg, 220deg, 50px, 10px);
}
</style>

<div id="example"></div>

<script>
    CSS.paintWorklet.addModule('arc.js');
</script>
// arc.js
registerPaint('arc', class {
  static get inputArguments() {
    return [
      '<color>',
      '<angle>',  // startAngle
      '<angle>',  // endAngle
      '<length>', // radius
      '<length>', // lineWidth
    ];
  }

  paint(ctx, geom, _, args) {
    ctx.strokeStyle = args[0].cssText;

    // 确定中心点。
    const x = geom.width / 2;
    const y = geom.height / 2;

    // 将起始角度和结束角度转换为弧度。
    const startAngle = this.convertAngle(args[1]) - Math.PI / 2;
    const endAngle = this.convertAngle(args[2]) - Math.PI / 2;

    // 将半径和线宽转换为像素。
    const radius = this.convertLength(args[3]);
    const lineWidth = this.convertLength(args[4]);

    ctx.lineWidth = lineWidth;

    ctx.beginPath();
    ctx.arc(x, y, radius, startAngle, endAngle, false);
    ctx.stroke();
  }

  convertAngle(angle) {
    switch (angle.unit) {
      case 'deg':
        return angle.value * Math.PI / 180;
      case 'rad':
        return angle.value;
      case 'grad':
        return angle.value * Math.PI / 200;
      case 'turn':
        return angle.value * Math.PI / 0.5;
      default:
        throw Error(`Unknown angle unit: ${angle.unit}`);
    }
  }

  convertLength(length) {
    switch (length.type) {
      case 'px':
        return length.value;
      default:
        throw Error(`Unkown length type: ${length.type}`);
    }
  }
});

8.4. 示例 4:不同的颜色(基于大小)

<h1>
    Heading 1
</h1>
<h1>
    Another heading
</h1>

<style>
h1 {
    background-image: paint(heading-color);
}
</style>

<script>
    CSS.paintWorklet.addModule('heading-color.js');
</script>
// heading-color.js
registerPaint('heading-color', class {
    static get inputProperties() { return []; }
    paint(ctx, geom, properties) {
        // 根据图像的宽度和高度选择颜色。 
        const width = geom.width;
        const height = geom.height;
        const color = colorArray[(width * height) % colorArray.length];

        // 仅绘制一个实心图像。 
        ctx.fillStyle = color;
        ctx.fillRect(0, 0, width, height);
    }
});

8.5. 示例 5:在元素区域外绘制

可以通过使用 border-image 属性在元素的区域外进行绘制。

<style>
#overdraw {
    --border-width: 10;

    border-style: solid;
    border-width: calc(var(--border-width) * 1px);

    border-image-source: paint(overdraw);
    border-image-slice: 0 fill;
    border-image-outset: calc(var(--border-width) * 1px);

    width: 200px;
    height: 200px;
}
</style>
<div id="overdraw"></div>
<script>
    CSS.paintWorklet.addModule('overdraw.js');
</script>
// overdraw.js
registerPaint('overdraw', class {
    static get inputProperties() { return ['--border-width']; }
    paint(ctx, geom, properties) {
        const borderWidth = parseInt(properties.get('--border-width'));
        ctx.shadowColor = 'rgba(0,0,0,0.25)';
        ctx.shadowBlur = borderWidth;

        ctx.fillStyle = 'rgba(255, 255, 255, 1)';
        ctx.fillRect(borderWidth,
                     borderWidth,
                     geom.width - 2 * borderWidth,
                     geom.height - 2 * borderWidth);
    }
});

9. 安全性考虑

这些功能没有引入已知的安全问题。

10. 隐私考虑

11. 更改记录

自 2018 年 8 月 9 日 CR 版本发布以来的更改:

规范一致性

文档约定

一致性要求通过描述性断言和 RFC 2119 术语的组合来表达。关键字 “MUST(必须)”, “MUST NOT(禁止)”, “REQUIRED(要求)”, “SHALL(应)”, “SHALL NOT(禁止)”, “SHOULD(应该)”, “SHOULD NOT(不应该)”, “RECOMMENDED(推荐)”, “MAY(可以)”, 和 “OPTIONAL(可选)” 在本规范的规范部分中应按照 RFC 2119 的定义进行解释。 然而,为了可读性,这些词语在本规范中并未全部使用大写字母。

本规范的所有文本都是规范性的,除非明确标记为非规范性的部分、示例和注释。 [RFC2119]

本规范中的示例以“例如”引入,或者与规范文本分开显示,并带有 class="example", 例如:

这是一个信息性示例。

信息性注释以“注”开头,并与规范文本分开显示,带有 class="note",例如:

注:这是一个信息性注释。

建议是规范性部分,样式用于引起特别注意,并与其他规范性文本分开,使用 <strong class="advisement">, 例如: 用户代理必须提供一个可访问的替代方案。

规范一致性类别

本规范的一致性定义为三种一致性类别:

样式表
一个 CSS 样式表。
渲染器
一个 用户代理,用于解释样式表的语义并渲染 使用这些样式表的文档。
作者工具
一个 用户代理,用于编写样式表。

样式表符合本规范, 如果它的所有语法使用符合本模块定义的通用 CSS 语法和每个特性定义的单独语法是有效的。

渲染器符合本规范, 如果除了按照适当的规范定义解释样式表外,它还支持本规范定义的所有特性,正确解析它们, 并相应地渲染文档。然而,由于设备的限制,用户代理无法正确渲染文档并不会导致该用户代理不符合规范。(例如,用户代理不需要在单色显示器上渲染颜色。)

作者工具符合本规范, 如果它编写的样式表在语法上符合通用 CSS 语法和本模块中每个特性定义的单独语法,并符合本模块中描述的样式表的一致性要求。

部分实现

为了使作者能够利用向前兼容的解析规则来指定回退值, CSS 渲染器必须将任何无法使用的 at-rules、属性、属性值、关键字 和其他语法构造视为无效,并适当忽略。特别是,用户代理不得在多值属性声明中有选择地忽略不支持的组件值并保留支持的值:如果任何值被认为无效 (因为不支持的值必须无效),则 CSS 要求整个声明被忽略。

实现不稳定和专有特性

为了避免与未来稳定的 CSS 特性冲突, CSS 工作组建议遵循 最佳实践, 以实现 不稳定 特性和 CSS 的 专有扩展。

非实验性实现

当规范达到候选推荐标准阶段后, 可以进行非实验性的实现,且实施者应当 发布任何 CR 级别特性的未加前缀的实现, 并展示其根据规范正确实现。

为了建立并保持 CSS 在不同实现间的互操作性, CSS 工作组请求非实验性的 CSS 渲染器在发布任何 CSS 特性的未加前缀实现之前, 向 W3C 提交一个实现报告(如果有必要,还包括用于该实现报告的测试用例)。 提交给 W3C 的测试用例将由 CSS 工作组审查和修正。

关于提交测试用例和实现报告的更多信息, 可以在 CSS 工作组的网站 https://www.w3.org/Style/CSS/Test/ 上找到。 如有疑问,请发送至 public-css-testsuite@w3.org 邮件列表。

CR 退出标准

为了将本规范推进到拟推荐标准阶段, 每个特性必须至少有两个独立的、可互操作的实现。 每个特性可以由不同的产品集实现, 没有要求所有特性必须由单一产品实现。为此标准的目的,我们定义以下术语:

独立
每个实现必须由不同的组织开发,不能共享、重用或派生自另一个符合条件的实现的代码。 与本规范实现无关的代码部分不受此要求限制。
互操作性
通过官方 CSS 测试套件中的相关测试用例,或(如果实现不是 Web 浏览器)等效的测试。 每个相关测试都应有一个等效测试创建,如果该用户代理(UA)要用于声称互操作性, 那么必须有一个或多个其他 UA 也能以相同的方式通过这些等效测试以确保互操作性。 这些等效测试必须公开供同行评审使用。
实现
一个用户代理,它:
  1. 实现了该规范。
  2. 向公众开放。实现可以是已发布的产品或其他公开版本 (如测试版、预览版或“夜间构建”)。非发布产品必须在至少一个月的时间内实现该功能, 以证明其稳定性。
  3. 不是实验性的(即专为通过测试套件设计,并且不打算继续正常使用的版本)。

该规范将至少保持候选推荐标准 6 个月。

索引

本规范定义的术语

通过引用定义的术语

参考文献

规范性引用

[CSS-BACKGROUNDS-3]
Bert Bos; Elika Etemad; Brad Kemper. CSS背景和边框模块第3级。2021年7月26日。CR。网址:https://www.w3.org/TR/css-backgrounds-3/
[CSS-CASCADE-5]
Elika Etemad; Miriam Suzanne; Tab Atkins Jr.。CSS层叠和继承第5级。2021年12月3日。WD。网址:https://www.w3.org/TR/css-cascade-5/
[CSS-DISPLAY-3]
Tab Atkins Jr.; Elika Etemad. CSS显示模块第3级。2021年9月3日。CR。网址:https://www.w3.org/TR/css-display-3/
[CSS-IMAGES-3]
Tab Atkins Jr.; Elika Etemad; Lea Verou. CSS图像模块第3级。2020年12月17日。CR。网址:https://www.w3.org/TR/css-images-3/
[CSS-IMAGES-4]
Tab Atkins Jr.; Elika Etemad; Lea Verou. CSS图像值和替换内容模块第4级。2017年4月13日。WD。网址:https://www.w3.org/TR/css-images-4/
[CSS-PROPERTIES-VALUES-API-1]
Tab Atkins Jr.; et al. CSS属性和值API第1级。2020年10月13日。WD。网址:https://www.w3.org/TR/css-properties-values-api-1/
[CSS-SYNTAX-3]
Tab Atkins Jr.; Simon Sapin. CSS语法模块第3级。2019年7月16日。CR。网址:https://www.w3.org/TR/css-syntax-3/
[CSS-TYPED-OM-1]
Shane Stephens; Tab Atkins Jr.; Naina Raisinghani. CSS类型化对象模型第1级。2018年4月10日。WD。网址:https://www.w3.org/TR/css-typed-om-1/
[CSS-UI-3]
Tantek Çelik; Florian Rivoal. CSS基本用户界面模块第3级(CSS3 UI)。2018年6月21日。REC。网址:https://www.w3.org/TR/css-ui-3/
[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS值和单位模块第4级。2021年10月16日。WD。网址:https://www.w3.org/TR/css-values-4/
[CSS-VARIABLES-1]
Tab Atkins Jr.. CSS自定义属性级联变量模块第1级。2021年11月11日。CR。网址:https://www.w3.org/TR/css-variables-1/
[CSSOM-1]
Daniel Glazman; Emilio Cobos Álvarez. CSS对象模型(CSSOM)。2021年8月26日。WD。网址:https://www.w3.org/TR/cssom-1/
[CSSOM-VIEW-1]
Simon Pieters. CSSOM视图模块。2016年3月17日。WD。网址:https://www.w3.org/TR/cssom-view-1/
[DOM]
Anne van Kesteren. DOM标准。现行标准。网址:https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML标准。现行标准。网址:https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. 基础标准。现行标准。网址:https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. 在RFC中用于指示要求级别的关键字。1997年3月。最佳当前实践。网址:https://datatracker.ietf.org/doc/html/rfc2119
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL标准。现行标准。网址:https://webidl.spec.whatwg.org/

IDL 索引

partial namespace CSS {
    [SameObject] readonly attribute Worklet paintWorklet;
};

[Global=(Worklet,PaintWorklet),Exposed=PaintWorklet]
interface PaintWorkletGlobalScope : WorkletGlobalScope {
    undefined registerPaint(DOMString name, VoidFunction paintCtor);
    readonly attribute unrestricted double devicePixelRatio;
};

dictionary PaintRenderingContext2DSettings {
    boolean alpha = true;
};

[Exposed=PaintWorklet]
interface PaintRenderingContext2D {
};
PaintRenderingContext2D includes CanvasState;
PaintRenderingContext2D includes CanvasTransform;
PaintRenderingContext2D includes CanvasCompositing;
PaintRenderingContext2D includes CanvasImageSmoothing;
PaintRenderingContext2D includes CanvasFillStrokeStyles;
PaintRenderingContext2D includes CanvasShadowStyles;
PaintRenderingContext2D includes CanvasRect;
PaintRenderingContext2D includes CanvasDrawPath;
PaintRenderingContext2D includes CanvasDrawImage;
PaintRenderingContext2D includes CanvasPathDrawingStyles;
PaintRenderingContext2D includes CanvasPath;

[Exposed=PaintWorklet]
interface PaintSize {
    readonly attribute double width;
    readonly attribute double height;
};