OffscreenCanvas в JavaScript: разгоняем графику до максимума

от автора

Привет, Хабр! Сегодня разберёмся с тем, что такое OffscreenCanvas, зачем он нужен и как правильно его использовать.

OffscreenCanvas — это API, которое позволяет рендерить графику в отдельном потоке Worker, не блокируя основной поток, где обрабатывается интерфейс.

Технически это достигается за счёт разделения UI и вычислений:

  1. Canvas вне DOM
    OffscreenCanvas работает независимо от DOM. У него нет прямого визуального представления в интерфейсе. Это «виртуальный холст», который не участвует в браузерном рендеринге.

  2. Рендеринг в Worker
    Основная фича — рендеринг может выполняться в Web Worker. Браузерный поток UI остается свободным для обработки событий, скролла и других пользовательских взаимодействий.

  3. Поддержка разных API
    OffscreenCanvas поддерживает стандартные API:

    • 2d для работы с 2D-графикой;

    • webgl/webgl2 для 3D-рендеринга;

    • bitmaprenderer для прямого отображения растровых изображений.

  4. Оптимизация многопоточности
    За счёт использования transferControlToOffscreen, холст передаётся в Worker как объект с передачей владения (transferable object). Это не копия, а прямой перенос ссылки на объект.

На момент написания статьи OffscreenCanvas поддерживается в современных версиях Chrome, Edge, Firefox и частично в других браузерах. Safari с версии 16.4 поддерживает OffscreenCanvas с контекстом «2d», а начиная с версии 17.0 — и с контекстом WebGL. Однако в некоторых источниках можно увидеть информацию о проблемах с доступом к OffscreenCanvas в Safari Technology Preview.

А теперь подключим OffscreenCanvas на примере.

Подключаем OffscreenCanvas

Начнем с HTML. Будет обычный <canvas>.

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <title>OffscreenCanvas Example</title> </head> <body>     <canvas id="mainCanvas" width="800" height="600"></canvas>     <script src="main.js"></script> </body> </html> 

Теперь переходим к JavaScript. Главная задача на этом этапе — создать OffscreenCanvas и передать его в Web Worker.

// main.js const canvas = document.getElementById('mainCanvas');  // Создаем Web Worker const worker = new Worker('worker.js');  // Передаем управление холстом OffscreenCanvas в Worker const offscreen = canvas.transferControlToOffscreen(); worker.postMessage({ canvas: offscreen }, [offscreen]);

transferControlToOffscreen() превращает<canvas> в объект OffscreenCanvas и передает его в Worker. После этого сам <canvas> на странице больше недоступен для рисования.

Теперь переходим к логике Worker’а. Будем анимировать частицы:

// worker.js self.onmessage = function (event) {     const canvas = event.data.canvas;     const ctx = canvas.getContext('2d');      // Настраиваем размеры холста     canvas.width = 800;     canvas.height = 600;      const particles = [];      // Генерация частиц     for (let i = 0; i < 1000; i++) {         particles.push({             x: Math.random() * canvas.width,             y: Math.random() * canvas.height,             vx: (Math.random() - 0.5) * 2,             vy: (Math.random() - 0.5) * 2,             size: Math.random() * 3 + 1,             color: `hsl(${Math.random() * 360}, 100%, 50%)`         });     }      // Основной цикл отрисовки     function draw() {         ctx.clearRect(0, 0, canvas.width, canvas.height);          for (const particle of particles) {             ctx.beginPath();             ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);             ctx.fillStyle = particle.color;             ctx.fill();              // Обновление позиции частицы             particle.x += particle.vx;             particle.y += particle.vy;              // Проверка выхода за границы             if (particle.x < 0 || particle.x > canvas.width) particle.vx *= -1;             if (particle.y < 0 || particle.y > canvas.height) particle.vy *= -1;         }          // Рекурсивный вызов для плавной анимации         requestAnimationFrame(draw);     }      draw(); };

После передачи холста через transferControlToOffscreen() основной поток больше не может напрямую взаимодействовать с этим <canvas>. Визуализация выполняется полностью в Worker.

Для более лучшего взаимодействия между потоками можно юзать MessageChannel. Это позволяет передавать данные между основным потоком и Worker без лишних копий:

// Основной поток const channel = new MessageChannel(); worker.postMessage({ port: channel.port1 }, [channel.port1]);  channel.port2.onmessage = (event) => {     console.log('Сообщение от Worker:', event.data); };  // Worker self.onmessage = function (event) {     const port = event.data.port;      setInterval(() => {         port.postMessage({ status: 'Работаю!' });     }, 1000); };

OffscreenCanvas так же поддерживает контекст WebGL:

// worker.js self.onmessage = function (event) {     const canvas = event.data.canvas;     const ctx = canvas.getContext('2d');      // Настраиваем размеры холста     canvas.width = 800;     canvas.height = 600;      const particles = [];      // Генерация частиц     for (let i = 0; i < 1000; i++) {         particles.push({             x: Math.random() * canvas.width,             y: Math.random() * canvas.height,             vx: (Math.random() - 0.5) * 2,             vy: (Math.random() - 0.5) * 2,             size: Math.random() * 3 + 1,             color: `hsl(${Math.random() * 360}, 100%, 50%)`         });     }      // Основной цикл отрисовки     function draw() {         ctx.clearRect(0, 0, canvas.width, canvas.height);          for (const particle of particles) {             ctx.beginPath();             ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);             ctx.fillStyle = particle.color;             ctx.fill();              // Обновление позиции частицы             particle.x += particle.vx;             particle.y += particle.vy;              // Проверка выхода за границы             if (particle.x < 0 || particle.x > canvas.width) particle.vx *= -1;             if (particle.y < 0 || particle.y > canvas.height) particle.vy *= -1;         }          // Рекурсивный вызов для плавной анимации         requestAnimationFrame(draw);     }      draw(); };

Попробуйте, тестируйте, а если найдете интересные кейсы, делитесь в комментариях!


18 декабря в Otus пройдет открытый урок «Манипуляции с HTML и CSS с помощью JavaScript — основы динамичного взаимодействия с элементами страницы». Записаться можно по ссылке.


ссылка на оригинал статьи https://habr.com/ru/articles/863634/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *