需求背景
当渲染大量卡片时,需要从卡片中的图片提取主色调,并据此计算合适的文本颜色。这一过程涉及大量计算,容易造成主线程阻塞,影响页面流畅度,尤其在处理多张图片或高分辨率图片时,存在性能问题。
优化方案
为了提升计算效率,采用 Web Worker 将颜色计算逻辑移至独立线程,利用浏览器的多线程能力,实现主色调提取与文本颜色计算的异步处理。从而避免主线程阻塞,提升页面性能。
主要逻辑
- 使用web worker进行图片取色&文本色计算
- 共用一个worker
- 抽离第三方库的代码到worker中
首先讲一下为什么需要将第三方库的代码进行抽离,例如取图片主色调的第三方库 color-thief

这段代码不能在worker中执行,原因如下:
Worker 不能访问 DOM Web Worker 运行在独立线程中,无法访问 document
、window
、canvas
等 DOM API,因此 document.createElement('canvas')
无法在 Worker 中执行。
所以我们不能直接在worker中操作dom,如果要在Worker 中处理图片颜色数据,可以使用 OffscreenCanvas,它允许在 Web Worker 线程中创建和操作 Canvas。那么就有两种方案:
方案 | 方案 1:Worker 直接 fetch 加载图片 | 方案 2:主线程加载图片,传数据给 Worker |
---|---|---|
加载方式 | Worker 内部 fetch(src) 下载图片 | 主线程 new Image().src = src |
缓存机制 | 无法使用浏览器 Disk Cache,每次都可能重新下载 | 能利用浏览器缓存,避免重复下载 |
CORS 影响 | 可能遇到 CORS 限制,需要手动处理跨域 | 主线程加载 crossOrigin="anonymous" 解决 CORS 问题 |
图片解码 | 需要 fetch → Blob → createImageBitmap 额外转换 | 直接 Image 解码,更高效 |
Worker 计算负担 | Worker 需 下载 + 解码 + 颜色计算,任务重 | Worker 仅计算颜色,更轻量 |
主线程占用 | 低(Worker 负责所有操作) | 需要主线程加载 Image 和 Canvas |
性能 | 解码 & 计算全在 Worker,但下载可能慢 | 加载快、缓存友好,Worker 专注计算,整体更快 |
ini
// 方案一worker.js
self.onmessage = async (e) => {
const imageUrl = e.data;
const res = await fetch(imageUrl); // Worker 自己下载
const blob = await res.blob();
const bitmap = await createImageBitmap(blob); // 解码为图像
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
const ctx = canvas.getContext('2d');
ctx.drawImage(bitmap, 0, 0);
const imageData = ctx.getImageData(0, 0, bitmap.width, bitmap.height);
// 👉 在这里做颜色计算
self.postMessage({ message: '颜色计算完了' });
};
因为我们最好是能够利用浏览器的缓存,而且需要避免跨域,最好采用方案二去实现:
在主线程中,我们加载图片,获取图片数据,通过postMessage传递给worker
ini
const img = new Image();
img.src = src;
img.crossOrigin = 'anonymous';
img.onload = () => {
const canvas = new OffscreenCanvas(img.width, img.height);
const ctx = canvas.getContext('2d')!;
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, img.width, img.height);
const buffer = imageData.data.buffer;
this.instance.postMessage(
{
key: src,
buffer,
src,
type: COLOR_TYPE.BOTH,
width: imageData.width,
height: imageData.height,
},
[buffer],
);
这里为什么要通过第二个参数传递buffer数据呢?
postMessage(aMessage, transferList)
是主线程与 Web Worker 之间通信的方法:
aMessage
是要发送的数据,默认通过结构化拷贝(深拷贝) 的方式传递,发送的是副本,发送后双方可以独立修改;transferList
是一个可选的"可转移对象"数组 (如ArrayBuffer
、MessagePort
、ImageBitmap
等),用于转移对象的所有权;被转移的对象不会被复制,而是直接从主线程"搬"到 Worker,发送后原上下文将无法再使用该对象,性能更高效;
在worker中拿到图片数据,对图片进行图片提取主色调,并据此计算合适的文本颜色。
我在worker中主要是把这段代码通过blob的形式实现。

在主线程只需要new Worker(workerScript)即可。
目前使用后存在一些问题,如果有人知道怎么解决,强烈欢迎来帮助我解决一下🙏🙏🙏
遇到的问题
- web worker不支持导入的时候使用变量,这样会导致报错


webpack和rspack 均不支持在worker中使用变量


2、在worker中通过import引入其他文件,在打包到测试环境上后,会报安全问题,目前不知道咋解决.

这样就很烦,解析图片主色调和计算对比度的算法就是从第三方库的代码里扒出来的,然后这样就导致一个文件里面巨多代码
本人采取过的尝试
- 参考了rspack的例子,在本地通过import 引入没有问题,但是打包测试环境就不行了
- 通过importScript导入,ts会直接报错==>在tsconfig.json引入一下可以解决报错。但是没用,importScript不能加载模块化的文件。导入的文件不能通过export导出

既然都把别人的第三方库代码扒下来了,取色和对比度算法也不是很复杂,接下来我们就来探究下这两个方法的原理吧,worker就先说到这。
参考文档: