background-image: paint() 必须配合 CSS.paintWorklet.addModule() 动态加载同源 JS 模块,且模块中需注册属性、声明 inputProperties 并实现纯函数式绘制逻辑,否则背景为空白。

background-image: paint() 必须配合 addModule() 才生效
直接在 CSS 里写 background-image: paint(gridPattern),但没调用 CSS.paintWorklet.addModule(),背景一定为空白——浏览器不报错,也不渲染,DevTools 的 Rendering 面板里完全看不到绘制痕迹。这是最常踩的坑。
必须用 JS 动态加载一个独立的 worklet 文件,且满足三个硬性条件:
- 文件需同源,MIME 类型为
application/javascript - 不能访问
document、window或任何 DOM API -
addModule()是 Promise,得等它 resolve 后再注入含paint()的 CSS 规则,否则样式被忽略
示例正确加载方式:
if ('paintWorklet' in CSS) {
CSS.paintWorklet.addModule('./grid-painter.js')
.then(() => {
const style = document.createElement('style');
style.textContent = `
.grid-bg { background-image: paint(gridPattern); }
`;
document.head.appendChild(style);
});
}
paint() 函数里只能用 geometry 和 properties 做响应式计算
Grid 背景要随容器尺寸或主题色变化,不能靠 JS 定时重绘,也不能读 clientWidth 或监听 resize——worklet 环境里没有这些。
立即学习“前端免费学习笔记(深入)”;
真正可用的输入只有三个参数:ctx(受限的绘图上下文)、geometry(当前绘制区域宽高,已适配 DPR)、properties(只读 CSS 自定义属性值)。你要做的就是把网格逻辑写成纯函数:
- 用
geometry.width和geometry.height算格子数量和间距,别硬编码20px - 用
properties.get('--grid-size')读取格子边长,前提是注册时已在inputProperties列表里声明过 - 用
properties.get('--grid-color')拿颜色值,返回的是CSSStyleValue,需调用.toString()转字符串再传给ctx.strokeStyle
错误写法:ctx.strokeStyle = 'var(--grid-color)' —— 不会解析变量,直接失效。
registerProperty() 是响应式更新的前提,不是可选项
如果 CSS 中写了 --grid-size: 16px,但在 worklet 注册时没声明 inputProperties: ['--grid-size'],那 properties.get('--grid-size') 永远返回 undefined,网格就画不出来。
注册代码必须包含这两部分:
registerProperty({
name: '--grid-size',
syntax: '<length>',
inherits: false,
initialValue: '16px'
});
registerPaint('gridPattern', class {
static get inputProperties() {
return ['--grid-size', '--grid-color'];
}
paint(ctx, geometry, properties) {
const size = parseFloat(properties.get('--grid-size').value);
const color = properties.get('--grid-color').toString();
// … 绘制逻辑
}
});
注意:syntax 必须匹配实际值类型,写成 '<number>' 却传 16px,解析失败;initialValue 不能缺,否则某些浏览器 fallback 行为不可控。
Grid 绘制必须轻量,避免 fillRect 循环生成密集线
Paint Worklet 运行在独立线程,但每帧绘制耗时超过几毫秒就会卡顿。常见错误是用 for 循环画几百条线:
// ❌ 危险:100x100 网格 = 20000 次 fillRect 调用
for (let x = 0; x < geometry.width; x += size) {
ctx.fillRect(x, 0, 1, geometry.height);
}
for (let y = 0; y < geometry.height; y += size) {
ctx.fillRect(0, y, geometry.width, 1);
}
更优解是用 strokeRect() + setLineDash() 模拟虚线网格,或用单次 fill() 绘制路径再 stroke,大幅减少调用次数。Chrome 115+ 支持 clip(),可先 clip 出格子区域再批量绘制,但兼容性需查实。
真正容易被忽略的点:geometry 是实时尺寸,但 paint() 可能被高频调用(比如滚动时),所有数学运算必须 O(1) 或 O(n) 且 n 极小。别在里头做 Math.sin()、getComputedStyle() 或任何非确定性操作。


















