1. 4.12.5 canvas 元素
        1. 4.12.5.1 二维渲染上下文
          1. 4.12.5.1.1 实现说明
          2. 4.12.5.1.2 画布状态
          3. 4.12.5.1.3 线条样式
          4. 4.12.5.1.4 文本样式
          5. 4.12.5.1.5 构建路径
          6. 4.12.5.1.6 Path2D 对象
          7. 4.12.5.1.7 变换
          8. 4.12.5.1.8 二维渲染上下文的图像源
          9. 4.12.5.1.9 填充和描边样式
          10. 4.12.5.1.10 在位图上绘制矩形
          11. 4.12.5.1.11 在位图上绘制文本
          12. 4.12.5.1.12 在画布上绘制路径
          13. 4.12.5.1.13 绘制焦点环
          14. 4.12.5.1.14 绘制图像
          15. 4.12.5.1.15 像素操作
          16. 4.12.5.1.16 合成
          17. 4.12.5.1.17 图像平滑
          18. 4.12.5.1.18 阴影
          19. 4.12.5.1.19 滤镜
          20. 4.12.5.1.20 使用外部定义的 SVG 滤镜
          21. 4.12.5.1.21 最佳实践
          22. 4.12.5.1.22 示例
        2. 4.12.5.2 ImageBitmap 渲染上下文
          1. 4.12.5.2.1 简介
          2. 4.12.5.2.2 ImageBitmapRenderingContext 接口
        3. 4.12.5.3 OffscreenCanvas 接口
          1. 4.12.5.3.1 离屏二维渲染上下文
        4. 4.12.5.4 将位图序列化到文件
        5. 4.12.5.5 预乘 Alpha 和二维渲染上下文

4.12.5 canvas 元素

元素/画布

所有当前引擎都支持。

Firefox1.5+Safari2+Chrome1+
Opera9+Edge79+
Edge(旧版)12+Internet Explorer9+
Firefox Android?Safari iOS1+Chrome Android?WebView Android37+Samsung Internet?Opera Android10.1+
类别:
流内容.
短语内容.
嵌入内容.
可感知内容.
可以使用此元素的上下文:
在预期 嵌入内容 的位置。
内容模型:
透明,但没有 交互式内容 后代,除了 a 元素、具有 usemap 属性的 img 元素、button 元素、input 元素(其 type 属性处于 复选框单选按钮 状态)、input 元素(它们是 按钮)以及具有 multiple 属性或大于 1 的 显示大小select 元素。
在 text/html 中的标签省略:
两个标签都不能省略。
内容属性:
全局属性
width — 水平尺寸
height — 垂直尺寸
可访问性注意事项:
面向作者.
面向实现者.
DOM 接口:
使用 HTMLCanvasElement

canvas 元素为脚本提供了一个分辨率相关的位图画布,可用于动态渲染图形、游戏图形、艺术作品或其他视觉图像。

当有更合适的元素可用时,作者不应在文档中使用 canvas 元素。例如,使用 canvas 元素渲染页面标题是不合适的:如果标题所需的呈现方式在图形上非常复杂,则应使用适当的元素(通常为 h1)进行标记,然后使用 CSS 和支持技术(如 影子树)进行样式设置。

当作者使用 canvas 元素时,还必须提供内容,以便在呈现给用户时,传达与 canvas 的位图本质上相同的功能或目的。此内容可以放置为 canvas 元素的内容。canvas 元素(如果有)的内容是元素的 回退内容


在交互式视觉媒体中,如果为 canvas 元素启用了 脚本,并且启用了对 canvas 元素的支持,则 canvas 元素 表示 由动态创建的图像(即元素的位图)组成的 嵌入内容

在非交互式静态视觉媒体中,如果 canvas 元素之前已与渲染上下文相关联(例如,如果页面在交互式视觉媒体中查看,现在正在打印,或者在页面布局过程中运行的一些脚本在元素上进行了绘制),则 canvas 元素 表示 具有元素当前位图和大小的 嵌入内容。否则,元素将改为表示其 回退内容

在非视觉媒体中,以及在视觉媒体中,如果为 canvas 元素禁用了 脚本 或禁用了对 canvas 元素的支持,则 canvas 元素 表示回退内容

canvas 元素 表示 嵌入内容 时,用户仍然可以聚焦 canvas 元素的后代(在 回退内容 中)。当元素 获得焦点 时,它成为键盘交互事件的目标(即使元素本身不可见)。这允许作者使交互式画布可通过键盘访问:作者应该在 回退内容 中将交互区域与 可聚焦区域 建立一一映射。(焦点对鼠标交互事件没有影响)。[UIEVENTS]

其最近的 canvas 元素祖先是 正在渲染表示 嵌入内容 的元素是 用作相关画布回退内容的元素


canvas 元素有两个属性用于控制元素位图的大小:widthheight。当指定这些属性时,其值必须是 有效的非负整数width 属性默认为 300,height 属性默认为 150。

设置 widthheight 属性的值时,如果 canvas 元素的 上下文模式 设置为 占位符,则用户代理必须抛出 "InvalidStateError" DOMException 并保持属性值不变。

canvas 元素 表示 嵌入内容 时,其 自然尺寸 等于元素位图的尺寸。

用户代理必须对 canvas 及其渲染上下文的位图使用由每个坐标空间单位一个像素的图像数据组成的正方形像素密度。

canvas 元素可以通过样式表任意调整大小,然后其位图将受 'object-fit' CSS 属性的影响。


context = canvas.getContext(contextId [, options ])

返回一个对象,该对象公开了用于在画布上绘制的 API。 contextId 指定所需的 API:“2d”、“bitmaprenderer”、“webgl”、“webgl2”或“webgpu”。 options 由该 API 处理。

本规范定义了以下“2d”和“bitmaprenderer”上下文。WebGL 规范定义了“webgl”和“webgl2”上下文。WebGPU 定义了“webgpu”上下文。 [WEBGL] [WEBGPU]

如果 contextId 不受支持,或者画布已使用其他上下文类型初始化(例如,在获取“webgl”上下文后尝试获取“2d”上下文),则返回 null。

url = canvas.toDataURL([ type [, quality ] ])

返回画布中图像的 data: URL

如果提供,第一个参数控制要返回的图像类型(例如 PNG 或 JPEG)。默认值为“image/png”;如果给定的类型不受支持,则也使用该类型。如果类型为支持可变质量的图像格式(例如“image/jpeg”),则第二个参数适用,它是一个介于 0.0 到 1.0(含)之间的数字,表示所需的结果图像的质量级别。

当尝试使用除“image/png”以外的类型时,作者可以通过检查返回的字符串是否以“data:image/png,”或“data:image/png;”这两个精确字符串中的一个开头来检查图像是否确实以请求的格式返回。如果是,则图像为 PNG,因此不支持请求的类型。(唯一的例外是,如果画布的高度或宽度均为零,则结果可能只是“data:,”。)

canvas.toBlob(callback [, type [, quality ] ])

创建一个表示包含画布中图像的文件的 Blob 对象,并使用该对象的句柄调用回调函数。

如果提供,第二个参数控制要返回的图像类型(例如 PNG 或 JPEG)。默认值为“image/png”;如果给定的类型不受支持,则也使用该类型。如果类型为支持可变质量的图像格式(例如“image/jpeg”),则第三个参数适用,它是一个介于 0.0 到 1.0(含)之间的数字,表示所需的结果图像的质量级别。

canvas.transferControlToOffscreen()

返回一个新创建的 OffscreenCanvas 对象,该对象使用 canvas 元素作为占位符。一旦 canvas 元素成为 OffscreenCanvas 对象的占位符,其自然大小将无法再更改,并且不能具有渲染上下文。占位符画布的内容在 OffscreenCanvas相关代理事件循环更新渲染 步骤中更新。

4.12.5.1 2D 渲染上下文

CanvasRenderingContext2D

所有当前引擎都支持。

Firefox1.5+Safari2+Chrome1+
Opera9+Edge79+
Edge(旧版)12+Internet Explorer9+
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android10.1+
context = canvas.getContext('2d' [, { [ alpha: true ] [, desynchronized: false ] [, colorSpace: 'srgb'] [, willReadFrequently: false ]} ])

返回一个 CanvasRenderingContext2D 对象,该对象永久绑定到特定的 canvas 元素。

如果 alpha 成员为 false,则上下文将强制始终不透明。

如果 desynchronized 成员为 true,则上下文可能 异步

colorSpace 成员指定渲染上下文的 颜色空间

如果 willReadFrequently 成员为 true,则上下文将被标记为 回读优化

context.canvas

返回 canvas 元素。

attributes = context.getContextAttributes()

返回一个对象,其


PredefinedColorSpace 枚举用于指定画布后备存储的 颜色空间

srgb”值表示 'srgb' 颜色空间。

display-p3”值表示 'display-p3' 颜色空间。

颜色空间之间转换的算法可以在 CSS 颜色预定义颜色空间 部分找到。 [CSSCOLOR]


CanvasFillRule 枚举用于选择用于确定点在路径内还是路径外的 填充规则 算法。

nonzero”值表示非零绕线规则,其中如果从该点绘制的半无限直线穿过形状路径的方向次数等于其穿过路径的反方向次数,则该点被视为在形状外部。

evenodd”值表示奇偶规则,其中如果从该点绘制的半无限直线穿过形状路径的次数为偶数,则该点被视为在形状外部。

如果一个点不在形状外部,则它在形状内部。


ImageSmoothingQuality 枚举用于表达对平滑图像时使用的插值质量的偏好。

low”值表示对低级别的图像插值质量的偏好。低质量图像插值可能比更高的设置更节省计算资源。

medium”值表示对中等级别的图像插值质量的偏好。

high”值表示对高级别的图像插值质量的偏好。高质量图像插值可能比较低的设置更消耗计算资源。

双线性缩放是相对快速、低质量图像平滑算法的一个示例。双三次或 Lanczos 缩放是生成更高质量输出的图像平滑算法的示例。本规范不要求必须使用特定的插值算法。

4.12.5.1.1 实现说明

输出位图 未由用户代理直接显示时,实现可以仅记住已应用于它的绘图操作序列,而不是更新此位图,直到需要位图的实际数据(例如,由于调用了 drawImage()createImageBitmap() 工厂方法)。在许多情况下,这将更节省内存。

canvas 元素的位图是在实践中几乎总是需要的唯一位图。渲染上下文的 输出位图(如果存在)始终只是 canvas 元素位图的别名。

有时需要其他位图,例如,当画布以不同于其 自然大小 的大小绘制时启用快速绘制,或者启用双缓冲以使图形更新(例如页面滚动)可以在执行画布绘制命令的同时并行处理。

4.12.5.1.2 画布状态

实现 CanvasState 接口的对象维护一个绘图状态堆栈。绘图状态 包括

渲染上下文的位图不是绘图状态的一部分,因为它们取决于渲染上下文是否以及如何绑定到 canvas 元素。

实现 CanvasState 混合的对象具有一个 上下文丢失 布尔值,在创建对象时将其初始化为 false。上下文丢失 值在 上下文丢失步骤 中更新。

context.save()

将当前状态推入堆栈。

context.restore()

弹出堆栈顶部的状态,将上下文恢复到该状态。

context.reset()

CanvasRenderingContext2D/reset

Firefox113+Safari不支持Chrome99+
Opera?Edge99+
Edge(旧版)?Internet Explorer
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

OffscreenCanvasRenderingContext2D#canvasrenderingcontext2d.reset

Firefox113+Safari不支持Chrome99+
Opera?Edge99+
Edge(旧版)?Internet Explorer
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

重置渲染上下文,包括后备缓冲区、绘图状态栈、路径和样式。

context.isContextLost()

CanvasRenderingContext2D/isContextLost

仅在一个引擎中支持。

FirefoxSafariChrome99+
Opera?Edge99+
Edge(旧版)?Internet Explorer
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

如果渲染上下文已丢失,则返回 true。上下文丢失可能由于驱动程序崩溃、内存不足等原因导致。在这些情况下,画布会丢失其后备存储,并采取措施将其渲染上下文重置为默认状态

4.12.5.1.3 线样式
context.lineWidth [ = value ]
styles.lineWidth [ = value ]

返回当前线宽。

可以设置,以更改线宽。非零有限值以外的值将被忽略。

context.lineCap [ = value ]
styles.lineCap [ = value ]

返回当前线帽样式。

可以设置,以更改线帽样式。

可能的线帽样式为“butt”、“round”和“square”。其他值将被忽略。

context.lineJoin [ = value ]
styles.lineJoin [ = value ]

返回当前线连接样式。

可以设置,以更改线连接样式。

可能的线连接样式为“bevel”、“round”和“miter”。其他值将被忽略。

context.miterLimit [ = value ]
styles.miterLimit [ = value ]

返回当前斜接限制比率。

可以设置,以更改斜接限制比率。非零有限值以外的值将被忽略。

context.setLineDash(segments)
styles.setLineDash(segments)

设置当前线虚线图案(在描边时使用)。参数是一个距离列表,用于交替显示线段的开和关。

segments = context.getLineDash()
segments = styles.getLineDash()

返回当前线虚线图案的副本。返回的数组将始终具有偶数个条目(即图案已规范化)。

context.lineDashOffset
styles.lineDashOffset

返回相位偏移量(以与线虚线图案相同的单位)。

可以设置,以更改相位偏移量。非有限值将被忽略。

4.12.5.1.4 文本样式
context.font [ = value ]
styles.font [ = value ]

返回当前字体设置。

可以设置,以更改字体。语法与 CSS 'font' 属性相同;无法解析为 CSS 字体值的值将被忽略。

相对关键字和长度相对于canvas元素的字体进行计算。

context.textAlign [ = value ]
styles.textAlign [ = value ]

返回当前文本对齐设置。

可以设置,以更改对齐方式。可能的值及其含义如下所示。其他值将被忽略。默认值为“start”。

context.textBaseline [ = value ]
styles.textBaseline [ = value ]

返回当前基线对齐设置。

可以设置,以更改基线对齐方式。可能的值及其含义如下所示。其他值将被忽略。默认值为“alphabetic”。

context.direction [ = value ]
styles.direction [ = value ]

返回当前方向性。

可以设置,以更改方向性。可能的值及其含义如下所示。其他值将被忽略。默认值为“inherit”。

context.letterSpacing [ = value ]

CanvasRenderingContext2D/letterSpacing

仅在一个引擎中支持。

FirefoxSafariChrome99+
Opera?Edge99+
Edge(旧版)?Internet Explorer
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
styles.letterSpacing [ = value ]

返回文本中字符之间的当前间距。

可以设置,以更改字符之间的间距。无法解析为 CSS <length> 的值将被忽略。默认值为“0px”。

context.fontKerning [ = value ]

CanvasRenderingContext2D/fontKerning

Firefox104+SafariChrome99+
Opera?Edge99+
Edge(旧版)?Internet Explorer
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
styles.fontKerning [ = value ]

返回当前字体间距设置。

可以设置,以更改字体间距。可能的值及其含义如下所示。其他值将被忽略。默认值为“auto”。

context.fontStretch [ = value ]

CanvasRenderingContext2D/fontStretch

仅在一个引擎中支持。

FirefoxSafariChrome99+
Opera?Edge99+
Edge(旧版)?Internet Explorer
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
styles.fontStretch [ = value ]

返回当前字体伸缩设置。

可以设置,以更改字体伸缩。可能的值及其含义如下所示。其他值将被忽略。默认值为“normal”。

context.fontVariantCaps [ = value ]

CanvasRenderingContext2D/fontVariantCaps

仅在一个引擎中支持。

FirefoxSafariChrome99+
Opera?Edge99+
Edge(旧版)?Internet Explorer
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
styles.fontVariantCaps [ = value ]

返回当前字体变体大写设置。

可以设置,以更改字体变体大写。可能的值及其含义如下所示。其他值将被忽略。默认值为“normal”。

context.textRendering [ = value ]

CanvasRenderingContext2D/textRendering

仅在一个引擎中支持。

FirefoxSafariChrome99+
Opera?Edge99+
Edge(旧版)?Internet Explorer
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
styles.textRendering [ = value ]

返回当前文本渲染设置。

可以设置,以更改文本渲染。可能的值及其含义如下所示。其他值将被忽略。默认值为“auto”。

context.wordSpacing [ = value ]

CanvasRenderingContext2D/wordSpacing

仅在一个引擎中支持。

FirefoxSafariChrome99+
Opera?Edge99+
Edge(旧版)?Internet Explorer
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
styles.wordSpacing [ = value ]

返回文本中单词之间的当前间距。

可以设置,以更改单词之间的间距。无法解析为 CSS <length> 的值将被忽略。默认值为“0px”。

textAlign 属性允许的关键字如下

start

与文本的起始边缘对齐(在从左到右的文本中为左侧,在从右到左的文本中为右侧)。

end

与文本的结束边缘对齐(在从左到右的文本中为右侧,在从右到左的文本中为左侧)。

left

左对齐。

right

右对齐。

center

居中对齐。

textBaseline 属性允许的关键字对应于字体中的对齐点

The top of the em square is roughly at the top of the glyphs in a font, the hanging baseline is where some glyphs like आ are anchored, the middle is half-way between the top of the em square and the bottom of the em square, the alphabetic baseline is where characters like Á, ÿ, f, and Ω are anchored, the ideographic-under baseline is where glyphs like 私 and 達 are anchored, and the bottom of the em square is roughly at the bottom of the glyphs in a font. The top and bottom of the bounding box can be far from these baselines, due to glyphs extending far outside the em square.

关键字映射到这些对齐点的方式如下

top
em 方块的顶部
hanging
悬挂基线
middle
em 方块的中间
alphabetic
字母基线
ideographic
表意文字下基线
bottom
em 方块的底部

direction 属性允许的关键字如下

ltr

将输入到文本准备算法中视为从左到右的文本。

rtl

将输入到文本准备算法中视为从右到左的文本。

inherit

默认为canvas元素或Document的方向性(视情况而定)。

fontKerning 属性允许的关键字如下

auto

用户代理自行决定是否应用字距调整。

normal

应用字距调整。

none

不应用字距调整。

fontStretch 属性允许的关键字如下

ultra-condensed

与 CSS 'font-stretch' 'ultra-condensed' 设置相同。

extra-condensed

与 CSS 'font-stretch' 'extra-condensed' 设置相同。

condensed

与 CSS 'font-stretch' 'condensed' 设置相同。

semi-condensed

与 CSS 'font-stretch' 'semi-condensed' 设置相同。

normal

默认设置,其中字形的宽度为 100%。

semi-expanded

与 CSS 'font-stretch' 'semi-expanded' 设置相同。

expanded

与 CSS 'font-stretch' 'expanded' 设置相同。

extra-expanded

与 CSS 'font-stretch' 'extra-expanded' 设置相同。

ultra-expanded

与 CSS 'font-stretch' 'ultra-expanded' 设置相同。

fontVariantCaps 属性允许的关键字如下

normal

下面列出的功能均未启用。

small-caps

与 CSS 'font-variant-caps' 'small-caps' 设置相同。

all-small-caps

与 CSS 'font-variant-caps' 'all-small-caps' 设置相同。

petite-caps

与 CSS 'font-variant-caps' 'petite-caps' 设置相同。

all-petite-caps

与 CSS 'font-variant-caps' 'all-petite-caps' 设置相同。

unicase

与 CSS 'font-variant-caps' 'unicase' 设置相同。

titling-caps

与 CSS 'font-variant-caps' 'titling-caps' 设置相同。

The textRendering 属性允许的关键字如下

auto

SVG text-rendering 属性中的“auto”相同。

optimizeSpeed

SVG text-rendering 属性中的“optimizeSpeed”相同。

optimizeLegibility

SVG text-rendering 属性中的“optimizeLegibility”相同。

geometricPrecision

SVG text-rendering 属性中的“geometricPrecision”相同。

The 文本准备算法 如下所示。它以字符串 textCanvasTextDrawingStyles 对象 target 和可选长度 maxWidth 作为输入。它返回一个字形形状数组,每个形状都位于一个公共坐标空间上,一个 物理对齐,其值为 leftrightcenter 之一,以及一个 内联框。(此算法的大多数调用者都会忽略 物理对齐内联框。)

  1. 如果提供了 maxWidth 但其值小于或等于零或等于 NaN,则返回一个空数组。

  2. text 中的所有 ASCII 空格 替换为 U+0020 空格字符。

  3. fonttarget 的当前字体,由该对象的 font 属性给出。

  4. 应用以下列表中的相应步骤来确定 direction 的值

    如果 target 对象的 direction 属性的值为 "ltr"
    direction 为 'ltr'。
    如果 target 对象的 direction 属性的值为 "rtl"
    direction 为 'rtl'。
    如果 target 对象的 字体样式源对象 是一个元素
    directiontarget 对象的 字体样式源对象方向性
    如果 target 对象的 字体样式源对象 是一个具有非空 文档元素Document
    directiontarget 对象的 字体样式源对象文档元素方向性
    否则
    direction 为 'ltr'。
  5. 形成一个包含单个包含文本 text内联框 的假设无限宽 CSS 行框,其 CSS 属性设置如下

    属性来源
    'direction'direction
    'font'font
    'font-kerning'targetfontKerning
    'font-stretch'targetfontStretch
    'font-variant-caps'targetfontVariantCaps
    'letter-spacing'target字母间距
    SVG text-renderingtargettextRendering
    'white-space''pre'
    'word-spacing'target单词间距

    以及所有其他属性都设置为其初始值。

  6. 如果提供了 maxWidth 并且假设 内联框 在假设 行框 中的宽度大于 maxWidth CSS 像素,则更改 font 以具有更紧凑的字体(如果可用或如果可以通过将水平缩放因子应用于字体来合成一个合理可读的字体)或更小的字体,并返回到上一步。

  7. The 锚点内联框 上的一个点,而 物理对齐leftrightcenter 值之一。这些变量由 textAligntextBaseline 值决定,如下所示

    水平位置

    如果 textAlignleft
    如果 textAlignstartdirection 为 'ltr'
    如果 textAlignenddirection 为 'rtl'
    锚点 的水平位置为 内联框 的左边缘,并令 物理对齐left
    如果 textAlignright
    如果 textAlignenddirection 为 'ltr'
    如果 textAlignstartdirection 为 'rtl'
    锚点 的水平位置为 内联框 的右边缘,并令 物理对齐right
    如果 textAligncenter
    锚点 的水平位置为 内联框 的左边缘和右边缘之间的一半,并令 物理对齐center

    垂直位置

    如果 textBaselinetop
    锚点 的垂直位置为 内联框第一个可用字体 的 em 盒的顶部。
    如果 textBaselinehanging
    锚点 的垂直位置为 内联框第一个可用字体悬挂基线
    如果 textBaselinemiddle
    锚点 的垂直位置为 内联框第一个可用字体 的 em 盒的底部和顶部之间的一半。
    如果 textBaselinealphabetic
    锚点 的垂直位置为 内联框第一个可用字体字母基线
    如果 textBaselineideographic
    锚点 的垂直位置为 内联框第一个可用字体表意文字下基线
    如果textBaselinebottom
    锚点的垂直位置设为第一个可用字体内联框的 em 方框底部。
  8. result 为一个数组,该数组通过从左到右迭代内联框中的每个字形(如果有)来构造,对于每个字形,将字形的形状(在内联框中)添加到数组中,并在使用CSS 像素且原点位于锚点的坐标空间中进行定位。

  9. 返回result物理对齐方式和内联框。

4.12.5.1.5 构建路径

实现CanvasPath接口的对象具有一个路径。一个路径包含零个或多个子路径的列表。每个子路径都由一个或多个点的列表组成,这些点通过直线或曲线线段连接,并带有一个标志,指示子路径是否闭合。闭合子路径是指子路径的最后一个点通过直线与子路径的第一个点连接的子路径。在绘制路径时,仅包含一个点的子路径将被忽略。

路径具有一个需要新子路径标志。当设置此标志时,某些 API 会创建新的子路径而不是扩展上一个子路径。当创建路径时,必须设置其需要新子路径标志。

当创建实现CanvasPath接口的对象时,其路径必须初始化为零个子路径。

context.moveTo(x, y)
path.moveTo(x, y)

使用给定点创建一个新的子路径。

context.closePath()
path.closePath()

将当前子路径标记为闭合,并使用与新闭合子路径的起始点和结束点相同的点开始一个新的子路径。

context.lineTo(x, y)
path.lineTo(x, y)

将给定点添加到当前子路径,并通过直线将其连接到前一个点。

context.quadraticCurveTo(cpx, cpy, x, y)
path.quadraticCurveTo(cpx, cpy, x, y)

将给定点添加到当前子路径,并通过具有给定控制点的二次贝塞尔曲线将其连接到前一个点。

context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
path.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

将给定点添加到当前子路径,并通过具有给定控制点的三次贝塞尔曲线将其连接到前一个点。

context.arcTo(x1, y1, x2, y2, radius)
path.arcTo(x1, y1, x2, y2, radius)

将具有给定控制点和半径的弧添加到当前子路径,并通过直线将其连接到前一个点。

如果给定的半径为负数,则抛出"IndexSizeError"DOMException

context.arc(x, y, radius, startAngle, endAngle [, counterclockwise ])
path.arc(x, y, radius, startAngle, endAngle [, counterclockwise ])

向子路径添加点,以便将由参数描述的圆的圆周描述的弧添加到路径中,从给定的起始角度开始,到给定的结束角度结束,以给定的方向(默认为顺时针方向)进行,并通过直线将其连接到前一个点。

如果给定的半径为负数,则抛出"IndexSizeError"DOMException

context.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle [, counterclockwise])
path.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle [, counterclockwise])

向子路径添加点,以便将由参数描述的椭圆的圆周描述的弧添加到路径中,从给定的起始角度开始,到给定的结束角度结束,以给定的方向(默认为顺时针方向)进行,并通过直线将其连接到前一个点。

如果给定的半径为负数,则抛出"IndexSizeError"DOMException

context.rect(x, y, w, h)
path.rect(x, y, w, h)

向路径添加一个新的闭合子路径,表示给定的矩形。

context.roundRect(x, y, w, h, radii)

CanvasRenderingContext2D/roundRect

所有当前引擎都支持。

Firefox112+Safari16+Chrome99+
Opera?Edge99+
Edge(旧版)?Internet Explorer
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
path.roundRect(x, y, w, h, radii)

向路径添加一个新的闭合子路径,表示给定的圆角矩形。radii 是一个半径列表或一个表示矩形角的单个半径(以像素为单位)。如果提供了一个列表,则这些半径的数量和顺序与 CSS 'border-radius' 属性相同。单个半径的行为与具有单个元素的列表相同。

如果wh 都大于或等于 0,或者都小于 0,则路径将顺时针绘制。否则,将逆时针绘制。

w 为负数时,圆角矩形将水平翻转,这意味着通常应用于左角的半径值将用于右侧,反之亦然。类似地,当h 为负数时,圆角矩形将垂直翻转。

radii 中的值r 为数字时,相应的角将绘制为半径为r 的圆弧。

radii 中的值r 为具有{ x, y } 属性的对象时,相应的角将绘制为椭圆弧,其xy 半径分别等于r.xr.y

当同一边的两个角的radii 之和大于边的长度时,圆角矩形的所有radii 将按长度 / (r1 + r2) 的比例缩放。如果多个边具有此属性,则使用比例因子最小的边的比例因子。这与 CSS 行为一致。

如果radii 是大小不是 1、2、3 或 4 的列表,则抛出RangeError

如果radii 中的值为负数,或者为{ x, y } 对象且其xy 属性为负数,则抛出RangeError

4.12.5.1.6 Path2D 对象

Path2D

所有当前引擎都支持。

Firefox31+Safari8+Chrome36+
Opera?Edge79+
Edge (Legacy)14+Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

Path2D 对象可用于声明路径,然后在实现CanvasDrawPath接口的对象上使用这些路径。除了前面各节中描述的许多 API 之外,Path2D 对象还具有组合路径和向路径添加文本的方法。

path = new Path2D()

创建一个新的空Path2D 对象。

path = new Path2D(path)

pathPath2D 对象时,返回一个副本。

path 为字符串时,创建由参数描述的路径,将其解释为 SVG 路径数据。[SVG]

path.addPath(path [, transform ])

将参数给出的路径添加到路径中。

4.12.5.1.7 变换

实现CanvasTransform接口的对象具有一个当前变换矩阵,以及用于操作它的方法(在本节中描述)。当创建实现CanvasTransform接口的对象时,其变换矩阵必须初始化为单位矩阵。

在创建当前默认路径时,以及在实现CanvasTransform接口的对象上绘制文本、形状和Path2D 对象时,会将当前变换矩阵应用于坐标。

context.scale(x, y)

当前变换矩阵更改为应用具有给定特征的缩放变换。

context.rotate(angle)

当前变换矩阵更改为应用具有给定特征的旋转变换。角度以弧度为单位。

context.translate(x, y)

当前变换矩阵更改为应用具有给定特征的平移变换。

context.transform(a, b, c, d, e, f)

当前变换矩阵更改为应用由参数给出的矩阵,如下所述。

matrix = context.getTransform()

返回当前变换矩阵的副本,作为一个新创建的DOMMatrix 对象。

context.setTransform(a, b, c, d, e, f)

将当前变换矩阵更改为由参数给定的矩阵,如下所述。

context.setTransform(transform)

将当前变换矩阵更改为传递的DOMMatrix2DInit字典所表示的矩阵。

context.resetTransform()

将当前变换矩阵更改为单位矩阵。

参数abcdef有时被称为m11m12m21m22dxdy,或者m11m21m12m22dxdy。尤其需要注意第二个和第三个参数(bc)的顺序,因为它们的顺序在不同的API之间有所不同,并且API有时使用m12/m21表示法,有时使用m21/m12表示法来表示这些位置。

给定一个由transform()setTransform()方法创建的矩阵形式,即:

ace
bdf
001

变换矩阵乘法后的最终变换坐标将为

xnew = a x + c y + e
ynew = b x + d y + f

4.12.5.1.8 2D渲染上下文的图像源

CanvasDrawImageCanvasFillStrokeStyles接口上的一些方法将联合类型CanvasImageSource作为参数。

此联合类型允许将实现以下任何接口的对象用作图像源

尽管没有正式指定为这样,但SVG image元素预计将与img元素几乎相同地实现。也就是说,SVG image元素共享img元素的基本概念和功能。

ImageBitmap接口可以从许多其他表示图像的类型创建,包括ImageData

检查image参数的可使用性,其中imageCanvasImageSource对象,请运行以下步骤

  1. 切换image

    HTMLOrSVGImageElement

    如果image当前请求状态broken,则抛出"InvalidStateError" DOMException

    如果image完全可解码,则返回bad

    如果image自然宽度自然高度(或两者)等于零,则返回bad

    HTMLVideoElement

    如果imagereadyState属性为HAVE_NOTHINGHAVE_METADATA,则返回bad

    HTMLCanvasElement
    OffscreenCanvas

    如果image的水平尺寸或垂直尺寸等于零,则抛出"InvalidStateError" DOMException

    ImageBitmap
    VideoFrame

    如果image[[Detached]]内部槽值设置为true,则抛出"InvalidStateError" DOMException

  2. 返回good

CanvasImageSource对象表示HTMLOrSVGImageElement时,必须使用元素的图像作为源图像。

具体来说,当CanvasImageSource对象表示HTMLOrSVGImageElement中的动画图像时,用户代理必须使用动画的默认图像(格式定义在不支持或禁用动画时要使用的图像),或者,如果没有这样的图像,则在为CanvasRenderingContext2D API渲染图像时使用动画的第一帧。

CanvasImageSource对象表示HTMLVideoElement时,调用带参数的方法时,当前播放位置处的帧必须用作渲染CanvasRenderingContext2D API的图像的源图像,并且源图像的尺寸必须是媒体资源的自然宽度和自然高度(即,在应用任何纵横比校正后)。

CanvasImageSource对象表示HTMLCanvasElement时,必须使用元素的位图作为源图像。

CanvasImageSource对象表示正在渲染的元素并且该元素已调整大小时,必须使用源图像的原始图像数据,而不是渲染后的图像(例如,源元素上的widthheight属性对渲染CanvasRenderingContext2D API时对象如何解释没有影响)。

CanvasImageSource对象表示ImageBitmap时,必须使用对象的位图图像数据作为源图像。

CanvasImageSource对象表示OffscreenCanvas时,必须使用对象的位图作为源图像。

CanvasImageSource对象表示VideoFrame时,必须使用对象的像素数据作为源图像,并且源图像的尺寸必须是对象的[[display width]][[display height]]

如果对象image不是源干净的,则根据image的类型进行切换

HTMLOrSVGImageElement

image当前请求图像数据CORS跨源

HTMLVideoElement

image媒体数据为CORS跨源

HTMLCanvasElement
ImageBitmap
OffscreenCanvas

image的位图的源干净标志为false。

4.12.5.1.9 填充和描边样式
context.fillStyle [ = value ]

返回用于填充形状的当前样式。

可以设置,以更改填充样式

样式可以是包含CSS颜色的字符串,也可以是CanvasGradientCanvasPattern对象。无效值将被忽略。

context.strokeStyle [ = value ]

返回用于描边形状的当前样式。

可以设置,以更改描边样式。

样式可以是包含CSS颜色的字符串,也可以是CanvasGradientCanvasPattern对象。无效值将被忽略。


渐变有三种类型:线性渐变、径向渐变和圆锥渐变,它们由实现不透明CanvasGradient接口的对象表示。

创建渐变后(见下文),将沿其放置渐变停止点以定义颜色如何在渐变中分布。

gradient.addColorStop(offset, color)

在给定偏移量处向渐变添加具有给定颜色的颜色停止点。0.0 是渐变一端的偏移量,1.0 是另一端的偏移量。

如果偏移量超出范围,则抛出"IndexSizeError" DOMException。如果无法解析颜色,则抛出"SyntaxError" DOMException

gradient = context.createLinearGradient(x0, y0, x1, y1)

返回一个CanvasGradient对象,该对象表示一个线性渐变,该渐变沿参数表示的坐标给出的线条绘制。

gradient = context.createRadialGradient(x0, y0, r0, x1, y1, r1)

返回一个CanvasGradient对象,该对象表示一个径向渐变,该渐变沿参数表示的圆形给出的圆锥绘制。

如果任一半径为负,则抛出"IndexSizeError" DOMException异常。

gradient = context.createConicGradient(startAngle, x, y)

返回一个CanvasGradient对象,该对象表示一个圆锥渐变,该渐变沿参数表示的中心周围顺时针旋转绘制。


模式由实现不透明CanvasPattern接口的对象表示。

pattern = context.createPattern(image, repetition)

返回一个CanvasPattern对象,该对象使用给定的图像并在repetition参数给定的方向上重复。

repetition的允许值为repeat(两个方向)、repeat-x(仅水平)、repeat-y(仅垂直)和no-repeat(均不)。如果repetition参数为空,则使用值repeat

如果图像尚未完全解码,则不绘制任何内容。如果图像是一个没有数据的画布,则抛出"InvalidStateError" DOMException

pattern.setTransform(transform)

设置在填充或描边绘制操作期间渲染模式时将使用的变换矩阵。

4.12.5.1.10 将矩形绘制到位图

实现 CanvasRect 接口的对象提供以下方法,用于立即将矩形绘制到位图上。每个方法都接受四个参数;前两个参数给出矩形左上角的 xy 坐标,后两个参数分别给出矩形的宽度 w 和高度 h

context.clearRect(x, y, w, h)

将给定矩形内位图上的所有像素清除为透明黑色

context.fillRect(x, y, w, h)

使用当前填充样式,将给定矩形绘制到位图上。

context.strokeRect(x, y, w, h)

使用当前描边样式,将给定矩形轮廓的边框绘制到位图上。

4.12.5.1.11 将文本绘制到位图

CanvasRenderingContext2D

所有当前引擎都支持。

Firefox1.5+Safari2+Chrome1+
Opera9+Edge79+
Edge(旧版)12+Internet Explorer9+
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android10.1+
context.fillText(text, x, y [, maxWidth ])
context.strokeText(text, x, y [, maxWidth ])

分别填充或描边给定位置的文本。如果提供了最大宽度,则文本将根据需要缩放以适应该宽度。

metrics = context.measureText(text)

返回一个 TextMetrics 对象,其中包含当前字体中给定文本的度量。

metrics.width
metrics.actualBoundingBoxLeft
metrics.actualBoundingBoxRight
metrics.fontBoundingBoxAscent
metrics.fontBoundingBoxDescent
metrics.actualBoundingBoxAscent
metrics.actualBoundingBoxDescent
metrics.emHeightAscent
metrics.emHeightDescent
metrics.hangingBaseline
metrics.alphabeticBaseline
metrics.ideographicBaseline

返回下面描述的测量值。

width 属性

内联框的宽度,以CSS 像素为单位。(文本的提前宽度。)

actualBoundingBoxLeft 属性

textAlign 属性给出的对齐点到给定文本边界矩形的左侧平行于基线的距离,以CSS 像素为单位;正数表示从给定对齐点向左的距离。

此值与下一个值(actualBoundingBoxRight)的总和可能大于内联框width)的宽度,特别是在倾斜字体中,字符会超出其提前宽度。

actualBoundingBoxRight 属性

textAlign 属性给出的对齐点到给定文本边界矩形的右侧平行于基线的距离,以CSS 像素为单位;正数表示从给定对齐点向右的距离。

fontBoundingBoxAscent 属性

textBaseline 属性指示的水平线到第一个可用字体上行度量的距离,以CSS 像素为单位;正数表示从给定基线向上移动的距离。

此值和下一个值在渲染必须具有始终一致的高度(即使渲染的文本发生变化)的背景时很有用。 actualBoundingBoxAscent 属性(及其对应于下降的属性)在围绕特定文本绘制边界框时很有用。

fontBoundingBoxDescent 属性

textBaseline 属性指示的水平线到第一个可用字体下降度量的距离,以CSS 像素为单位;正数表示从给定基线向下移动的距离。

actualBoundingBoxAscent 属性

textBaseline 属性指示的水平线到给定文本边界矩形顶部的距离,以CSS 像素为单位;正数表示从给定基线向上移动的距离。

即使第一个指定的字体涵盖了输入中的所有字符,此数字也会根据输入文本而发生很大变化。例如,从字母基线的小写字母“o”的actualBoundingBoxAscent将小于大写字母“F”的actualBoundingBoxAscent。该值很容易为负数;例如,当给定文本只是一个逗号“,”时,从 em 方框顶部(textBaseline 值“top”)到边界矩形顶部的距离很可能(除非字体非常不寻常)为负数。

actualBoundingBoxDescent 属性

textBaseline 属性指示的水平线到给定文本边界矩形底部的距离,以CSS 像素为单位;正数表示从给定基线向下移动的距离。

emHeightAscent 属性

textBaseline 属性指示的水平线到内联框中 em 方块的最高顶部之间的距离,以CSS 像素为单位;正数表示给定基线位于该 em 方块的顶部下方(因此此值通常为正数)。如果给定基线是该 em 方块的顶部,则为零;如果给定基线是该 em 方块的中间,则为字体大小的一半。

emHeightDescent 属性

textBaseline 属性指示的水平线到内联框中 em 方块的最低底部之间的距离,以CSS 像素为单位;正数表示给定基线位于该 em 方块的底部上方。(如果给定基线是该 em 方块的底部,则为零。)

hangingBaseline 属性

textBaseline 属性指示的水平线到内联框悬挂基线的距离,以CSS 像素为单位;正数表示给定基线位于悬挂基线下方。(如果给定基线是悬挂基线,则为零。)

alphabeticBaseline 属性

textBaseline 属性指示的水平线到内联框字母基线的距离,以CSS 像素为单位;正数表示给定基线位于字母基线下方。(如果给定基线是字母基线,则为零。)

ideographicBaseline 属性

从由textBaseline属性指示的水平线到汉字下划线基线内联框的距离,以CSS像素为单位;正数表示给定基线位于汉字下划线基线下方。(如果给定基线是汉字下划线基线,则为零。)

使用fillText()strokeText()渲染的字形可能会超出由字体大小(em 方块大小)和measureText()返回的宽度(文本宽度)给出的框。如果出现此问题,建议作者使用上面描述的边界框值。

2D 上下文 API 的未来版本可能会提供一种方法,用于将使用 CSS 渲染的文档片段直接渲染到画布上。这将优先于专门的多行布局方式。

4.12.5.1.12 将路径绘制到画布

实现CanvasDrawPath接口的对象具有当前默认路径。只有一个当前默认路径,它不是绘图状态的一部分。当前默认路径是一个路径,如上所述。

context.beginPath()

重置当前默认路径

context.fill([ fillRule ])
context.fill(path [, fillRule ])

使用当前填充样式填充当前默认路径或给定路径的子路径,并遵守给定的填充规则。

context.stroke()
context.stroke(path)

使用当前描边样式描边当前默认路径或给定路径的子路径。

context.clip([ fillRule ])
context.clip(path [, fillRule ])

将裁剪区域进一步约束到当前默认路径或给定路径,使用给定的填充规则来确定哪些点在路径中。

context.isPointInPath(x, y [, fillRule ])
context.isPointInPath(path, x, y [, fillRule ])

如果给定点在当前默认路径或给定路径中,则返回 true,使用给定的填充规则来确定哪些点在路径中。

context.isPointInStroke(x, y)
context.isPointInStroke(path, x, y)

如果给定点将在当前默认路径或给定路径的描边覆盖的区域中,则返回 true,前提是当前描边样式。

画布元素包含几个复选框。与路径相关的命令已突出显示。

<canvas height=400 width=750>
 <label><input type=checkbox id=showA> Show As</label>
 <label><input type=checkbox id=showB> Show Bs</label>
 <!-- ... -->
</canvas>
<script>
 function drawCheckbox(context, element, x, y, paint) {
   context.save();
   context.font = '10px sans-serif';
   context.textAlign = 'left';
   context.textBaseline = 'middle';
   var metrics = context.measureText(element.labels[0].textContent);
   if (paint) {
     context.beginPath();
     context.strokeStyle = 'black';
     context.rect(x-5, y-5, 10, 10);
     context.stroke();
     if (element.checked) {
       context.fillStyle = 'black';
       context.fill();
     }
     context.fillText(element.labels[0].textContent, x+5, y);
   }
   context.beginPath();
   context.rect(x-7, y-7, 12 + metrics.width+2, 14);

   context.drawFocusIfNeeded(element);
   context.restore();
 }
 function drawBase() { /* ... */ }
 function drawAs() { /* ... */ }
 function drawBs() { /* ... */ }
 function redraw() {
   var canvas = document.getElementsByTagName('canvas')[0];
   var context = canvas.getContext('2d');
   context.clearRect(0, 0, canvas.width, canvas.height);
   drawCheckbox(context, document.getElementById('showA'), 20, 40, true);
   drawCheckbox(context, document.getElementById('showB'), 20, 60, true);
   drawBase();
   if (document.getElementById('showA').checked)
     drawAs();
   if (document.getElementById('showB').checked)
     drawBs();
 }
 function processClick(event) {
   var canvas = document.getElementsByTagName('canvas')[0];
   var context = canvas.getContext('2d');
   var x = event.clientX;
   var y = event.clientY;
   var node = event.target;
   while (node) {
     x -= node.offsetLeft - node.scrollLeft;
     y -= node.offsetTop - node.scrollTop;
     node = node.offsetParent;
   }
   drawCheckbox(context, document.getElementById('showA'), 20, 40, false);
   if (context.isPointInPath(x, y))
     document.getElementById('showA').checked = !(document.getElementById('showA').checked);
   drawCheckbox(context, document.getElementById('showB'), 20, 60, false);
   if (context.isPointInPath(x, y))
     document.getElementById('showB').checked = !(document.getElementById('showB').checked);
   redraw();
 }
 document.getElementsByTagName('canvas')[0].addEventListener('focus', redraw, true);
 document.getElementsByTagName('canvas')[0].addEventListener('blur', redraw, true);
 document.getElementsByTagName('canvas')[0].addEventListener('change', redraw, true);
 document.getElementsByTagName('canvas')[0].addEventListener('click', processClick, false);
 redraw();
</script>
4.12.5.1.13 绘制焦点环
context.drawFocusIfNeeded(element)

如果element获得焦点,则围绕当前默认路径绘制焦点环,遵循平台焦点环约定。

context.drawFocusIfNeeded(path, element)

如果element获得焦点,则围绕path绘制焦点环,遵循平台焦点环约定。

4.12.5.1.14 绘制图像

实现CanvasDrawImage接口的对象具有drawImage()方法来绘制图像。

context.drawImage(image, dx, dy)
context.drawImage(image, dx, dy, dw, dh)
context.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)

将给定的图像绘制到画布上。参数解释如下

The sx and sy parameters give
    the x and y coordinates of the source rectangle; the sw and sh arguments give the width and
    height of the source rectangle; the dx and dy give the x and y coordinates of the destination
    rectangle; and the dw and dh arguments give the width and height of the destination
    rectangle.

如果图像尚未完全解码,则不绘制任何内容。如果图像是没有数据的画布,则抛出"InvalidStateError"DOMException

4.12.5.1.15 像素操作
imagedata = new ImageData(sw, sh [, settings])

返回一个具有给定尺寸和settings指示的颜色空间的ImageData对象。返回的对象中的所有像素都是透明黑色

如果宽度或高度参数为零,则抛出"IndexSizeError"DOMException

imagedata = new ImageData(data, sw [, sh [, settings ] ])

使用Uint8ClampedArray参数中提供的数据返回一个ImageData对象,并使用给定的尺寸和settings指示的颜色空间进行解释。

由于数据中的每个像素都由四个数字表示,因此数据的长度需要是给定宽度的四倍的倍数。如果也提供了高度,则长度需要正好是宽度乘以高度乘以 4。

如果给定的数据和尺寸无法一致地解释,或者任一尺寸为零,则抛出"IndexSizeError"DOMException

imagedata = context.createImageData(imagedata)

返回一个与参数具有相同尺寸和颜色空间的ImageData对象。返回的对象中的所有像素都是透明黑色

imagedata = context.createImageData(sw, sh [, settings])

返回一个具有给定尺寸的ImageData对象。返回的对象的颜色空间是context颜色空间,除非由settings覆盖。返回的对象中的所有像素都是透明黑色

如果宽度或高度参数为零,则抛出"IndexSizeError"DOMException

imagedata = context.getImageData(sx, sy, sw, sh [, settings])

返回一个ImageData对象,其中包含位图给定矩形的图像数据。返回的对象的颜色空间是context颜色空间,除非由settings覆盖。

如果宽度或高度参数为零,则抛出"IndexSizeError"DOMException

imagedata.width
imagedata.height

以像素为单位返回ImageData对象中数据的实际尺寸。

imagedata.data

返回包含以 RGBA 顺序排列的数据的一维数组,作为 0 到 255 范围内的整数。

imagedata.colorSpace

ImageData/colorSpace

Firefox不支持Safari15.2+Chrome92+
Opera未知Edge92+
Edge(旧版)?Internet Explorer
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

返回像素的颜色空间。

context.putImageData(imagedata, dx, dy [, dirtyX, dirtyY, dirtyWidth, dirtyHeight ])

将给定ImageData对象中的数据绘制到位图上。如果提供了脏矩形,则仅绘制该矩形中的像素。

对于此方法调用,globalAlphaglobalCompositeOperation属性以及阴影属性将被忽略;画布中的像素将被整体替换,没有任何合成、alpha 混合、阴影等。

如果imagedata对象的data属性值的 [[ViewedArrayBuffer]] 内部槽已分离,则抛出"InvalidStateError"DOMException

在以下示例中,脚本生成一个ImageData对象以便能够在其上进行绘制。

// canvas is a reference to a <canvas> element
var context = canvas.getContext('2d');

// create a blank slate
var data = context.createImageData(canvas.width, canvas.height);

// create some plasma
FillPlasma(data, 'green'); // green plasma

// add a cloud to the plasma
AddCloud(data, data.width/2, data.height/2); // put a cloud in the middle

// paint the plasma+cloud on the canvas
context.putImageData(data, 0, 0);

// support methods
function FillPlasma(data, color) { ... }
function AddCloud(data, x, y) { ... }

以下是如何使用getImageData()putImageData()实现边缘检测滤镜的示例。

<!DOCTYPE HTML>
<html lang="en">
 <head>
  <title>Edge detection demo</title>
  <script>
   var image = new Image();
   function init() {
     image.onload = demo;
     image.src = "image.jpeg";
   }
   function demo() {
     var canvas = document.getElementsByTagName('canvas')[0];
     var context = canvas.getContext('2d');

     // draw the image onto the canvas
     context.drawImage(image, 0, 0);

     // get the image data to manipulate
     var input = context.getImageData(0, 0, canvas.width, canvas.height);

     // get an empty slate to put the data into
     var output = context.createImageData(canvas.width, canvas.height);

     // alias some variables for convenience
     // In this case input.width and input.height
     // match canvas.width and canvas.height
     // but we'll use the former to keep the code generic.
     var w = input.width, h = input.height;
     var inputData = input.data;
     var outputData = output.data;

     // edge detection
     for (var y = 1; y < h-1; y += 1) {
       for (var x = 1; x < w-1; x += 1) {
         for (var c = 0; c < 3; c += 1) {
           var i = (y*w + x)*4 + c;
           outputData[i] = 127 + -inputData[i - w*4 - 4] -   inputData[i - w*4] - inputData[i - w*4 + 4] +
                                 -inputData[i - 4]       + 8*inputData[i]       - inputData[i + 4] +
                                 -inputData[i + w*4 - 4] -   inputData[i + w*4] - inputData[i + w*4 + 4];
         }
         outputData[(y*w + x)*4 + 3] = 255; // alpha
       }
     }

     // put the image data back after manipulation
     context.putImageData(output, 0, 0);
   }
  </script>
 </head>
 <body onload="init()">
  <canvas></canvas>
 </body>
</html>

以下是一个在绘制纯色并使用 getImageData() 读取结果时应用的颜色空间转换示例。

<!DOCTYPE HTML>
<html lang="en">
<title>Color space image data demo</title>

<canvas></canvas>

<script>
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d', {colorSpace:'display-p3'});

// Draw a red rectangle. Note that the hex color notation
// specifies sRGB colors.
context.fillStyle = "#FF0000";
context.fillRect(0, 0, 64, 64);

// Get the image data.
const pixels = context.getImageData(0, 0, 1, 1);

// This will print 'display-p3', reflecting the default behavior
// of returning image data in the canvas's color space.
console.log(pixels.colorSpace);

// This will print the values 234, 51, and 35, reflecting the
// red fill color, converted to 'display-p3'.
console.log(pixels.data[0]);
console.log(pixels.data[1]);
console.log(pixels.data[2]);
</script>
4.12.5.1.16 合成
context.globalAlpha [ = value ]

返回应用于渲染操作的当前全局 alpha值。

可以设置,以更改全局 alpha值。超出 0.0 .. 1.0 范围的值将被忽略。

context.globalCompositeOperation [ = value ]

返回当前合成和混合运算符,取值范围由合成和混合中定义。 [COMPOSITE]

可以设置,以更改当前合成和混合运算符。未知值将被忽略。

4.12.5.1.17 图像平滑
context.imageSmoothingEnabled [ = value ]

返回在放大图像时,图案填充和 drawImage() 方法是否会尝试平滑图像(如果图像像素与显示屏不完全对齐)。

可以设置,以更改是否平滑图像(true)或不平滑(false)。

context.imageSmoothingQuality [ = value ]

返回当前的图像平滑质量首选项。

可以设置,以更改首选的图像平滑质量。可能的值为 "low"、"medium" 和 "high"。未知值将被忽略。

4.12.5.1.18 阴影

实现 CanvasShadowStyles 接口的对象上的所有绘图操作都受四个全局阴影属性的影响。

context.shadowColor [ = value ]

返回当前的阴影颜色。

可以设置,以更改阴影颜色。无法解析为 CSS 颜色的值将被忽略。

context.shadowOffsetX [ = value ]
context.shadowOffsetY [ = value ]

返回当前的阴影偏移量。

可以设置,以更改阴影偏移量。不是有限数字的值将被忽略。

context.shadowBlur [ = value ]

返回应用于阴影的当前模糊级别。

可以设置,以更改模糊级别。不是大于或等于零的有限数字的值将被忽略。

如果当前合成和混合运算符为 "copy",则阴影实际上不会渲染(因为形状将覆盖阴影)。

4.12.5.1.19 过滤器

实现 CanvasFilters 接口的对象上的所有绘图操作都受全局 filter 属性的影响。

context.filter [ = value ]

返回当前过滤器。

可以设置,以更改过滤器。值可以是字符串 "none" 或可解析为 <filter-value-list> 的字符串。其他值将被忽略。

虽然 context.filter = "none" 将禁用上下文的过滤器,但 context.filter = ""context.filter = nullcontext.filter = undefined 都被视为无法解析的输入,并且当前过滤器的值保持不变。

当前过滤器值中使用的坐标被解释为一个像素等效于一个 SVG 用户空间单位和一个画布坐标空间单位。过滤器坐标不受 当前变换矩阵 的影响。当前变换矩阵仅影响过滤器的输入。过滤器在输出位图的坐标空间中应用。

4.12.5.1.20 使用外部定义的 SVG 过滤器

由于绘图使用过滤器值 "none" 执行,直到外部定义的过滤器完成加载,因此作者可能希望在继续执行绘图操作之前确定此类过滤器是否已完成加载。实现此目的的一种方法是在同一页面中的其他位置加载外部定义的过滤器,并在某些发送 load 事件的元素中(例如,SVG use 元素),并等待分派 load 事件。

4.12.5.1.21 最佳实践

当画布具有交互性时,作者应在元素的回退内容中包含与画布每个可聚焦部分对应的可聚焦元素,如上例所示。

渲染焦点环时,为确保焦点环的外观与原生焦点环一致,作者应使用 drawFocusIfNeeded() 方法,并将其传递给正在为其绘制环的元素。此方法仅在元素获得焦点时绘制焦点环,因此可以在每次绘制元素时简单地调用它,而无需事先检查元素是否已获得焦点。

作者应避免使用 canvas 元素实现文本编辑控件。这样做有许多缺点

这是一项巨大的工作量,强烈建议作者避免执行任何此类操作,而是使用 input 元素、textarea 元素或 contenteditable 属性。

4.12.5.1.22 示例

以下是一个使用画布绘制 漂亮的辉光线的脚本示例。

<canvas width="800" height="450"></canvas>
<script>

 var context = document.getElementsByTagName('canvas')[0].getContext('2d');

 var lastX = context.canvas.width * Math.random();
 var lastY = context.canvas.height * Math.random();
 var hue = 0;
 function line() {
   context.save();
   context.translate(context.canvas.width/2, context.canvas.height/2);
   context.scale(0.9, 0.9);
   context.translate(-context.canvas.width/2, -context.canvas.height/2);
   context.beginPath();
   context.lineWidth = 5 + Math.random() * 10;
   context.moveTo(lastX, lastY);
   lastX = context.canvas.width * Math.random();
   lastY = context.canvas.height * Math.random();
   context.bezierCurveTo(context.canvas.width * Math.random(),
                         context.canvas.height * Math.random(),
                         context.canvas.width * Math.random(),
                         context.canvas.height * Math.random(),
                         lastX, lastY);

   hue = hue + 10 * Math.random();
   context.strokeStyle = 'hsl(' + hue + ', 50%, 50%)';
   context.shadowColor = 'white';
   context.shadowBlur = 10;
   context.stroke();
   context.restore();
 }
 setInterval(line, 50);

 function blank() {
   context.fillStyle = 'rgba(0,0,0,0.1)';
   context.fillRect(0, 0, context.canvas.width, context.canvas.height);
 }
 setInterval(blank, 40);

</script>

canvas 的 2D 渲染上下文通常用于基于精灵的游戏。以下示例演示了这一点

此示例的源代码如下

<!DOCTYPE HTML>
<html lang="en">
<meta charset="utf-8">
<title>Blue Robot Demo</title>
<style>
  html { overflow: hidden; min-height: 200px; min-width: 380px; }
  body { height: 200px; position: relative; margin: 8px; }
  .buttons { position: absolute; bottom: 0px; left: 0px; margin: 4px; }
</style>
<canvas width="380" height="200"></canvas>
<script>
 var Landscape = function (context, width, height) {
   this.offset = 0;
   this.width = width;
   this.advance = function (dx) {
     this.offset += dx;
   };
   this.horizon = height * 0.7;
   // This creates the sky gradient (from a darker blue to white at the bottom)
   this.sky = context.createLinearGradient(0, 0, 0, this.horizon);
   this.sky.addColorStop(0.0, 'rgb(55,121,179)');
   this.sky.addColorStop(0.7, 'rgb(121,194,245)');
   this.sky.addColorStop(1.0, 'rgb(164,200,214)');
   // this creates the grass gradient (from a darker green to a lighter green)
   this.earth = context.createLinearGradient(0, this.horizon, 0, height);
   this.earth.addColorStop(0.0, 'rgb(81,140,20)');
   this.earth.addColorStop(1.0, 'rgb(123,177,57)');
   this.paintBackground = function (context, width, height) {
     // first, paint the sky and grass rectangles
     context.fillStyle = this.sky;
     context.fillRect(0, 0, width, this.horizon);
     context.fillStyle = this.earth;
     context.fillRect(0, this.horizon, width, height-this.horizon);
     // then, draw the cloudy banner
     // we make it cloudy by having the draw text off the top of the
     // canvas, and just having the blurred shadow shown on the canvas
     context.save();
     context.translate(width-((this.offset+(this.width*3.2)) % (this.width*4.0))+0, 0);
     context.shadowColor = 'white';
     context.shadowOffsetY = 30+this.horizon/3; // offset down on canvas
     context.shadowBlur = '5';
     context.fillStyle = 'white';
     context.textAlign = 'left';
     context.textBaseline = 'top';
     context.font = '20px sans-serif';
     context.fillText('WHATWG ROCKS', 10, -30); // text up above canvas
     context.restore();
     // then, draw the background tree
     context.save();
     context.translate(width-((this.offset+(this.width*0.2)) % (this.width*1.5))+30, 0);
     context.beginPath();
     context.fillStyle = 'rgb(143,89,2)';
     context.lineStyle = 'rgb(10,10,10)';
     context.lineWidth = 2;
     context.rect(0, this.horizon+5, 10, -50); // trunk
     context.fill();
     context.stroke();
     context.beginPath();
     context.fillStyle = 'rgb(78,154,6)';
     context.arc(5, this.horizon-60, 30, 0, Math.PI*2); // leaves
     context.fill();
     context.stroke();
     context.restore();
   };
   this.paintForeground = function (context, width, height) {
     // draw the box that goes in front
     context.save();
     context.translate(width-((this.offset+(this.width*0.7)) % (this.width*1.1))+0, 0);
     context.beginPath();
     context.rect(0, this.horizon - 5, 25, 25);
     context.fillStyle = 'rgb(220,154,94)';
     context.lineStyle = 'rgb(10,10,10)';
     context.lineWidth = 2;
     context.fill();
     context.stroke();
     context.restore();
   };
 };
</script>
<script>
 var BlueRobot = function () {
   this.sprites = new Image();
   this.sprites.src = 'blue-robot.png'; // this sprite sheet has 8 cells
   this.targetMode = 'idle';
   this.walk = function () {
     this.targetMode = 'walk';
   };
   this.stop = function () {
     this.targetMode = 'idle';
   };
   this.frameIndex = {
     'idle': [0], // first cell is the idle frame
     'walk': [1,2,3,4,5,6], // the walking animation is cells 1-6
     'stop': [7], // last cell is the stopping animation
   };
   this.mode = 'idle';
   this.frame = 0; // index into frameIndex
   this.tick = function () {
     // this advances the frame and the robot
     // the return value is how many pixels the robot has moved
     this.frame += 1;
     if (this.frame >= this.frameIndex[this.mode].length) {
       // we've reached the end of this animation cycle
       this.frame = 0;
       if (this.mode != this.targetMode) {
         // switch to next cycle
         if (this.mode == 'walk') {
           // we need to stop walking before we decide what to do next
           this.mode = 'stop';
         } else if (this.mode == 'stop') {
           if (this.targetMode == 'walk')
             this.mode = 'walk';
           else
             this.mode = 'idle';
         } else if (this.mode == 'idle') {
           if (this.targetMode == 'walk')
             this.mode = 'walk';
         }
       }
     }
     if (this.mode == 'walk')
       return 8;
     return 0;
   },
   this.paint = function (context, x, y) {
     if (!this.sprites.complete) return;
     // draw the right frame out of the sprite sheet onto the canvas
     // we assume each frame is as high as the sprite sheet
     // the x,y coordinates give the position of the bottom center of the sprite
     context.drawImage(this.sprites,
                       this.frameIndex[this.mode][this.frame] * this.sprites.height, 0, this.sprites.height, this.sprites.height,
                       x-this.sprites.height/2, y-this.sprites.height, this.sprites.height, this.sprites.height);
   };
 };
</script>
<script>
 var canvas = document.getElementsByTagName('canvas')[0];
 var context = canvas.getContext('2d');
 var landscape = new Landscape(context, canvas.width, canvas.height);
 var blueRobot = new BlueRobot();
 // paint when the browser wants us to, using requestAnimationFrame()
 function paint() {
   context.clearRect(0, 0, canvas.width, canvas.height);
   landscape.paintBackground(context, canvas.width, canvas.height);
   blueRobot.paint(context, canvas.width/2, landscape.horizon*1.1);
   landscape.paintForeground(context, canvas.width, canvas.height);
   requestAnimationFrame(paint);
 }
 paint();
 // but tick every 100ms, so that we don't slow down when we don't paint
 setInterval(function () {
   var dx = blueRobot.tick();
   landscape.advance(dx);
 }, 100);
</script>
<p class="buttons">
 <input type=button value="Walk" onclick="blueRobot.walk()">
 <input type=button value="Stop" onclick="blueRobot.stop()">
<footer>
 <small> Blue Robot Player Sprite by <a href="https://johncolburn.deviantart.com/">JohnColburn</a>.
 Licensed under the terms of the Creative Commons Attribution Share-Alike 3.0 Unported license.</small>
 <small> This work is itself licensed under a <a rel="license" href="https://creativecommons.org/licenses/by-sa/3.0/">Creative
 Commons Attribution-ShareAlike 3.0 Unported License</a>.</small>
</footer>
4.12.5.2 ImageBitmap 渲染上下文
4.12.5.2.1 简介

ImageBitmapRenderingContext 是一个面向性能的接口,它提供了一种低开销的方法来显示 ImageBitmap 对象的内容。它使用传输语义来减少总体内存消耗。与 CanvasRenderingContext2DdrawImage() 方法不同,它还通过避免中间合成来简化性能。

例如,使用 img 元素作为中间元素将图像资源放入画布中,会导致内存中同时存在图像的两个副本:img 元素的副本和画布的后备存储中的副本。当处理非常大的图像时,此内存成本可能过高。这可以通过使用 ImageBitmapRenderingContext 来避免。

使用 ImageBitmapRenderingContext,以下是如何以内存和 CPU 高效的方式将图像转码为 JPEG 格式

createImageBitmap(inputImageBlob).then(image => {
  const canvas = document.createElement('canvas');
  const context = canvas.getContext('bitmaprenderer');
  context.transferFromImageBitmap(image);

  canvas.toBlob(outputJPEGBlob => {
    // Do something with outputJPEGBlob.
  }, 'image/jpeg');
});
4.12.5.2.2 ImageBitmapRenderingContext 接口

ImageBitmapRenderingContext

所有当前引擎都支持。

Firefox46+Safari11.1+Chrome66+
Opera?Edge79+
Edge(旧版)?Internet Explorer
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
context = canvas.getContext('bitmaprenderer' [, { [ alpha: false ] } ])

返回一个 ImageBitmapRenderingContext 对象,该对象永久绑定到特定的 canvas 元素。

如果提供了 alpha 设置并将其设置为 false,则强制画布始终不透明。

context.canvas

返回 canvas 元素,该上下文绑定到此元素。

context.transferFromImageBitmap(imageBitmap)

将底层 位图数据imageBitmap 传输到 context,并且位图成为 context 所绑定的 canvas 元素的内容。

context.transferFromImageBitmap(null)

context 所绑定的 canvas 元素的内容替换为一个 透明黑色 位图,其大小对应于 widthheight 内容属性。

4.12.5.3 OffscreenCanvas 接口

OffscreenCanvas

所有当前引擎都支持。

Firefox105+Safari16.4+Chrome69+
Opera?Edge79+
Edge(旧版)?Internet Explorer
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

OffscreenCanvas 是一个 EventTarget,因此 OffscreenCanvasRenderingContext2D 和 WebGL 都可以向其触发事件。OffscreenCanvasRenderingContext2D 可以触发 contextlostcontextrestored,而 WebGL 可以触发 webglcontextlostwebglcontextrestored[WEBGL]

OffscreenCanvas 对象用于创建渲染上下文,类似于 HTMLCanvasElement,但与 DOM 无连接。这使得在 worker 中使用 canvas 渲染上下文成为可能。

OffscreenCanvas 对象可能持有对 占位符 canvas 元素 的弱引用,该元素通常位于 DOM 中,其嵌入内容由 OffscreenCanvas 对象提供。OffscreenCanvas 对象的位图作为 OffscreenCanvas相关代理事件循环更新渲染 步骤的一部分推送到 占位符 canvas 元素

offscreenCanvas = new OffscreenCanvas(width, height)

返回一个新的 OffscreenCanvas 对象,该对象未链接到 占位符 canvas 元素,其位图的大小由 widthheight 参数确定。

context = offscreenCanvas.getContext(contextId [, options ])

返回一个对象,该对象公开了用于在 OffscreenCanvas 对象上绘图的 API。 contextId 指定所需的 API:“2d”、“bitmaprenderer”、“webgl”、“webgl2” 或“webgpu”。 options 由该 API 处理。

本规范定义了下面的“2d”上下文,它类似于但不同于从 canvas 元素创建的“2d”上下文。WebGL 规范定义了“webgl”和“webgl2”上下文。 WebGPU 定义了“webgpu”上下文。 [WEBGL] [WEBGPU]

如果 canvas 已使用其他上下文类型初始化,则返回 null(例如,在获取“webgl”上下文后尝试获取“2d”上下文)。

offscreenCanvas.width [ = value ]
offscreenCanvas.height [ = value ]

这些属性返回 OffscreenCanvas 对象的 位图 的尺寸。

可以设置它们,以使用具有指定尺寸的新 透明黑色 位图替换 位图(有效地调整其大小)。

如果尺寸已更改的 OffscreenCanvas 对象具有 占位符 canvas 元素,则 占位符 canvas 元素自然大小 仅会在 OffscreenCanvas相关代理事件循环更新渲染 步骤期间更新。

promise = offscreenCanvas.convertToBlob([options])

返回一个 promise,该 promise 将以一个新的 Blob 对象为结果,该对象表示包含 OffscreenCanvas 对象中图像的文件。

如果提供,则参数是一个字典,用于控制要创建的图像文件的编码选项。type 字段指定文件格式,其默认值为“image/png”;如果请求的类型不受支持,则也会使用该类型。如果图像格式支持可变质量(例如“image/jpeg”),则 quality 字段是一个介于 0.0 到 1.0(含)之间的数字,表示所需的结果图像的质量级别。

canvas.transferToImageBitmap()

返回一个新创建的 ImageBitmap 对象,其中包含 OffscreenCanvas 对象中的图像。OffscreenCanvas 对象中的图像将替换为新的空白图像。

以下是所有实现 OffscreenCanvas 接口的对象作为 事件处理程序(及其相应的 事件处理程序事件类型)支持的 事件处理程序 IDL 属性

事件处理程序 事件处理程序事件类型
oncontextlost contextlost
oncontextrestored contextrestored
4.12.5.3.1 离屏 2D 渲染上下文

OffscreenCanvasRenderingContext2D

所有当前引擎都支持。

Firefox105+Safari16.4+Chrome69+
Opera?Edge79+
Edge(旧版)?Internet Explorer
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

OffscreenCanvasRenderingContext2D 对象是用于绘制到 OffscreenCanvas 对象的 位图 的渲染上下文。它类似于 CanvasRenderingContext2D 对象,但存在以下差异

OffscreenCanvasRenderingContext2D 对象有一个 位图,该位图在对象创建时初始化。

位图 具有一个 origin-clean 标志,该标志可以设置为 true 或 false。最初,当创建这些位图之一时,其 origin-clean 标志必须设置为 true。

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

OffscreenCanvasRenderingContext2D 对象还有一个类型为 PredefinedColorSpace颜色空间 设置。上下文的 位图 的颜色空间设置为上下文的 颜色空间

OffscreenCanvasRenderingContext2D 对象有一个 关联的 OffscreenCanvas 对象,它是创建 OffscreenCanvasRenderingContext2D 对象的 OffscreenCanvas 对象。

offscreenCanvas = offscreenCanvasRenderingContext2D.canvas

返回 关联的 OffscreenCanvas 对象

4.12.5.4 将位图序列化到文件
4.12.5.5 预乘 alpha 和 2D 渲染上下文

预乘 alpha 指的是表示图像中透明度的一种方式,另一种方式是非预乘 alpha。

在非预乘 alpha 下,像素的红色、绿色和蓝色通道表示该像素的颜色,其 alpha 通道表示该像素的不透明度。

然而,在预乘 alpha 下,像素的红色、绿色和蓝色通道表示像素添加到图像的颜色量,其 alpha 通道表示像素遮挡其后方内容的程度。

例如,假设颜色通道范围从 0(关闭)到 255(全强度),则以下示例颜色以以下方式表示

CSS 颜色表示预乘表示非预乘表示颜色描述在其他内容上方混合的颜色图像
rgba(255, 127, 0, 1)255, 127, 0, 255 255, 127, 0, 255 完全不透明的橙色An opaque orange circle sits atop a background
rgba(255, 255, 0, 0.5)127, 127, 0, 127 255, 255, 0, 127 半透明的黄色A yellow circle, halfway transparent, sits atop a background
不可表示255, 127, 0, 127 不可表示加性半透明橙色An orange circle somewhat brightens the background that it sits atop
不可表示255, 127, 0, 0 不可表示加性完全透明橙色An orange circle completely brightens the background that it sits atop
rgba(255, 127, 0, 0)0, 0, 0, 0 255, 127, 0, 0 完全透明(“不可见”)橙色An empty background with nothing atop it
rgba(0, 127, 255, 0)0, 0, 0, 0 255, 127, 0, 0 完全透明(“不可见”)青绿色An empty background with nothing atop it

将颜色值从非预乘表示转换为预乘表示 涉及将颜色的红色、绿色和蓝色通道乘以其 alpha 通道(重新映射 alpha 通道的范围,使得“完全透明”为 0,“完全不透明”为 1)。

将颜色值从预乘表示转换为非预乘表示 涉及逆运算:将颜色的红色、绿色和蓝色通道除以其 alpha 通道。

由于某些颜色只能在预乘 alpha 下表示(例如,加性颜色),而其他颜色只能在非预乘 alpha 下表示(例如,“不可见”颜色,即使没有不透明度也保留某些红色、绿色和蓝色值);并且 8 位整数上的除法和乘法(canvas 的颜色当前存储方式)会导致精度损失,因此在非完全不透明的颜色之间转换预乘和非预乘 alpha 是一个有损操作。

CanvasRenderingContext2D输出位图OffscreenCanvasRenderingContext2D位图 必须使用预乘 alpha 来表示透明颜色。

canvas 位图使用预乘 alpha 表示颜色非常重要,因为它会影响可表示颜色的范围。虽然由于 CSS 颜色是非预乘的且无法表示加性颜色,因此目前无法直接将加性颜色绘制到画布上,但仍然可以通过例如将加性颜色绘制到 WebGL 画布上,然后通过 drawImage() 将该 WebGL 画布绘制到 2D 画布上来实现。