CSS 绘画 API 第一级

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

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

FirefoxSafariChrome65+
Opera52+Edge79+
Edge (旧版)IE
Firefox for AndroidiOS SafariChrome for Android65+Android WebView65+Samsung Internet9.0+Opera Mobile47+

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

paintWorkletWorklet全局作用域类型PaintWorkletGlobalScope

paintWorkletWorklet目标类型"paintworklet"

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

PaintWorkletGlobalScopepaintWorklet 的全局执行上下文。

PaintWorklet/devicePixelRatio

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

FirefoxSafariChrome65+
OperaEdge79+
Edge (旧版)IE
Firefox for AndroidiOS SafariChrome for Android65+Android WebView65+Samsung Internet9.0+Opera Mobile

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

PaintWorklet

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

FirefoxSafariChrome65+
OperaEdge79+
Edge (旧版)IE
Firefox for AndroidiOS SafariChrome 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

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

FirefoxSafariChrome65+
OperaEdge79+
Edge (旧版)IE
Firefox for AndroidiOS SafariChrome for Android65+Android WebView65+Samsung Internet9.0+Opera Mobile
当调用registerPaint(name, paintCtor) 方法时,用户代理必须运行以下步骤:
  1. 如果name是空字符串,抛出TypeError并中止所有这些步骤。

  2. paintDefinitionMapPaintWorkletGlobalScope绘画定义映射。

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

  4. inputProperties为空的sequence<DOMString>

  5. inputPropertiesIterablepaintCtorGet(paintCtor, "inputProperties")的结果。

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

  7. 过滤inputProperties,使其只包含支持的CSS属性自定义属性

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

注意:CSS属性列表可能包含简写形式。

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

  1. inputArguments为空的sequence<DOMString>

  2. inputArgumentsIterablepaintCtorGet(paintCtor, "inputArguments")的结果。

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

  4. inputArgumentSyntaxes空的列表

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

    1. 尝试从item获取语法定义。 如果返回失败,抛出 TypeError 并中止所有这些步骤。 否则,将parsedSyntax设置为返回的语法定义

    2. parsedSyntax附加到 inputArgumentSyntaxes

  6. contextOptionsValuepaintCtorGet("contextOptions")的结果。

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

    注意:paintRenderingContext2DSettings.alpha设置为false允许用户代理 除了进行"可见性"优化外,还可以对文本进行抗锯齿处理,例如,当绘画图像不透明时,不会在绘画图像后面绘制图像。

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

  9. prototypepaintCtorGet("prototype")的结果。

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

  11. paintValueprototypeGet("paint")的结果。

  12. paintValue转换为函数回调函数类型。重新抛出转换中的任何异常。

  13. definition为一个新的绘画定义,包括以下内容:

  14. paintDefinitionMap[name]设置为 definition

  15. 排队任务以运行以下步骤:

    1. documentPaintDefinitionMap为关联的文档文档 绘画定义映射

    2. documentDefinition为一个新的文档 绘画定义,包括以下内容:

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

      1. existingDocumentDefinitiondocumentPaintDefinitionMap[name]的get结果。

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

      3. 如果existingDocumentDefinitiondocumentDefinition不相等 (即输入属性输入参数语法PaintRenderingContext2DSettings 对象不同),则:

        documentPaintDefinitionMap[name]设置为 "invalid"

        向调试控制台记录错误,指出相同类已使用不同的inputPropertiesinputArgumentspaintRenderingContext2DSettings注册。

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

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

注意:在未来的规范版本中,作者可以有机会接收不同类型的RenderingContext。 特别是作者可能希望使用WebGL渲染上下文来渲染3D效果。 设置WebGL渲染上下文以将PaintSizeStylePropertyMap 作为输入是很复杂的。

5. 绘画符号

image/paint()

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

FirefoxSafariChrome65+
Opera52+Edge79+
Edge (旧版)IE
Firefox for AndroidiOS SafariChrome 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 的一个子集。 它特别没有实现 CanvasImageDataCanvasUserInterfaceCanvasText, 或者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);
    }
});
当用户代理要为给定的 widthheightpaintRenderingContext2DSettings 创建一个 PaintRenderingContext2D 对象 时,它必须运行以下步骤:
  1. 创建一个新的 PaintRenderingContext2D

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

  3. PaintRenderingContext2Dalpha 标志设置为 paintRenderingContext2DSettingsalpha

  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;
};
当用户代理希望为 <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) { /* paint code here. */ }
    });
    
    <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 成为来自绘画 WorkletPaintWorkletGlobalScope,遵循 § 7.1 全局范围选择中的规则。

    用户代理可以在此时根据需要 创建一个工作全局范围

  11. 运行 调用绘画回调 给定 nameinputArgumentssnappedConcreteObjectSizeworkletGlobalScope,可选地并行运行。

    注意:如果用户代理在 调用绘画回调 时在并行线程上运行,它应选择可用于该线程的绘画工作全局范围。

当用户代理希望根据 nameinputArgumentssnappedConcreteObjectSizeworkletGlobalScope调用绘画回调时,它必须运行以下步骤:
  1. paintDefinitionMap 成为 workletGlobalScope绘画定义 映射。

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

    1. 队列任务运行以下步骤:

      1. documentPaintDefinitionMap 成为相关文档的 文档绘画定义 映射。

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

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

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

    注意: 这处理了可能有一个绘画工作全局范围没有收到 registerPaint(name, paintCtor) 的情况(然而另一个全局范围收到了)。在另一个全局范围上调用的绘画回调可能成功,但当调用 绘制一个绘画图像 时,在后续帧上不会成功。

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

  4. paintClassInstanceMap 成为 workletGlobalScope绘画类实例 映射。

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

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

    2. paintCtor 成为 definition类构造函数

    3. paintInstance 成为通过 构造(paintCtor) 的结果。如果 构造抛出异常,设置 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 实际上被“隔离”。

  9. paintSize 成为一个新的 PaintSize,初始化为 snappedConcreteObjectSize 定义的宽度和高度。

  10. 在此阶段,用户代理可以重用先前调用的图像(如果 paintSizestyleMapinputArguments 与先前调用的内容相同)。如果是这样,则让图像输出为该缓存图像并中止所有这些步骤。

    在下面的示例中,div-1div-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 成为 definitionpaint function

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

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

    注意:用户代理可以在其调试工具中为开发者提供工具,显示绘制类的开销。用户代理在适当情况下也可以显示 "无响应的脚本" 对话框。

  13. 图像输出应从传递给方法的 renderingContext 中生成。

    如果抛出 异常,则将图像输出设为 无效图像

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

7.1. 全局作用域选择

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

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

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;
};