WebWorker发消息等回复:画布满时创建新OffscreenCanvas方案问询
解决方案:WebWorker通过Promise请求并获取新OffscreenCanvas
核心思路
借助postMessage的消息传递机制,结合Promise的异步特性,用唯一ID关联Worker的请求与主线程的响应,确保消息能精准匹配,让Worker端的画布请求逻辑更简洁可控。
具体实现步骤
1. 主线程代码
维护一个请求映射表,用唯一ID存储每个请求的Promise resolve函数,监听Worker的画布请求后创建新画布,再通过ID关联返回结果。
// 主线程 const worker = new Worker('draw-worker.js'); const requestMap = new Map(); let requestId = 0; // 监听Worker消息 worker.addEventListener('message', (e) => { const { type, id, data } = e.data; if (type === 'REQUEST_NEW_CANVAS') { // 创建新Canvas及对应的OffscreenCanvas const newCanvas = document.createElement('canvas'); newCanvas.width = data.width; newCanvas.height = data.height; document.body.appendChild(newCanvas); // 按需添加到DOM const offscreen = newCanvas.transferControlToOffscreen(); // 触发对应Promise的resolve const resolve = requestMap.get(id); if (resolve) { resolve(offscreen); requestMap.delete(id); } // 传递OffscreenCanvas,需标记为transferable对象 worker.postMessage({ type: 'NEW_CANVAS_READY', id, canvas: offscreen }, [offscreen]); } }); // 封装给Worker的新画布请求函数 function createNewCanvasForWorker(width, height) { return new Promise((resolve) => { const id = requestId++; requestMap.set(id, resolve); worker.postMessage({ type: 'REQUEST_NEW_CANVAS', id, data: { width, height } }); }); } // 初始化:给Worker发送首个OffscreenCanvas const initialCanvas = document.getElementById('initial-canvas'); const initialOffscreen = initialCanvas.transferControlToOffscreen(); worker.postMessage({ type: 'INIT', canvas: initialOffscreen, elements: [...yourElementList] }, [initialOffscreen]);
2. Worker线程代码
维护请求ID计数器,封装返回Promise的画布请求函数,监听主线程的响应消息,通过ID匹配后resolve结果。
// draw-worker.js let currentOffscreenCanvas = null; let currentRequestId = 0; const responseMap = new Map(); // 监听主线程消息 self.addEventListener('message', (e) => { const { type, id, canvas, elements } = e.data; if (type === 'INIT') { currentOffscreenCanvas = canvas; startDrawing(elements); } else if (type === 'NEW_CANVAS_READY') { // 匹配请求ID并resolve const resolve = responseMap.get(id); if (resolve) { resolve(canvas); responseMap.delete(id); } } }); // 封装请求新画布的Promise函数 function requestNewCanvas(width, height) { return new Promise((resolve) => { const id = currentRequestId++; responseMap.set(id, resolve); self.postMessage({ type: 'REQUEST_NEW_CANVAS', id, data: { width, height } }); }); } // 核心绘制逻辑(异步写法) async function startDrawing(elements) { let ctx = currentOffscreenCanvas.getContext('2d'); let elementIndex = 0; const { width: canvasWidth, height: canvasHeight } = currentOffscreenCanvas; while (elementIndex < elements.length) { const element = elements[elementIndex]; // 检查当前画布是否剩余空间(替换为你的实际判断逻辑) if (!hasSpace(element, canvasWidth, canvasHeight)) { // 请求新画布并切换 currentOffscreenCanvas = await requestNewCanvas(canvasWidth, canvasHeight); ctx = currentOffscreenCanvas.getContext('2d'); } // 绘制元素(替换为你的实际绘制逻辑) drawElement(ctx, element); elementIndex++; } } // 示例:判断画布空间(按需实现) function hasSpace(element, canvasWidth, canvasHeight) { // 示例逻辑:判断元素是否超出画布边界 return element.x + element.width <= canvasWidth && element.y + element.height <= canvasHeight; } // 示例:绘制元素(按需实现) function drawElement(ctx, element) { ctx.fillStyle = element.color; ctx.fillRect(element.x, element.y, element.width, element.height); }
关键细节
- 唯一ID关联:避免多请求场景下的消息混乱,确保每个请求都能对应到正确的响应
- Transferable对象:传递OffscreenCanvas时必须加入transfer列表,减少内存拷贝,提升性能
- async/await简化逻辑:Promise封装后,Worker的绘制流程可以用同步写法处理异步请求,代码更易维护
内容的提问来源于stack exchange,提问作者Sauriel
相关产品推荐
相关产品推荐

