Using Web Workers for Large Excel Export and Batch Image Compression in Frontend Projects
This article explains how to leverage Web Workers and OffscreenCanvas to offload heavy Excel file generation and bulk image compression from the main thread, improving UI responsiveness and reducing rendering stalls in typical backend‑admin web applications.
Preface
The author introduces the motivation: many developers learn Web Workers but struggle to find practical scenarios. Two simple, project‑relevant examples—large Excel export and batch image compression—are presented.
Browser Processes and Threads
The article lists the main browser processes (browser main process, GPU process, third‑party process, render process) and the key threads inside a render process (GUI rendering thread, JS engine thread, event thread, timer thread, async HTTP thread, worker thread). It explains that the GUI thread and JS engine thread share the render process and are mutually exclusive, causing UI freezes when the JS thread is busy.
Excel Large‑File Export
Two export strategies are compared:
Main‑thread export : Uses exceljs to build a workbook, writes it to a Blob , and saves it with file‑save .
Worker‑thread export : Creates an ExcelWorker , posts column and data information, builds the workbook inside the worker, converts it to a Blob , and sends the result back to the main thread for saving.
Both implementations are shown in the following code snippets:
// index.tsx
import { Button, Table } from 'antd';
import React, { useState, useEffect } from 'react';
import ExcelJS from 'exceljs';
import FileSaver from 'file-saver';
import ExcelWorker from './excel.worker?worker';
// ... data generation omitted for brevity ...
const mainExportExcel = () => {
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('sheet1');
// set title and columns, add rows
workbook.xlsx.writeBuffer().then(buffer => {
const file = new Blob([buffer], { type: 'application/octet-stream' });
FileSaver.saveAs(file, 'ExcelJS.xlsx');
});
};
const workerExportExcel = async () => {
const file = await new Promise(resolve => {
const myWorker = new ExcelWorker();
myWorker.postMessage({ columns, dataSource });
myWorker.onmessage = e => { resolve(e.data.data); myWorker.terminate(); };
});
FileSaver.saveAs(file, 'ExcelJS.xlsx');
}; // excel.worker.ts
import ExcelJS from 'exceljs';
onmessage = function (e) {
const { columns, dataSource } = e.data;
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('sheet1');
// set title and columns, add rows
workbook.xlsx.writeBuffer().then(buffer => {
const file = new Blob([buffer], { type: 'application/octet-stream' });
self.postMessage({ data: file, name: 'worker test' });
self.close();
});
};Performance tests show that the main‑thread export of 30 000 rows causes noticeable UI stutter, while the worker‑based export remains smooth.
OffscreenCanvas
OffscreenCanvas is introduced as an experimental feature that can be used inside Web Workers to perform canvas drawing without blocking the main thread. Compatibility is good for Chrome, Edge, and Firefox, but not for Safari or IE.
Batch Image Compression
The article compares two approaches for compressing many images before upload:
Main‑thread compression : Loads each image, draws it onto a regular canvas, and calls canvas.toDataURL('image/jpeg', 0.75) to obtain a compressed base64 string.
Worker‑thread compression : Fetches images as Blob s, creates multiple workers, each uses an OffscreenCanvas to draw the image, calls convertToBlob for JPEG compression, converts the blob to a data URL via FileReader , and sends the result back.
Key code snippets:
// index.tsx (main compression)
const compressImg = img => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
return canvas.toDataURL('image/jpeg', 0.75);
}; // compress.worker.ts (worker compression)
onmessage = async function (e) {
const { imageList } = e.data;
const resList = [];
for (let img of imageList) {
const offscreen = new OffscreenCanvas(100, 100);
const ctx = offscreen.getContext('2d');
const imgData = await createImageBitmap(img);
offscreen.width = imgData.width;
offscreen.height = imgData.height;
ctx.drawImage(imgData, 0, 0, offscreen.width, offscreen.height);
const blob = await offscreen.convertToBlob({ type: 'image/jpeg', quality: 0.75 });
const reader = new FileReader();
reader.readAsDataURL(blob);
const dataUrl = await new Promise(resolve => { reader.onloadend = () => resolve(reader.result); });
resList.push(dataUrl);
}
self.postMessage({ data: resList, name: 'worker test' });
self.close();
};Benchmark results show that compressing 100 images 100 times on the main thread takes about 108 seconds and blocks the GUI, while using five workers reduces the total time to roughly 37 seconds and keeps the UI responsive.
Conclusion
The author summarizes that Web Workers provide practical solutions for two common pain points—large Excel export and bulk image compression—especially in backend‑admin systems lacking obvious features. By moving heavy computation to workers and optionally using OffscreenCanvas, developers can avoid UI freezes and improve overall performance.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.