Firefox无法从Worker控制的Offscreen Canvas调用drawImage问题求助
问题:Web Worker绘制Offscreen Canvas后转移到可见Canvas在Firefox中失效?
我尝试将Web Worker绘制在Offscreen Canvas上的图像转移到可见Canvas中,该功能在Chrome中正常运行,但在Firefox中失效。控制台无报错信息,但可见Canvas始终处于空白状态。以下是完整的index.html代码:
<!doctype html> <html> <head> <meta charset="utf-8"> <script> window.addEventListener('load', function () { const w = 600, h = 400; const blob = new Blob(['(' + render.toString() + ')()'], {type: 'text/javascript'}); const worker = new Worker(URL.createObjectURL(blob)); const offs = document.getElementById('worker').transferControlToOffscreen(); worker.postMessage({ msg: 'load', canvas: offs }, [offs]); // draw image in offscreen canvas document.getElementById('render').addEventListener('click', function () { worker.postMessage({ msg: 'draw' }); }); // transfer image to main canvas document.getElementById('transfer').addEventListener('click', function () { const img = document.getElementById('worker'); const ctx = document.getElementById('main').getContext('2d'); ctx.clearRect(0, 0, w, h); ctx.drawImage(img, 0, 0, w, h, 0, 0, w, h); }); }); // worker for rendering function render() { let canvas; onmessage = function(e) { if (e.data.msg === 'load') { canvas = e.data.canvas; } else if (e.data.msg === 'draw') { ctx = canvas.getContext('2d'); ctx.fillStyle = 'green'; ctx.fillRect(150, 150, 300, 200); } } } </script> </head> <body> <div> <p> <canvas id="main" width="600" height="400"></canvas> <canvas id="worker" width="600" height="400" style="display: none"></canvas> </p> <p> <button id="render">Render</button> <button id="transfer">Transfer</button> </p> </div> </body> </html>
原因分析
这是Firefox与Chrome对已转移控制权的Canvas元素处理逻辑的差异导致的:
- Chrome中,即便Canvas的控制权通过
transferControlToOffscreen()转移给Worker,主线程中的原Canvas元素仍能作为图像源被drawImage()读取; - Firefox中,一旦Canvas的控制权被转移,主线程的Canvas元素就不再关联Worker中绘制的内容,因此调用
drawImage()时无法获取有效图像数据,最终呈现空白。
修复方案
方案1:通过ImageBitmap跨线程传递图像(推荐)
这是标准的跨线程图像传递方式,兼容性更好,符合规范。
修改主线程代码
在window.load回调中添加Worker消息监听,并缓存传递过来的ImageBitmap:
window.addEventListener('load', function () { const w = 600, h = 400; const blob = new Blob(['(' + render.toString() + ')()'], {type: 'text/javascript'}); const worker = new Worker(URL.createObjectURL(blob)); const offs = document.getElementById('worker').transferControlToOffscreen(); worker.postMessage({ msg: 'load', canvas: offs }, [offs]); // 新增:缓存Worker传递的ImageBitmap let cachedBitmap = null; worker.onmessage = function(e) { if (e.data.msg === 'bitmap') { cachedBitmap = e.data.data; } }; // draw image in offscreen canvas document.getElementById('render').addEventListener('click', function () { worker.postMessage({ msg: 'draw' }); }); // 修改:绘制缓存的ImageBitmap document.getElementById('transfer').addEventListener('click', function () { const ctx = document.getElementById('main').getContext('2d'); ctx.clearRect(0, 0, w, h); if (cachedBitmap) { ctx.drawImage(cachedBitmap, 0, 0, w, h); } }); });
修改Worker代码
在绘制完成后生成ImageBitmap并传递回主线程:
function render() { let canvas; onmessage = function(e) { if (e.data.msg === 'load') { canvas = e.data.canvas; } else if (e.data.msg === 'draw') { const ctx = canvas.getContext('2d'); ctx.fillStyle = 'green'; ctx.fillRect(150, 150, 300, 200); // 生成ImageBitmap并传递回主线程 createImageBitmap(canvas).then(bitmap => { postMessage({ msg: 'bitmap', data: bitmap }, [bitmap]); }); } } }
方案2:使用独立OffscreenCanvas(无需关联DOM)
如果不想依赖DOM中的隐藏Canvas,可以直接在主线程创建OffscreenCanvas,Worker绘制后通过transferToImageBitmap()获取图像。
修改主线程代码
window.addEventListener('load', function () { const w = 600, h = 400; const blob = new Blob(['(' + render.toString() + ')()'], {type: 'text/javascript'}); const worker = new Worker(URL.createObjectURL(blob)); // 直接创建OffscreenCanvas,不关联DOM元素 const offs = new OffscreenCanvas(w, h); worker.postMessage({ msg: 'load', canvas: offs }, [offs]); let cachedBitmap = null; worker.onmessage = function(e) { if (e.data.msg === 'drawComplete') { // 从OffscreenCanvas获取ImageBitmap cachedBitmap = offs.transferToImageBitmap(); } }; document.getElementById('render').addEventListener('click', function () { worker.postMessage({ msg: 'draw' }); }); document.getElementById('transfer').addEventListener('click', function () { const ctx = document.getElementById('main').getContext('2d'); ctx.clearRect(0, 0, w, h); if (cachedBitmap) { ctx.drawImage(cachedBitmap, 0, 0, w, h); } }); });
修改Worker代码
绘制完成后发送完成通知:
function render() { let canvas; onmessage = function(e) { if (e.data.msg === 'load') { canvas = e.data.canvas; } else if (e.data.msg === 'draw') { const ctx = canvas.getContext('2d'); ctx.fillStyle = 'green'; ctx.fillRect(150, 150, 300, 200); // 通知主线程绘制完成 postMessage({ msg: 'drawComplete' }); } } }
内容的提问来源于stack exchange,提问作者Qiyun Zhu
相关产品推荐
相关产品推荐

