🌐用 Web Worker 实现浏览器端图片压缩:不卡页面的高性能实践

10 阅读4分钟

什么是 Web Workers?

Web Workers 是 HTML5 提供的一种浏览器多线程机制,允许你在后台线程中运行 JavaScript 脚本,而不会阻塞用户界面(主线程)。这意味着你可以执行耗时的计算任务(如大模型推理、图像处理、数据压缩等),同时保持页面的流畅响应。

核心目标: 解放主线程,提升用户体验。

为什么需要 Web Workers?

JavaScript 是单线程的,这意味着所有任务(DOM 操作、事件处理、网络请求、计算)都运行在一个线程上。

  • 当执行一个耗时任务(比如训练模型、压缩图片、遍历大数据)时:

    • 页面会“卡住”(无响应)
    • 用户无法点击、滚动、输入
    • 浏览器可能提示“页面未响应”

👉 Web Workers 就是为了解决这个问题而生的。

Web Workers 的特点

特性说明
多线程支持在浏览器中开启独立线程执行 JS
不阻塞主线程主线程继续响应用户操作
独立运行环境Worker 有自己独立的全局上下文(不能访问 DOM)
通过消息通信主线程与 Worker 之间通过 postMessage 和 onmessage 通信
受限权限不能操作 DOM、不能使用 windowdocument 等对象

基本使用 API

1. 创建 Worker

// main.js - 主线程
const worker = new Worker('worker.js');

注意:Worker 文件必须是一个独立的 .js 文件,且同源(出于安全考虑)。

2. 发送消息给 Worker

worker.postMessage('Hello Worker!');
// 也可以发送对象
worker.postMessage({ type: 'compress', data: imageData });

3. 接收来自 Worker 的消息

worker.onmessage = function(e) {
  console.log('接收到结果:', e.data);
};

4. Worker 线程中(worker.js)

// worker.js
self.onmessage = function(e) {
  console.log('收到消息:', e.data);

  // 模拟耗时计算
  const result = heavyComputation(e.data);

  // 将结果返回主线程
  self.postMessage(result);
};

function heavyComputation(data) {
  // 比如:大模型推理、图像压缩、加密解密...
  let sum = 0;
  for (let i = 0; i < 1e9; i++) {
    sum += i;
  }
  return sum;
}

5. 终止 Worker

worker.terminate(); // 立即终止

实际应用场景

1. 浏览器端运行大模型(端模型趋势)

随着 WebAssembly 和 WebGPU 的发展,越来越多的大模型(如 Llama、Bloom)开始支持在浏览器中运行。

  • 使用 Web Worker 执行模型推理,避免页面卡顿。
  • 模型加载、tokenization、推理都在 Worker 中完成。
  • 主线程只负责 UI 更新。

🔮 趋势:AI in Browser(如 Hugging Face 的 transformers.js、Ollama Web、WebLLM)

2. 图片/视频压缩与处理

  • 用户上传高清图 → Worker 中压缩 → 返回压缩结果
  • 不影响用户继续操作页面

实战:用 Web Worker 实现图片压缩

我们来实现一个功能:用户上传图片 → 后台压缩 → 显示压缩结果,全程页面不卡顿

项目结构如下:

project/
├── index.html        // 页面结构
├── main.js           // 主线程逻辑
└── compressWorker.js // 后台压缩线程
  1. compressWorker.js → Web Worker 线程(后台干活的)
  2. main.js → 主线程 JavaScript(用户交互、启动压缩)
  3. index.html → 页面结构(用户界面)
用户上传图片
      ↓
index.html → 触发 change 事件
      ↓
main.js → 读取文件为 base64 (FileReader)
      ↓
main.js → postMessage → compressWorker.js
      ↓
compressWorker.js → 接收 base64
      ↓
fetch + blob + createImageBitmap → 解码图像
      ↓
OffscreenCanvas + drawImage → 绘制
      ↓
convertToBlob({quality}) → 压缩
      ↓
FileReader → 转回 base64
      ↓
self.postMessage → 发回主线程
      ↓
main.js → onmessage → 收到压缩图
      ↓
innerHTML → 显示图片

1. 页面结构(index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图片压缩</title>
</head>
<body>
    <input type="file" id="fileInput" accept="image/*">
    <div id="output"></div>

    <script src="./main.js"></script>
</body>
</html>
  • input 用于上传图片
  • output 用于显示压缩后的图片

2. 主线程控制(main.js)

// 启动一个 Web Worker
const worker = new Worker('./compressWorker.js');

// 接收 Worker 返回的结果
worker.onmessage = function(e) {
    const output = document.getElementById('output');
    if (e.data.success) {
        output.innerHTML = `<img src="${e.data.data}" style="max-width:100%"/>`;
    } else {
        output.innerHTML = `<p style="color:red">压缩失败: ${e.data.error}</p>`;
    }
};

// 读取文件为 base64
function handleFile(file) {
    return new Promise(resolve => {
        const reader = new FileReader();
        reader.onload = () => resolve(reader.result);
        reader.readAsDataURL(file);
    });
}

// 发起压缩任务
async function compressFile(file) {
    const imgDataUrl = await handleFile(file);
    // 将图片数据发送给 Worker
    worker.postMessage({
        imgData: imgDataUrl,
        quality: 0.3  // 压缩质量(0-1)
    });
}

// 监听文件上传
document.getElementById('fileInput').addEventListener('change', async function(e) {
    const file = e.target.files[0];
    if (file) await compressFile(file);
});

📌 关键点

  • 使用 new Worker() 启动后台线程
  • 用 postMessage 把图片数据发给 Worker
  • 用 onmessage 接收压缩结果

3. 后台压缩逻辑(compressWorker.js)

self.onmessage = async function(e) {
    const { imgData, quality = 0.8 } = e.data;

    try {
        // 1. base64 → Blob → ImageBitmap
        const response = await fetch(imgData);
        const blob = await response.blob();
        const bitmap = await createImageBitmap(blob);

        // 2. 创建 OffscreenCanvas(Worker 中可用的画布)
        const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
        const ctx = canvas.getContext('2d');
        ctx.drawImage(bitmap, 0, 0); // 绘制图像

        // 3. 压缩:Canvas → Blob(JPEG + 质量控制)
        const compressedBlob = await canvas.convertToBlob({
            type: 'image/jpeg',
            quality
        });

        // 4. Blob → base64(供主线程显示)
        const reader = new FileReader();
        reader.onloadend = () => {
            self.postMessage({
                success: true,
                data: reader.result
            });
        };
        reader.readAsDataURL(compressedBlob);

        // 清理资源
        bitmap.close();
    } catch (err) {
        self.postMessage({
            success: false,
            error: err.message
        });
    }
};

🚀 结尾

Web Worker 是每个前端开发者都应该掌握的进阶技能。它不仅是性能优化的利器,更是构建高性能 Web 应用(如在线 PS、AI 工具、数据可视化)的关键。

下次当你遇到“页面卡顿”问题时,不妨问自己:这个任务,能不能交给 Web Worker?