Web Worker 中的离屏画布
如何在 Web Worker 中运行 Konva?
警告!这个演示是非常实验性的!可能在许多浏览器中无法正常工作。 请查看 Offscreen canvas capability tabletv。
通过一些额外的工作,我们可以在 Web Worker 中使用 Offscreen Canvas 渲染 Konva
阶段,以提高性能或实现一些疯狂的想法。
您可以使用 Web Worker 使用 Konva
制作一些可视化效果。
但 Konva
的主要特点之一是它的交互性(对画布形状的完整事件支持)。而在 Web Worker 内部没有 DOM 事件。因此,我们必须编写某种“代理”来传递所有 DOM 事件到 Konva 引擎中。这样我们也可以在 Web Worker 内部拥有交互式对象。
这个演示改编自 跳跃的兔子 性能压力测试。
说明:舞台上有两个交互对象。“添加按钮”和一个可拖动的红色圆圈。尝试添加更多兔子或拖动圆圈。
您在屏幕上看到的所有内容都是在另一个 JavaScript 线程中渲染的!因此,它不应该阻塞当前页面的主 JS 线程。
- Vanilla
// main.js const workerCode = ` // 加载 konva 框架 importScripts('https://unpkg.com/konva@9/konva.min.js'); // monkeypatch Konva 以使用离屏画布 Konva.Util.createCanvasElement = () => { const canvas = new OffscreenCanvas(1, 1); canvas.style = {}; return canvas; }; // 现在我们可以创建我们的画布内容 var stage = new Konva.Stage({ width: 200, height: 200, }); var layer = new Konva.Layer(); stage.add(layer); var topGroup = new Konva.Group(); layer.add(topGroup); // 计数器将显示兔子的数量 var counter = new Konva.Text({ x: 5, y: 35, }); topGroup.add(counter); // “添加更多兔子”按钮 var button = new Konva.Label({ x: 5, y: 5, opacity: 0.75, }); topGroup.add(button); button.add( new Konva.Tag({ fill: 'black', }) ); button.add( new Konva.Text({ text: '推我以添加兔子', fontFamily: 'Calibri', fontSize: 18, padding: 5, fill: 'white', }) ); // 可拖动的圆圈以显示交互性 var circle = new Konva.Circle({ x: stage.width() / 2, y: stage.height() / 2, radius: 20, fill: 'red', draggable: true, }); topGroup.add(circle); self.onmessage = function (evt) { // 当画布被传递时,我们可以启动工作线程 if (evt.data.canvas) { var canvas = evt.data.canvas; stage.setSize({ width: canvas.width, height: canvas.height, }); const ctx = canvas.getContext('2d'); layer.on('draw', () => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(layer.getCanvas()._canvas, 0, 0); }); } // 模拟一些拖动和放置事件 if (evt.data.eventName === 'mouseup') { Konva.DD._endDragBefore(evt.data.event); } if (evt.data.eventName === 'touchend') { Konva.DD._endDragBefore(evt.data.event); } if (evt.data.eventName === 'mousemove') { Konva.DD._drag(evt.data.event); } if (evt.data.eventName === 'touchmove') { Konva.DD._drag(evt.data.event); } if (evt.data.eventName === 'mouseup') { Konva.DD._endDragAfter(evt.data.event); } if (evt.data.eventName === 'touchend') { Konva.DD._endDragAfter(evt.data.event); } // 将传入的事件传递到舞台 if (evt.data.eventName) { const event = evt.data.eventName.replace('mouse', 'pointer'); stage['_' + event](evt.data.event); } }; function requestAnimationFrame(cb) { setTimeout(cb, 16); } async function runBunnies() { const imgBlob = await fetch('https://konvajs.org/assets/bunny.png').then( (r) => r.blob() ); const img = await createImageBitmap(imgBlob); var bunnys = []; var gravity = 0.75; var startBunnyCount = 100; var isAdding = false; var count = 0; var amount = 10; button.on('mousedown', function () { isAdding = true; }); button.on('mouseup', function () { isAdding = false; }); for (var i = 0; i < startBunnyCount; i++) { var bunny = new Konva.Image({ image: img, transformsEnabled: 'position', x: 10, y: 10, listening: false, }); bunny.speedX = Math.random() * 10; bunny.speedY = Math.random() * 10 - 5; bunnys.push(bunny); counter.text('兔子数量: ' + bunnys.length); layer.add(bunny); } topGroup.moveToTop(); function update() { var maxX = stage.width() - 10; var minX = 0; var maxY = stage.height() - 10; var minY = 0; if (isAdding) { for (var i = 0; i < amount; i++) { var bunny = new Konva.Image({ image: img, transformsEnabled: 'position', x: 0, y: 0, listening: false, }); bunny.speedX = Math.random() * 10; bunny.speedY = Math.random() * 10 - 5; bunnys.push(bunny); layer.add(bunny); counter.text('兔子数量: ' + bunnys.length); count++; } topGroup.moveToTop(); } for (var i = 0; i < bunnys.length; i++) { var bunny = bunnys[i]; bunny.setX(bunny.getX() + bunny.speedX); bunny.setY(bunny.getY() + bunny.speedY); bunny.speedY += gravity; if (bunny.getX() > maxX - img.width) { bunny.speedX *= -1; bunny.setX(maxX - img.width); } else if (bunny.getX() < minX) { bunny.speedX *= -1; bunny.setX(minX); } if (bunny.getY() > maxY - img.height) { bunny.speedY *= -0.85; bunny.setY(maxY - img.height); if (Math.random() > 0.5) { bunny.speedY -= Math.random() * 6; } } else if (bunny.getY() < minY) { bunny.speedY = 0; bunny.setY(minY); } } layer.drawScene(); requestAnimationFrame(update); } update(); } runBunnies(); `; // 从工作代码创建一个 Blob const blob = new Blob([workerCode], { type: 'application/javascript' }); const worker = new Worker(URL.createObjectURL(blob)); const canvas = document.createElement('canvas'); document.body.appendChild(canvas); canvas.style.border = '1px solid black'; canvas.width = window.innerWidth; canvas.height = window.innerHeight; // 将画布的控制权转移给工作线程 const offscreen = canvas.transferControlToOffscreen(); worker.postMessage({ canvas: offscreen }, [offscreen]); // 代理所有事件 const events = [ 'mousedown', 'mouseup', 'mousemove', 'mouseenter', 'mouseleave', // 'click', // 'dblclick', 'touchstart', 'touchend', 'touchmove', ]; events.forEach((eventName) => { canvas.addEventListener(eventName, (event) => { worker.postMessage({ eventName, event: { clientX: event.clientX, clientY: event.clientY, type: event.type, button: event.button, }, }); }); });