HTML-оверлей поверх canvas

В Three.js сцена отрисовывается внутри элемента <canvas>, который является контейнером для WebGL-графики. В большинстве случаев взаимодействие с элементами интерфейса требует наложения HTML-компонентов поверх этого canvas. HTML-оверлей позволяет размещать текстовые подписи, кнопки, информационные панели и другие элементы UI, синхронизированные с 3D-сценой.

Основные принципы

  1. Структура DOM Элемент <canvas> располагается внутри контейнера, который может быть обычным <div>. Все HTML-элементы, предназначенные для оверлея, помещаются поверх canvas с помощью абсолютного позиционирования (position: absolute;).

    <div id="container" style="position: relative; width: 800px; height: 600px;">
        <canvas id="threeCanvas"></canvas>
        <div id="overlay" style="position: absolute; top: 0; left: 0; pointer-events: none;"></div>
    </div>

    Ключевой момент: использование pointer-events: none на оверлее предотвращает блокировку взаимодействия с canvas.

  2. Синхронизация позиции Чтобы HTML-элемент следовал за 3D-объектом, необходимо преобразовать координаты объекта из 3D-пространства в экранные координаты. Three.js предоставляет метод Vector3.project(camera) для проекции.

    const vector = new THREE.Vector3();
    const widthHalf = window.innerWidth / 2;
    const heightHalf = window.innerHeight / 2;
    
    function updateOverlayPosition(object3D, overlayElement, camera) {
        vector.copy(object3D.position);
        vector.project(camera);
    
        const x = (vector.x * widthHalf) + widthHalf;
        const y = -(vector.y * heightHalf) + heightHalf;
    
        overlayElement.style.transform = `translate(-50%, -50%) translate(${x}px, ${y}px)`;
    }

    Здесь vector.project(camera) преобразует 3D-координаты в нормализованные координаты устройства (NDC), а последующее масштабирование переводит их в координаты экрана.

  3. Обновление при рендеринге Для динамичных сцен позиция оверлеев обновляется в цикле рендера:

    function animate() {
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
        updateOverlayPosition(mesh, document.getElementById('overlay'), camera);
    }
    animate();

    Это гарантирует точное позиционирование при вращении камеры или движении объекта.

Работа с несколькими оверлеями

Для нескольких элементов можно хранить массив объектов с их соответствующими HTML-элементами и обновлять их позицию циклически:

const overlays = [
    { object: mesh1, element: document.getElementById('overlay1') },
    { object: mesh2, element: document.getElementById('overlay2') }
];

function updateOverlays() {
    overlays.forEach(({ object, element }) => updateOverlayPosition(object, element, camera));
}

Вызывается внутри animate() после рендера сцены.

Важные аспекты

  • Скрытие объектов вне экрана: если объект находится за камерой или за пределами видимой области, оверлей следует скрыть. Это можно сделать проверкой vector.z после project():

    if (vector.z < -1 || vector.z > 1) {
        overlayElement.style.display = 'none';
    } else {
        overlayElement.style.display = 'block';
    }
  • Масштабирование и адаптивность: при изменении размера окна необходимо пересчитывать widthHalf и heightHalf, а также обновлять размеры контейнера и canvas:

    window.addEventListener('resize', () => {
        widthHalf = window.innerWidth / 2;
        heightHalf = window.innerHeight / 2;
        renderer.setSize(window.innerWidth, window.innerHeight);
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
    });
  • Стилизация оверлеев: CSS позволяет применять анимации, шрифты, цвета и тени для элементов, не затрагивая WebGL. Это удобно для создания подсказок или HUD-интерфейсов.

Интеграция с событиями

HTML-оверлей может обрабатывать клики, наведение и другие события без влияния на WebGL-сцену. Если необходимо реагировать на клики по 3D-объектам, рекомендуется использовать Raycaster для определения попаданий и синхронизировать их с оверлеем.

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

window.addEventListener('click', (event) => {
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObjects(scene.children, true);
    if (intersects.length > 0) {
        console.log('Clicked on', intersects[0].object);
    }
});

Оптимизация

  • Минимизировать количество DOM-элементов на сцене для повышения производительности.
  • Использовать requestAnimationFrame для синхронизации обновлений.
  • Для статических объектов позицию можно вычислять один раз, чтобы не перегружать рендер-цикл.

HTML-оверлей в Three.js обеспечивает гибкое и мощное средство интеграции UI с 3D-графикой. Правильное управление проекцией координат, видимостью и обновлением элементов позволяет создавать информативные и интерактивные интерфейсы без ущерба для производительности сцены.