Three.js активно использует возможности WebGL для рендеринга 3D-сцен в браузере. При сложных сценах или интенсивных вычислениях основная нагрузка ложится на главный поток (main thread), что может приводить к задержкам интерфейса. Для решения этой проблемы используется OffscreenCanvas вместе с веб-воркерами (Web Workers), что позволяет переносить рендеринг в отдельный поток.
OffscreenCanvas создается аналогично обычному
<canvas>, но при этом не привязан напрямую к DOM.
Основной способ создания:
const canvas = new OffscreenCanvas(width, height);
Его можно передавать в воркер через postMessage с
указанием { transfer: [canvas] }, чтобы воркер получил
полный контроль над объектом:
worker.postMessage({ canvas: canvas }, [canvas]);
Это позволяет в воркере использовать WebGL-контекст без участия главного потока.
В воркере создается рендерер, который принимает OffscreenCanvas:
import * as THREE from 'three';
self.onmess age = (event) => {
const canvas = event.data.canvas;
const renderer = new THREE.WebGLRenderer({ canvas });
renderer.setSize(canvas.width, canvas.height);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, canvas.width / canvas.height, 0.1, 1000);
// Настройка геометрии и материалов
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
function animate() {
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate();
};
Ключевой момент: requestAnimationFrame в воркере работает только если используется OffscreenCanvas, иначе кадры не будут обновляться автоматически.
Чтобы сцена корректно реагировала на изменения окна и пользовательский ввод, необходимо передавать события из главного потока в воркер:
window.addEventListener('resize', () => {
worker.postMessage({ type: 'resize', width: window.innerWidth, height: window.innerHeight });
});
canvas.addEventListener('mousemove', (event) => {
worker.postMessage({ type: 'mousemove', x: event.clientX, y: event.clientY });
});
В воркере нужно обработать эти сообщения и обновить параметры камеры и рендерера:
self.onmess age = (event) => {
if(event.data.type === 'resize') {
renderer.setSize(event.data.width, event.data.height);
camera.aspect = event.data.width / event.data.height;
camera.updateProjectionMatrix();
} else if(event.data.type === 'mousemove') {
// Можно использовать координаты для управления камерой или объектами
}
};
<canvas> не поддерживают
работу с воркером напрямую.postMessage.ImageBitmap или
fetch, чтобы воркер мог загрузить изображения.Для передачи изображений в воркер рекомендуется использовать
createImageBitmap, что минимизирует нагрузку на главный
поток:
const img = new Image();
img.src = 'texture.png';
img.onl oad = async () => {
const bitmap = await createImageBitmap(img);
worker.postMessage({ type: 'texture', bitmap }, [bitmap]);
};
В воркере этот bitmap можно использовать для создания текстуры:
const texture = new THREE.CanvasTexture(event.data.bitmap);
material.map = texture;
material.needsUpdate = true;
drawImage.Основной поток может отобразить результат воркера, создавая обычный
<canvas> и связывая его с OffscreenCanvas:
const mainCanvas = document.getElementById('mainCanvas');
const offscreen = mainCanvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);
Таким образом, визуализация остается в главном DOM, а тяжелые вычисления полностью уходят в воркер.
transfer вместо копирования.postMessage.Эта архитектура позволяет создавать интерактивные 3D-приложения высокой сложности без просадок FPS и зависаний интерфейса, полностью используя возможности многопоточности браузера через OffscreenCanvas и Web Workers в связке с Three.js.