Web 图形渲染中,当多个图层重叠时,需要将它们组合成最终显示的图像。本文基于 W3C Compositing and Blending 规范,系统讲解 Blending 和 Porter-Duff Compositing 两大核心技术。从数学原理到实际应用,深入解析 Web 图形合成的完整体系。
符号与公式约定
理解图形合成的数学公式需要先明确符号约定。本章定义文中使用的核心术语和数学符号,包括颜色表示方式、Alpha 通道处理以及合成过程中的中间结果。
核心术语
Source 是当前要绘制的图层,通常是前景元素。它包含颜色信息和 Alpha 通道。
Backdrop 是 Source 后面的图层内容,是 Source 要与之合成的对象。Backdrop 是之前所有图层合成的累积结果。
Destination 是渲染目标,即画布上的像素缓冲区。合成的最终结果会写入 Destination。
Alpha 通道表示像素的不透明度,取值范围为 [0, 1]。Alpha 为 0 表示完全透明,Alpha 为 1 表示完全不透明。
预乘 Alpha 与非预乘 Alpha
颜色可以用两种形式表示:
非预乘 Alpha (Non-premultiplied Alpha) 表示颜色分量和 Alpha 通道独立存储。例如,半透明红色表示为 (R=1.0, G=0, B=0, α=0.5),颜色值不受 Alpha 影响。
预乘 Alpha (Premultiplied Alpha) 表示颜色分量已乘以 Alpha 值。同样的半透明红色表示为 (r=0.5, g=0, b=0, α=0.5),其中 r = R × α。预乘形式在图形硬件中更高效,简化了合成运算。
两种形式之间的转换:
javascript
// 非预乘转预乘
const cs = Cs * αs;
const cb = Cb * αb;
// 预乘转非预乘(需处理 α = 0 的情况)
const Cs = αs > 0 ? cs / αs : 0;
const Cb = αb > 0 ? cb / αb : 0;
符号定义
本文使用以下符号约定:
| 符号 | 含义 | 取值范围 |
|---|---|---|
Cs |
Source 的非预乘颜色 (R, G, B) | [0, 1] |
Cb |
Backdrop 的非预乘颜色 (R, G, B) | [0, 1] |
Cr |
Blending 后的结果颜色 (R, G, B) | [0, 1] |
Co |
最终输出的非预乘颜色 (R, G, B) | [0, 1] |
cs |
Source 的预乘颜色 (r, g, b) | [0, 1] |
cb |
Backdrop 的预乘颜色 (r, g, b) | [0, 1] |
cr |
Blending 后的预乘颜色 (r, g, b) | [0, 1] |
co |
最终输出的预乘颜色 (r, g, b) | [0, 1] |
αs |
Source 的 Alpha 值 | [0, 1] |
αb |
Backdrop 的 Alpha 值 | [0, 1] |
αo |
输出的 Alpha 值 | [0, 1] |
Fa |
Source 的 Porter-Duff 因子 | [0, 1] |
Fb |
Backdrop 的 Porter-Duff 因子 | [0, 1] |
关键符号说明:
- Cr (Result color) :Blending 阶段计算出的中间结果颜色。它是 Backdrop 颜色
Cb和 Source 颜色Cs通过 Blending 函数混合后的结果,会在 Porter-Duff Compositing 阶段作为新的 Source 颜色使用。 - Fa 和 Fb:Porter-Duff Compositing 的控制因子,决定 Source 和 Backdrop 对最终结果的贡献程度。
颜色值裁剪
合成计算过程中,颜色值可能超出 [0, 1] 范围。需要进行裁剪 (clamp) 处理:
javascript
// 裁剪到有效范围
const clamp = (value) => Math.max(0, Math.min(1, value));
Co = clamp(Co);
αo = clamp(αo);
某些 Blending 模式(如 color-dodge)可能产生大于 1 的值,裁剪确保最终输出的颜色在显示设备的有效范围内。
合成模型
图形合成是一个严格的两阶段流程:先进行 Blending 计算重叠区域的颜色混合,再使用 Alpha 合成公式处理 Alpha 通道的影响。本章介绍通用合成公式及其特例 Simple Alpha Compositing。
通用合成公式
完整的合成流程严格按照以下顺序执行:
ini
输入: Source (Cs, αs), Backdrop (Cb, αb)
↓
[1. Blending]
Cr = (1 - αb) × Cs + αb × B(Cb, Cs)
↓
[2. Alpha Compositing]
co = Fa × (Cr × αs) + Fb × (Cb × αb)
αo = Fa × αs + Fb × αb
↓
输出: Result (Co, αo)
第 1 步:Blending
计算混合后的颜色 Cr,对每个颜色通道(R/G/B)应用 Blending 函数 B(Cb, Cs):
javascript
// 非预乘形式
Cr = (1 - αb) × Cs + αb × B(Cb, Cs);
B(Cb, Cs) 定义了 Backdrop 和 Source 颜色的混合方式。不同的 Blending 模式对应不同的 B 函数实现:
normal:B(Cb, Cs) = Cs(无混合,保持 Source 原色)multiply:B(Cb, Cs) = Cb × Cs(正片叠底)screen:B(Cb, Cs) = Cb + Cs - Cb × Cs(滤色)
αb 控制 Blending 的影响程度:当 Backdrop 透明(αb=0)时保持 Source 原色,当 Backdrop 不透明(αb=1)时完全混合。
第 2 步:Alpha Compositing
将 Cr 作为新的 Source 颜色,使用因子 Fa 和 Fb 控制 Source 和 Backdrop 的贡献程度:
javascript
// 预乘形式
co = Fa × (Cr × αs) + Fb × (Cb × αb);
αo = Fa × αs + Fb × αb;
Fa 和 Fb 是合成因子,通过不同的取值实现不同的合成效果。例如:
Fa = 1, Fb = 1 - αs:Source 覆盖 Backdrop(source-over)Fa = αb, Fb = 0:Source 裁剪到 Backdrop 形状(source-in)Fa = 1 - αb, Fb = 1:Backdrop 覆盖 Source(destination-over)
合并公式
将两个步骤合并,得到完整的合成公式:
javascript
// 完整的合成公式(预乘形式)
co = Fa × B(Cb, Cs) × αs × αb + Fa × (1 - αb) × cs + Fb × cb;
αo = Fa × αs + Fb × αb;
这个公式清楚地展示了三部分的贡献:
Fa × B(Cb, Cs) × αs × αb:Blending 结果对重叠区域的贡献Fa × (1 - αb) × cs:Source 在 Backdrop 透明区域的贡献Fb × cb:Backdrop 根据合成因子的贡献
Simple Alpha Compositing
Simple Alpha Compositing 是通用公式的最常用特例,对应参数:
- Blending 模式:
normal,即B(Cb, Cs) = Cs - 合成因子:
Fa = 1, Fb = 1 - αs
代入通用公式得到:
javascript
// 预乘形式
co = cs + cb × (1 - αs);
αo = αs + αb × (1 - αs);
这就是默认的图层叠加方式(source-over):Source 以其不透明度覆盖在 Backdrop 上方。当 Source 完全不透明(αs = 1)时,Backdrop 完全被遮盖;当 Source 完全透明(αs = 0)时,输出就是 Backdrop。
关键要点:
- Blending 在前,Alpha Compositing 在后,顺序不可颠倒
- Blending 通过函数
B计算颜色,Alpha Compositing 通过因子Fa和Fb控制区域和 Alpha 通道 - Simple Alpha Compositing 是
B=Cs, Fa=1, Fb=1-αs的特例 - 两个阶段通过中间结果
Cr连接
Porter-Duff Compositing Operators
Porter-Duff Compositing Operators 由 Thomas Porter 和 Tom Duff 在 1984 年提出,定义了通过 Alpha 通道控制图层合成区域的代数运算体系。本章介绍 Porter-Duff 操作符的数学原理、13 种标准操作符以及它们与 Compositing Groups 的交互行为。
操作符原理
Porter-Duff 操作符基于 Alpha 通道的线性合成。核心思想是通过两个因子 Fa 和 Fb 控制 Source 和 Backdrop 对最终结果的贡献程度:
javascript
co = Fa × cs + Fb × cb;
αo = Fa × αs + Fb × αb;
Porter-Duff 理论将两张图像的像素空间分为四个逻辑区域:
- Source 独占区域:Source 不透明,Backdrop 透明(αs > 0, αb = 0)
- Backdrop 独占区域:Backdrop 不透明,Source 透明(αs = 0, αb > 0)
- 重叠区域:两者都不透明(αs > 0, αb > 0)
- 空白区域:两者都透明(αs = 0, αb = 0)
为了精确控制这四个区域,Fa 和 Fb 的取值被限制在与 Alpha 值相关的离散集合中:
css
Fa ∈ {0, αb, 1 - αb, 1}
Fb ∈ {0, αs, 1 - αs, 1}
这种限制确保了操作符具有明确的几何意义:
Fa = 0:Source 不贡献Fa = 1:Source 完全贡献Fa = αb:Source 仅在 Backdrop 存在的区域贡献Fa = 1 - αb:Source 仅在 Backdrop 不存在的区域贡献
Fb 的含义与 Fa 对称。
13 种 Porter-Duff 操作符
从数学上看,Fa 和 Fb 的取值集合可以产生 4 × 4 = 16 种组合。W3C 规范定义了其中 13 种具有实际意义的操作符:
| 操作符 | Fa | Fb | 效果描述 |
|---|---|---|---|
clear |
0 | 0 | 清除所有内容 |
copy |
1 | 0 | 仅显示 Source |
destination |
0 | 1 | 仅显示 Backdrop |
source-over |
1 | 1 - αs | Source 覆盖 Backdrop(默认) |
destination-over |
1 - αb | 1 | Backdrop 覆盖 Source |
source-in |
αb | 0 | Source 裁剪到 Backdrop 形状 |
destination-in |
0 | αs | Backdrop 裁剪到 Source 形状 |
source-out |
1 - αb | 0 | Source 在 Backdrop 外的部分 |
destination-out |
0 | 1 - αs | Backdrop 在 Source 外的部分 |
source-atop |
αb | 1 - αs | Source 在 Backdrop 上方(限定在 Backdrop 区域) |
destination-atop |
1 - αb | αs | Backdrop 在 Source 上方(限定在 Source 区域) |
xor |
1 - αb | 1 - αs | 仅显示非重叠区域 |
lighter |
1 | 1 | 相加(需要 clamp) |
未定义的 3 种组合(Fa=αb, Fb=αs、Fa=αb, Fb=1、Fa=1, Fb=αs)缺乏明确的语义或实际应用价值,因此被排除在规范之外。
W3C 选择这 13 种操作符的原则是:
- 完备性:覆盖所有常见的图像合成需求(覆盖、裁剪、遮罩、异或等)
- 对称性 :提供 Source 和 Destination 的对称操作(如
source-in对应destination-in) - 语义清晰:每个操作符都有明确的视觉效果和应用场景
常见应用场景
遮罩效果 :使用 source-in 或 destination-in
javascript
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
// 绘制遮罩形状(圆形)
ctx.arc(100, 100, 50, 0, Math.PI * 2);
ctx.fill();
// 使用 source-in 将图像裁剪到圆形
ctx.globalCompositeOperation = "source-in";
ctx.drawImage(image, 0, 0);
擦除效果 :使用 destination-out
javascript
// 绘制底图
ctx.drawImage(image, 0, 0);
// 使用 destination-out 擦除
ctx.globalCompositeOperation = "destination-out";
ctx.arc(100, 100, 30, 0, Math.PI * 2);
ctx.fill(); // 擦除圆形区域
发光叠加 :使用 lighter
javascript
// 绘制第一个光源
ctx.drawImage(light1, 0, 0);
// 使用 lighter 叠加第二个光源
ctx.globalCompositeOperation = "lighter";
ctx.drawImage(light2, 50, 50);
Blending 混合模式
Blending 控制重叠区域的颜色如何混合。与 Porter-Duff Compositing Operators 通过 Fa 和 Fb 控制区域选择不同,Blending 通过函数 B(Cb, Cs) 改变颜色的计算方式。本章介绍 Blending 函数、与 Alpha Compositing 的结合、可分离和不可分离混合模式,以及 Group isolation 对 Blending 的影响。
Blending 函数
Blending 的核心是 Blending 函数 B(Cb, Cs),它定义了 Backdrop 颜色 Cb 和 Source 颜色 Cs 如何计算出混合后的颜色 Cr:
javascript
// Blending 公式(非预乘形式)
Cr = (1 - αb) × Cs + αb × B(Cb, Cs);
αb 控制 Blending 函数的影响程度:
- 当
αb = 0(Backdrop 透明)时,Cr = Cs(保持 Source 原色) - 当
αb = 1(Backdrop 不透明)时,Cr = B(Cb, Cs)(完全混合)
不同的 Blending 模式对应不同的 B 函数实现。Blending 模式分为两大类:
- 可分离混合模式 (Separable Blend Modes) :
B(Cb, Cs)对 RGB 每个通道独立计算 - 不可分离混合模式 (Non-separable Blend Modes) :
B(Cb, Cs)需要同时考虑所有颜色通道,涉及 RGB 与 HSL 色彩空间转换
Blending 与 Alpha Compositing
在完整的合成流程中,Blending 计算出 Cr 后,需要与 Porter-Duff Compositing 结合。最常见的简化模式是:Blending 之后进行 source-over 合成。
这种模式对应参数:
- Blending 模式:任意
B(Cb, Cs)函数(如 multiply、screen 等) - Alpha Compositing 因子:
Fa = 1, Fb = 1 - αs(source-over)
完整公式为:
javascript
// 第 1 步:Blending
Cr = (1 - αb) × Cs + αb × B(Cb, Cs);
// 第 2 步:source-over Alpha Compositing
co = Cr × αs + Cb × αb × (1 - αs);
αo = αs + αb × (1 - αs);
合并为单一公式:
javascript
// Blending + source-over 合成(预乘形式)
co = B(Cb, Cs) × αs × αb + (1 - αb) × cs + (1 - αs) × cb;
αo = αs + αb × (1 - αs);
这个公式清楚地展示了三部分的贡献:
B(Cb, Cs) × αs × αb:Blending 结果对重叠区域的贡献(1 - αb) × cs:Source 在 Backdrop 透明区域的贡献(1 - αs) × cb:Backdrop 在 Source 透明区域的贡献
这是 Web 平台最常用的合成模式,通过 CSS 的 mix-blend-mode 属性或 Canvas 的 globalCompositeOperation 属性使用。
可分离混合模式
可分离混合模式的 B(Cb, Cs) 函数对每个颜色通道独立应用。
| 混合模式 | B(Cb, Cs) 公式 | 效果描述 | 常见用途 |
|---|---|---|---|
normal |
Cs | 无混合 | 默认 |
multiply |
Cb × Cs | 正片叠底 | 阴影、纹理叠加 |
screen |
Cb + Cs - Cb × Cs | 滤色 | 发光效果 |
overlay |
Cb ≤ 0.5 ? 2×Cb×Cs : 1 - 2×(1-Cb)×(1-Cs) | 叠加 | 图像增强 |
darken |
min(Cb, Cs) | 变暗 | 暗部合成 |
lighten |
max(Cb, Cs) | 变亮 | 亮部合成 |
color-dodge |
Cs ≥ 1 ? 1 : min(1, Cb / (1 - Cs)) | 颜色减淡 | 强光效果 |
color-burn |
Cs ≤ 0 ? 0 : 1 - min(1, (1 - Cb) / Cs) | 颜色加深 | 加深效果 |
hard-light |
Cs ≤ 0.5 ? 2×Cb×Cs : 1 - 2×(1-Cb)×(1-Cs) | 强光 | 高对比度效果 |
soft-light |
复杂公式(详见 W3C 规范) | 柔光 | 柔光效果 |
difference |
|Cb - Cs| | 差值 | 差异检测 |
exclusion |
Cb + Cs - 2×Cb×Cs | 排除 | 柔和差异 |
代码示例:使用 multiply 混合模式
javascript
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
// 绘制 Backdrop(蓝色矩形)
ctx.fillStyle = "blue";
ctx.fillRect(50, 50, 100, 100);
// 使用 multiply 混合模式绘制 Source(红色圆形)
ctx.globalCompositeOperation = "multiply";
ctx.fillStyle = "red";
ctx.arc(100, 100, 50, 0, Math.PI * 2);
ctx.fill();
// 重叠区域:blue × red = 暗紫色
不可分离混合模式
不可分离混合模式需要在 HSL 色彩空间中操作。辅助函数:
javascript
// 计算亮度 (Luminosity)
Lum(C) = 0.3 × C.red + 0.59 × C.green + 0.11 × C.blue;
// 计算饱和度 (Saturation)
Sat(C) = max(C.red, C.green, C.blue) - min(C.red, C.green, C.blue);
// 设置亮度:保持色相和饱和度,修改亮度
SetLum(C, lum);
// 设置饱和度:保持色相和亮度,修改饱和度
SetSat(C, sat);
| 混合模式 | B(Cb, Cs) 公式 | 效果描述 | 常见用途 |
|---|---|---|---|
hue |
SetLum(SetSat(Cs, Sat(Cb)), Lum(Cb)) | 使用 Source 色相 | 色彩调整 |
saturation |
SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb)) | 使用 Source 饱和度 | 饱和度调整 |
color |
SetLum(Cs, Lum(Cb)) | 使用 Source 色相和饱和度 | 着色效果 |
luminosity |
SetLum(Cb, Lum(Cs)) | 使用 Source 亮度 | 亮度调整 |
Compositing Groups
Compositing Groups 影响元素如何与背景交互。理解 Groups 对于掌握复杂的视觉效果至关重要,它决定了子元素能否看到外部背景,以及 Group 如何作为整体与外部合成。
Group Invariance(组不变性)
Group Invariance 是 Compositing Groups 的核心原则:一个 Group 作为整体与外部 Backdrop 进行合成的结果,应该与 Group 内部的具体实现细节无关。
具体来说,Group Invariance 要求:
- Group 内部的元素先相互合成,生成 Group 的最终图像
- Group 作为单个图层与外部 Backdrop 进行合成
- Group 内部元素的顺序、数量、合成方式对外部不可见
这个原则确保了模块化和封装性。例如,一个包含多个子元素的 <div> 可以作为一个整体与页面其他部分进行合成,而不会因为内部结构变化而影响最终效果。
Group Invariance 的数学表达:
javascript
// Group 内部合成
let groupResult = initialBackdrop;
for (const element of groupElements) {
groupResult = composite(groupResult, element);
}
// Group 作为整体与外部合成
const finalResult = composite(externalBackdrop, groupResult);
组不变性的数学基础
Compositing 操作满足结合律,这是组不变性的数学基础:
javascript
// 三个元素 A、B、C
composite(composite(A, B), C) === composite(A, composite(B, C));
// (A + B) + C = A + (B + C)
Simple Alpha Compositing 满足组不变性
对于 Simple Alpha Compositing (source-over),组不变性总是成立,因为其公式是线性的:
javascript
// source-over 是线性操作
co = cs + cb × (1 - αs);
αo = αs + αb × (1 - αs);
这意味着:
- 可以先把 A 和 B 合成,再与 C 合成 →
(A + B) + C - 也可以先把 B 和 C 合成,再与 A 合成 →
A + (B + C) - 两种方式结果相同
因为满足结合律,所以可以安全地将元素分组:先在 Group 内部合成,再与外部合成,不会改变最终结果。
Blending 打破组不变性
某些 Blending 模式会打破组不变性,因为 Blending 函数 B(Cb, Cs) 是非线性的。例如 multiply 模式:
javascript
B(Cb, Cs) = Cb × Cs;
// 非线性导致不满足结合律
B(B(Ca, Cb), Cc) ≠ B(Ca, B(Cb, Cc));
考虑完整的 Blending 公式:
javascript
Cr = (1 - αb) × Cs + αb × B(Cb, Cs);
由于 αb 和 B 的非线性特性,不同的分组方式会导致不同的结果:
javascript
// 情况 1:B 作为 Group 先与 C 合成
// B 看到的 Backdrop 是 C,αb 来自 C
groupResult = blend(B, C);
final = blend(groupResult, A);
// 情况 2:B 直接与外部 A 合成
// B 看到的 Backdrop 是 A,αb 来自 A
final = blend(blend(A, B), C);
// 两种情况结果不同
Isolated Groups 的必要性
为了在使用 Blending 时保持确定性,CSS 中设置 mix-blend-mode 会自动创建新的层叠上下文和 Compositing Group:
css
.element {
mix-blend-mode: multiply;
/* 自动创建 Compositing Group,确保混合顺序的确定性 */
}
通过创建 Compositing Group,系统明确定义:
- 混合顺序:Group 内的元素按文档顺序依次混合
- Backdrop 来源 :Blending 函数
B(Cb, Cs)中的Cb来自 Group 的 initialBackdrop - 隔离范围:如果是 Isolated Group,initialBackdrop 为透明,元素只与 Group 内部混合
实际例子:
html
<div style="background: red;">
<div style="background: green; mix-blend-mode: multiply;">
<div style="background: blue; mix-blend-mode: multiply;"></div>
</div>
</div>
- 绿色层设置了
multiply,创建 Compositing Group - 蓝色层在绿色层的 Group 内,先与绿色混合
- 绿色 Group 的结果再与红色背景混合
- 混合顺序:blue → green → red,确定且不可改变
这就引出了 Isolated Groups 和 Non-isolated Groups 的区别:它们的 initialBackdrop 不同,决定了 Group 内元素能否看到外部背景。
Isolated 和 Non-isolated Groups
Isolated Groups 和 Non-isolated Groups 的核心区别在于 initialBackdrop 的取值:
Isolated Groups(隔离组)
初始 Backdrop 为全透明黑色 rgba(0, 0, 0, 0),Group 内部元素看不到外部背景:
javascript
let groupResult = { r: 0, g: 0, b: 0, α: 0 }; // 透明背景
for (const element of groupElements) {
groupResult = composite(groupResult, element);
}
// Group 作为整体与外部合成
const finalResult = composite(externalBackdrop, groupResult);
Non-isolated Groups(非隔离组)
初始 Backdrop 使用外部 Backdrop,Group 内部元素可以看到外部背景:
javascript
let groupResult = externalBackdrop; // 使用外部背景
for (const element of groupElements) {
groupResult = composite(groupResult, element);
}
const finalResult = groupResult;
创建 Isolated Group 的方式
根据 W3C 规范:
- CSS:所有创建层叠上下文 (stacking context) 的操作都会创建 Isolated Group
- SVG:opacity、filters、3D transforms、blending、masking 会创建 Isolated Group
- 显式创建:使用
isolation: isolate属性
默认情况下,HTML 元素形成 Non-isolated Group。
Isolated vs Non-isolated 对比
假设有一个绿色圆形使用 multiply 混合模式叠加在红色方块和蓝色背景上:
html
<!-- Non-isolated Group(默认) -->
<div style="background: blue; padding: 50px;">
<div style="background: red; width: 100px; height: 100px;"></div>
<div
style="background: green; border-radius: 50%; width: 80px; height: 80px;
margin: -60px 0 0 40px; mix-blend-mode: multiply;"
></div>
</div>
<!-- Isolated Group -->
<div style="background: blue; padding: 50px;">
<div style="isolation: isolate;">
<div style="background: red; width: 100px; height: 100px;"></div>
<div
style="background: green; border-radius: 50%; width: 80px; height: 80px;
margin: -60px 0 0 40px; mix-blend-mode: multiply;"
></div>
</div>
</div>
Non-isolated Group 的合成过程:
javascript
// 初始 Backdrop 是蓝色背景
let result = blue;
// 红色方块与蓝色背景合成
result = composite(result, red); // → 蓝色背景上的红色方块
// 绿色圆形使用 multiply 模式与当前结果合成
// 绿色圆形会与红色方块和蓝色背景都发生混合
result = composite_with_blend(result, green, multiply);
// 重叠红色区域:green × red = 暗红色
// 重叠蓝色区域:green × blue = 暗蓝色
Isolated Group 的合成过程:
javascript
// Group 内部:初始 Backdrop 是透明
let groupResult = transparent;
// 红色方块与透明背景合成
groupResult = composite(groupResult, red); // → 红色方块(无背景)
// 绿色圆形使用 multiply 模式与红色方块合成
// 绿色圆形只能看到红色方块,看不到外部蓝色背景
groupResult = composite_with_blend(groupResult, green, multiply);
// 重叠区域:green × red = 暗红色
// 非重叠区域:保持原色
// Group 结果与蓝色背景合成
result = composite(blue, groupResult);
// 最终:蓝色背景 + 红色方块 + 暗红色重叠区域(绿色圆形不会与蓝色混合)
关键区别:
- Non-isolated:绿色圆形与红色方块和蓝色背景都发生 multiply 混合
- Isolated:绿色圆形只与红色方块发生 multiply 混合,看不到蓝色背景
实际应用
Compositing 和 Blending 技术在 Web 平台的三个主要领域有广泛应用:Canvas 2D API、HTML/CSS、SVG。本章介绍如何在这些平台使用这些技术,以及性能优化建议。
平台能力对比:
- Canvas 2D:同时支持 Porter-Duff 操作符和 Blending 模式
- CSS :仅支持 Blending 模式(通过
mix-blend-mode、background-blend-mode) - SVG :通过滤镜元素支持 Porter-Duff 操作符(
feComposite)和 Blending 模式(feBlend)
重要说明 :在所有 Web 平台(Canvas 2D、CSS、SVG)中,Blending 模式都采用 Blending + source-over 的合成方式,即 Blending 计算后使用 source-over 进行 Alpha Compositing(Fa=1, Fb=1-αs)。
Canvas 2D
Canvas 2D 通过 globalCompositeOperation 属性控制 Compositing 和 Blending。
globalCompositeOperation 属性
该属性接受字符串值,指定合成或混合模式。支持的值包括:
- Porter-Duff 操作符 :
source-over(默认),source-in,source-out,source-atop,destination-over,destination-in,destination-out,destination-atop,lighter,copy,xor,destination - Blending 模式 :
multiply,screen,overlay,darken,lighten,color-dodge,color-burn,hard-light,soft-light,difference,exclusion,hue,saturation,color,luminosity
CSS
CSS 仅支持 Blending 模式,不支持 Porter-Duff 操作符。提供以下属性控制 Blending:
mix-blend-mode
控制元素与其 Backdrop 的混合方式。元素会与下方的所有内容进行混合。
- 语法:
mix-blend-mode: <blend-mode> - 支持的值:
normal(默认),multiply,screen,overlay,darken,lighten,color-dodge,color-burn,hard-light,soft-light,difference,exclusion,hue,saturation,color,luminosity - 自动创建 Compositing Group(非
normal时)
background-blend-mode
控制同一元素的多个背景层之间的混合。多个背景按照从上到下的顺序混合。
- 语法:
background-blend-mode: <blend-mode> - 支持的值:与
mix-blend-mode相同 - 可以指定多个值,对应多个背景层
isolation
显式创建 Isolated Group,隔离子元素的混合效果。
- 语法:
isolation: auto | isolate auto:默认,不创建隔离组isolate:创建 Isolated Group,子元素的混合不会影响外部背景
SVG
SVG 通过滤镜元素实现 Compositing 和 Blending。
feComposite 元素
实现 Porter-Duff Compositing 操作符。
- 属性:
operator指定操作符类型 - 支持的值:
over,in,out,atop,xor,lighter,arithmetic in和in2属性指定 Source 和 Backdroparithmetic模式支持自定义线性组合公式
feBlend 元素
实现 Blending 混合模式。
- 属性:
mode指定混合模式 - 支持的值:
normal,multiply,screen,overlay,darken,lighten,color-dodge,color-burn,hard-light,soft-light,difference,exclusion,hue,saturation,color,luminosity in和in2属性指定要混合的两个输入源
Isolated Group 的性能影响
Isolated Group 需要离屏渲染,带来额外的性能开销:
- 浏览器为 Group 分配离屏缓冲区(offscreen buffer)
- Group 内容先渲染到离屏缓冲区,再合成到主画布
- 增加内存占用和 GPU 计算负担
过多或嵌套的 Isolated Group 会显著影响渲染性能。
Canvas 2D
Canvas 2D 是即时模式(immediate mode)绘图 API,不会自动创建 Isolated Group。如需隔离效果,需手动使用离屏 Canvas。
HTML/CSS
根据 W3C 规范 Section 3.2,HTML/CSS 中所有创建层叠上下文(Stacking Context)的属性都会创建 Isolated Group,包括 opacity、transform、filter、mix-blend-mode、isolation: isolate 等。
SVG
根据 W3C 规范 Section 3.3,SVG 中只有特定操作会创建 Isolated Group:
opacityfilters(滤镜)- 3D transforms(2D transforms 不会创建)
blending(混合模式)masking(遮罩)