При создании сложных интерактивных визуальных приложений для интернета веб-разработчикам приходится принимать непростое архитектурное решение. Следует ли использовать DOM с его богатыми семантическими функциями или напрямую отрисовывать элементы в элементе <canvas> для повышения производительности графики?
Новый экспериментальный HTML-in-Canvas API, который сейчас доступен в пробной версии, позволяет не выбирать между этими вариантами. Этот API позволяет рисовать контент DOM непосредственно на двумерном холсте или текстуре WebGL/WebGPU, сохраняя при этом интерактивность и доступность интерфейса, а также его связь с функциями браузера. Сочетание HTML с низкоуровневой обработкой графики позволяет создавать контент, который раньше был невозможен.
DOM и Canvas
Чтобы понять, насколько полезен новый API, нужно сравнить возможности DOM и Canvas.
DOM – основа веб-интерфейса. Он предлагает готовые решения для макетов текста, используя семантически понятный контент для создания насыщенных интерфейсов. Это позволяет пользователям выполнять на веб-страницах распространенные действия, которые мы часто воспринимаем как должное, например выделять текст для копирования или нажимать правой кнопкой мыши на изображение, чтобы сохранить его. Кроме того, DOM интегрируется с основными функциями браузера: инструментами специальных возможностей, переводчиком, поиском на странице, режимом чтения, расширениями, темной темой, масштабированием и автозаполнением.
Canvas (и WebGL/WebGPU), напротив, позволяет получить доступ к низкоуровневому управлению сеткой пикселей для создания сложной 2D- и 3D-графики. Играм и сложным веб-приложениям (например, Google Документам или Figma) требуется высокопроизводительный доступ на низком уровне. Поскольку холст представляет собой сетку пикселей, для поддержки таких функций, как адаптивный текст, раньше требовалась сложная логика пользовательского интерфейса, что значительно увеличивало размер пакета. Важно отметить, что все мощные функции браузера, интегрированные в DOM, полностью перестают работать, когда интерфейс оказывается заперт в статической пиксельной сетке.
Преимущества использования DOM в Canvas
HTML-in-Canvas API – это мост, который позволяет использовать преимущества обоих подходов. Если поместить HTML-код в элемент <canvas> и синхронизировать его преобразование, контент останется полностью интерактивным, а все интеграции с браузером будут работать автоматически.
Вот что вы получите, если позволите DOM обрабатывать интерфейс в элементе <canvas>:
- Макет и форматирование текста. Упрощенный макет и форматирование текста, в том числе многострочного или двунаправленного, с применением стилей CSS.
- Элементы управления формами. Удобные и выразительные элементы управления формами с широкими возможностями настройки.
- Выделение текста, копирование и вставка, а также нажатие правой кнопки мыши. Пользователи могут выделять текст в 3D-сценах или открывать контекстное меню, нажав правую кнопку мыши.
- Специальные возможности. Контент, отрисованный внутри холста, доступен для дерева специальных возможностей. Системы специальных возможностей могут анализировать интерфейс так же, как обычный HTML, и предоставлять его программам чтения с экрана.
- Поиск на странице. Пользователи могут искать текст на странице (Ctrl/Cmd+F), и браузер будет выделять его прямо в текстурах WebGL.
- Индексируемость и совместимость с ИИ-агентами. Поисковые роботы и ИИ-агенты могут без проблем индексировать и читать текст, отображаемый в ваших 2D- и 3D-сценах.
- Интеграция расширений. Расширения браузера работают без изменений. Например, расширение для замены текста автоматически обновляет текст, отображаемый на ваших 3D-моделях.
- Интеграция с инструментами разработчика Chrome. Вы можете проверять контент холста, в том числе элементы интерфейса WebGL/WebGPU, прямо в инструментах разработчика Chrome. Измените стиль CSS в инспекторе, и он сразу же обновится на 3D-текстуре.
Примеры использования
Этот API открывает широкие возможности в разных областях:
- Приложения на основе большого холста. Теперь ресурсоемкие веб-приложения могут отрисовывать сложные компоненты интерфейса приложения на основе холста, что повышает доступность и уменьшает размер пакета.
- 3D-сцены и игры. Теперь на маркетинговых сайтах, в играх и других приложениях с поддержкой WebXR можно размещать полностью интерактивные веб-интерфейсы в 3D-сценах, например 3D-книгу с текстом, созданным с помощью DOM, или внутриигровой терминал с поддержкой копирования и вставки.
Как использовать API
Использование API состоит из трех этапов: настройки холста, отрисовки на холсте и обновления преобразования CSS, чтобы браузер знал, где элемент физически находится на экране.
Требования
HTML-in-Canvas API тестируется в Chrome 148–150. Чтобы протестировать ее на своем сайте, используйте Chrome Canary 149 или более поздней версии с включенным флагом chrome://flags/#canvas-draw-element. Чтобы включить API для других пользователей, зарегистрируйтесь в Origin Trial.
Шаг 1. Настройте основные параметры Canvas
Сначала добавьте в тег <canvas> атрибут layoutsubtree. Это позволяет браузеру узнать о контенте, вложенном в элемент canvas, подготовить его к показу внутри элемента canvas и сделать его доступным для вспомогательных технологий.
<canvas id="canvas" style="width: 200px; height: 200px;" layoutsubtree>
<div id="form_element">
<label for="name">Name:</label> <input id="name" type="text">
</div>
</canvas>
Как изменить размер сетки на холсте
Чтобы избежать размытости контента, убедитесь, что размер сетки холста соответствует коэффициенту масштабирования устройства.
const observer = new ResizeObserver(([entry]) => {
const dpc = entry.devicePixelContentBoxSize;
canvas.width = dpc ? dpc[0].inlineSize : Math.round(entry.contentRect.width * window.devicePixelRatio);
canvas.height = dpc ? dpc[0].blockSize : Math.round(entry.contentRect.height * window.devicePixelRatio);
});
const supportsDevicePixelContentBox =
typeof ResizeObserverEntry !== 'undefined' &&
'devicePixelContentBoxSize' in ResizeObserverEntry.prototype;
const options = supportsDevicePixelContentBox ? { box: 'device-pixel-content-box' } : {};
observer.observe(canvas, options);
Шаг 2. Рендеринг
Для 2D-контекста используйте метод drawElementImage. Сделайте это в событии paint, которое активируется при каждом перерисовывании элемента, например при выделении текста или вводе данных пользователем. Чтобы интерактивность продолжала работать, важно обновить преобразование CSS элемента с возвращаемым значением.
const ctx = document.getElementById('canvas').getContext('2d');
const form_element = document.getElementById('form_element');
const canvas = document.getElementById('canvas');
canvas.onpaint = () => {
ctx.reset();
// Draw the form element at x:0, y:0
let transform = ctx.drawElementImage(form_element, 0, 0);
// Use the transform returned later on...
};
Отрисовка с помощью WebGL
Для WebGL используется texElementImage2D. Она работает так же, как texImage2D, но в качестве источника использует элемент DOM.
canvas.onpaint = () => {
if (gl.texElementImage2D) {
gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, form_element);
}
};
Визуализация с помощью WebGPU
WebGPU использует метод copyElementImageToTexture в очереди устройства, аналогично copyExternalImageToTexture:
canvas.onpaint = () => {
root.device.queue.copyElementImageToTexture(
valueElement,
{ texture: targetTexture }
);
};
Шаг 3. Обновите преобразование CSS
После того как вы отрисовали элемент на холсте, вам нужно сообщить браузеру, где он находится. Это обеспечивает пространственную синхронизацию между холстом и макетом DOM. Это важно, чтобы браузер мог правильно сопоставить зону события (место, где пользователь нажал или навел указатель) с местом, где отрисован элемент.
В случае с 2D-контекстом примените к .style.transform property преобразование, возвращенное вызовом отрисовки:
const ctx = document.getElementById('canvas').getContext('2d');
const form_element = document.getElementById('form_element');
const canvas = document.getElementById('canvas');
canvas.onpaint = () => {
ctx.reset();
// Draw the form element at x:0, y:0
let transform = ctx.drawElementImage(form_element, 0, 0);
// Sync the DOM location with the drawn location
form_element.style.transform = transform.toString();
};
При использовании WebGL или WebGPU положение элемента на экране зависит от того, как выходная текстура используется кодом шейдера, и не может быть определено из контекста отрисовки холста. Однако если ваша программа шейдера использует стандартную модель проекции вида для отрисовки текстуры, то вы можете использовать новую удобную функцию element.getElementTransform() для вычисления преобразования, которое можно использовать так же, как возвращаемое значение из drawElementImage(). Чтобы это сделать, выполните следующие действия:
- Преобразование матрицы MVP WebGL в матрицу DOM.
- Нормализуйте элемент HTML. Размеры HTML-элементов задаются в пикселях (например, ширина – 200 пикселей). Однако в WebGL объекты обычно рассматриваются как единичные квадраты, например в диапазоне от 0 до 1. Если не нормализовать размер, кнопка размером 200 пикселей будет выглядеть в 200 раз больше.
- Сопоставить с областью просмотра макета. Это этап масштабирования. Он преобразует единицы измерения в пиксели, чтобы они соответствовали фактическим размерам элемента
<canvas>на экране. Также происходит отражение по оси Y, поскольку в WebGL положительное направление – вверх, а в CSS – вниз. - Рассчитайте окончательное преобразование. Умножьте матрицы в следующем порядке:
Viewport * MVP * Normalization.. Объединив их в одну итоговую матрицу преобразования, вы получите "карту", которая точно укажет браузеру, где должен находиться слой элемента HTML, чтобы он был выровнен по отношению к 3D-рисунку. - Примените преобразование к элементу HTML. Это перемещает слой элемента HTML непосредственно над его отрисованными пикселями. Это гарантирует, что при нажатии на кнопку или выборе текста пользователь взаимодействует с реальным элементом HTML.
if (canvas.getElementTransform) {
// 1. Convert WebGL MVP Matrix to DOM Matrix
const mvpDOM = new DOMMatrix(Array.from(htmlElementMVP));
// 2. Normalize the HTML element (pixels -> 1x1 unit square)
const width = targetHTMLElement.offsetWidth;
const height = targetHTMLElement.offsetHeight;
const cssToUnitSpace = new DOMMatrix()
.scale(1 / width, -1 / height, 1) // Shrink to unit size and flip Y
.translate(-width / 2, -height / 2); // Center the element
// 3. Map to the canvas viewport
const clipToCanvasViewport = new DOMMatrix()
.translate(canvas.width / 2, canvas.height / 2) // Move origin to center
.scale(canvas.width / 2, -canvas.height / 2, 1); // Stretch to canvas dimensions
// 4. Multiply: (Clip -> Pixels) * (MVP) * (pixels -> unit square)
const screenSpaceTransform = clipToCanvasViewport
.multiply(mvpDOM)
.multiply(cssToUnitSpace);
// 5. Apply to the transform
const computedTransform = canvas.getElementTransform(targetHTMLElement, screenSpaceTransform);
if (computedTransform) {
targetHTMLElement.style.transform = computedTransform.toString();
}
}
Поддержка библиотек и фреймворков
Некоторые популярные библиотеки уже поддерживают функцию HTML в Canvas.
Three.js
Обновлять матрицы вручную может быть утомительно, поэтому фреймворки уже начали внедрять эту функцию. В Three.js реализована экспериментальная поддержка с использованием нового элемента THREE.HTMLTexture:
const material = new THREE.MeshBasicMaterial();
material.map = new THREE.HTMLTexture(uiElement); // Pass the DOM element
const geometry = new THREE.BoxGeometry(1, 1, 1);
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
PlayCanvas
PlayCanvas также поддерживает HTML-в-Canvas с помощью API текстур:
// Wait for the 'paint' event to set the source
canvas.addEventListener('paint', () => {
htmlTexture.setSource(htmlElement);
}, { once: true });
canvas.requestPaint();
// Keep up to date
canvas.addEventListener('paint', onPaintUpload);
const material = new pc.StandardMaterial();
material.diffuseMap = htmlTexture;
material.update();
Деморолики
Прежде чем пробовать демоверсии, убедитесь, что ваша среда правильно настроена.
Существует несколько демонстрационных версий, которые можно использовать в качестве справочного материала по работе с API. Мы уже видим креативные решения от сообщества, от 3D-книг с возможностью перевода до элементов интерфейса, которые преломляются через шейдеры стекла:
- 3D-книга. 3D-книга, созданная с помощью WebGL и HTML. Пользователи могут менять шрифты с помощью CSS. Встроенный перевод работает мгновенно, поскольку основан на DOM, а ИИ-агенты могут извлекать текст с меньшими сложностями.
- Интерактивные 3D-интерфейсы. Слайдер WebGPU, который преломляет свет на основе лежащей в его основе 3D-модели и при этом реагирует на стандартные атрибуты шага HTML
<input type="range">. - Анимированные текстуры. Динамический 3D-билборд, на котором анимированный карандаш SVG отрисовывается с помощью DOM непосредственно в текстуру WebGL без необходимости использовать специальный цикл анимации.
- Рефракционные наложения. Интерактивный типографический слой, искаженный движущимся 3D-курсором, но при этом полностью доступный для выбора и поиска на странице.
Посмотрите демоверсии, созданные сообществом. Если вы хотите, чтобы ваш демопроект HTML-in-Canvas был добавлен в эту коллекцию, создайте pull-запрос.
Ограничения
У API есть несколько ограничений:
- Междоменный контент. По соображениям безопасности и конфиденциальности API не работает с междоменным контентом iframe.
- Прокрутка в основном потоке. HTML-код в элементе canvas отрисовывается с помощью JavaScript, поэтому прокрутка и анимация не могут обновляться независимо от JavaScript, как это происходит за пределами элемента canvas. Разработчикам следует тщательно продумать, как лучше реализовать прокрутку: только контента внутри элемента canvas или всего элемента.
Как оставить отзыв
Если вы экспериментируете с HTML-in-Canvas API, расскажите нам об этом. Вы можете зарегистрироваться для участия в пробном тестировании, чтобы включить эту функцию на своем сайте, пока она находится на экспериментальном этапе. Это поможет нам улучшить дизайн API. Вы также можете сообщить о проблеме.
Ресурсы
- Поддержка HTML-in-Canvas в Three.js
- Демоверсия HTML-in-Canvas в Three.js
- Поддержка HTML в Canvas в PlayCanvas: документация для разработчиков
- Демоверсия HTML-in-Canvas в PlayCanvas
- HTML-в-контейнере: объяснение
- Современные рекомендации по использованию инструментов ИИ для написания кода HTML в Canvas
- Демоверсии HTML в Canvas на сайте chrome.dev
- Коллекция демонстрационных проектов HTML в Canvas от сообщества