С развитием аппаратных платформ пространственных вычислений (Spatial Computing: Apple Vision Pro, Meta Quest 3) веб-разработка перешла от двумерных плоских экранов к погружению интерфейса в физическое трехмерное пространство пользователя. Технологическим фундаментом этой эволюции выступает спецификация W3C WebXR Device API.
В отличие от стандартного рендеринга на плоский HTML Canvas, конвейер WebXR вносит фундаментальные изменения в графический цикл:
┌─────────────────────────────────────────────────────────────────────────────┐
│ WEBXR RUNTIME ARCHITECTURE │
│ │
│ [Hardware Headset Sensors] ──► SLAM Tracking & Hand / Eye Tracking │
│ │ │
│ ▼ │
│ [WebXR Device API Session] (XRSession: 'immersive-vr' | 'immersive-ar') │
│ │ │
│ ▼ │
│ [XRFrame Pipeline] ──► XRReferenceSpace: локальная привязка к полу комнаты │
│ │ ──► XRView (Left Eye Matrix4, Right Eye Matrix4) │
│ │ ──► XRInputSource: Лучи контроллеров и суставы рук │
│ ▼ │
│ [React Three Fiber + @react-three/xr Store] │
│ │ │
│ ▼ │
│ [WebGL / WebGPU Compositor] ──► Аппаратный вывод в линзы шлема на 90/120 Hz│
└─────────────────────────────────────────────────────────────────────────────┘Параллельно с нативным WebXR встает задача интеграции привычных пользовательских интерфейсов. Создавать сложные формы ввода, таблицы и тексты процедурной полигональной геометрией в 3D крайне неэффективно. Компонент <Html> из @react-three/drei реализует гибридную архитектуру: он проецирует стандартные HTML/CSS элементы поверх WebGL холста, математически синхронизируя CSS 3D Transforms (matrix3d) с мировой матрицей трехмерного узла.
Ментальная модель пространственного приложения в React Three Fiber разделяет окружение на три координатных пространства:
СИСТЕМЫ КООРДИНАТ
┌───────────────────────────────────────────────────────────────────────────┐
│ 1. World Space (Мировые координаты комнаты / XRReferenceSpace) │
│ - Виртуальные объекты, столы, мебель, физические препятствия. │
└─────────────────────────────────────┬─────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────────────────┐
│ 2. Camera Space / Lazy HUD (Пространство головы пользователя) │
│ - Heads-Up Display: плавает перед глазами с демпфированием (Spring LERP│
└─────────────────────────────────────┬─────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────────────────┐
│ 3. Screen-Projected DOM Layer (Гибридный Drei <Html> оверлей) │
│ - Обычные React-компоненты с Tailwind, CSS-анимациями и формами ввода │
└───────────────────────────────────────────────────────────────────────────┘<Html transform occlude>:
transform: Проецирует div в 3D-пространство с использованием CSS свойства transform: matrix3d(...). При удалении камеры HTML-элемент уменьшается по законам перспективы.occlude: Проверяет видимость элемента. Если между камерой и точкой крепления интерфейса находится непрозрачный трехмерный меш, <Html> динамически скрывается (через CSS opacity: 0 или display: none) на основе Raycasting.useXRInputSourceState): Взаимодействие осуществляется либо 6DoF-контроллерами с лазерными указками, либо трекингом рук (Hand Tracking), где жест щипка (Pinch Gesture между указательным и большим пальцем) трансформируется в событие пространственного клика.camera.add(hud)) быстро утомляет зрение и провоцирует укачивание. Промышленным стандартом является упругое следование (Lazy Damping): панель плавно подтягивается к полю зрения пользователя при поворотах головы с задержкой и сглаживанием интерполяцией кватернионов (SLERP).Запрос сессии 'immersive-vr' или 'immersive-ar' с проверкой аппаратной поддержки шлема в браузере.
WebXR передает в Three.js матрицы проекции и вида для каждого глаза, обновляя стерео-камеру ArrayCamera.
Проекция мировых координат узла в экранные пиксели и расчет матрицы CSS matrix3d с проверкой окклюзии.
Хук useFrame покадрово подтягивает позицию интерфейса к вектору взгляда камеры через экспоненциальный LERP/SLERP.
Каким образом компонент <Html transform occlude> из библиотеки @react-three/drei определяет, что трехмерный объект заслонил HTML-интерфейс, и скрывает его?
В корпоративных аналитических панелях операторы работают в смешанной реальности (Pass-through AR). Графики, таблицы и диаграммы отображаются в виде полупрозрачных парящих карточек с эффектом размытия стекла (Glassmorphism). Использование <Html transform> позволяет повторно использовать существующие React-компоненты дизайн-системы компании (Radix UI, Tailwind CSS, Recharts) прямо в 3D без переписывания на шейдеры.
В промышленных тренажерах для инженеров трехмерная модель двигателя дополняется информационными метками деталей. При наведении взгляда или лазерного луча контроллера на узел генератора рядом всплывает интерактивная карточка с инструкцией по замене и кнопками вызова спецификаций, которая автоматически скрывается, если деталь закрыта корпусом (occlude="blending").
Хирург во время операции в шлеме дополненной реальности должен непрерывно видеть жизненные показатели пациента (пульс, давление, ЭКГ). Информация выводится в виде плавающего 3D HUD, зафиксированного в верхнем углу периферийного зрения. Плавное демпфирование следования (Lazy HUD) гарантирует, что при быстрых поворотах головы панель не загораживает операционное поле, но всегда возвращается в поле зрения.
В VR-шоурумах пользователи взаимодействуют со сценой без физических пластиковых контроллеров. Шлем с помощью оптических камер отслеживает положение 25 суставов каждой кисти руки (XRHand). Соединение кончиков большого и указательного пальцев (Pinch) детектируется алгоритмом как пространственный клик по виртуальным кнопкам и слайдерам.
<Html transform>#Компонент <Html> не рендерит разметку внутрь WebGL-текстуры. Он создает абсолютный HTML-контейнер div поверх холста <canvas>. На каждом кадре хука useFrame выполняются следующие матричные преобразования:
vector.project(camera).[-1..1]) конвертируются в пиксельные координаты экрана:
x = (vector.x * 0.5 + 0.5) * canvasWidth;
y = (-(vector.y * 0.5) + 0.5) * canvasHeight;transform рассчитывается полная матрица matrix3d:
Инвертированная ориентация камеры и мировая трансформация родительского меша упаковываются в 16 значений CSS-функции matrix3d(...).Рассмотрим архитектуру пространственного интерфейса с использованием @react-three/xr и Drei.
import React, { useRef, useState } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import { Html, Float } from '@react-three/drei';
import {
createXRStore,
XR,
useXR,
useXRInputSourceState,
} from '@react-three/xr';
import * as THREE from 'three';
// 1. Создание глобального хранилища состояния WebXR
const xrStore = createXRStore({
emulate: {
type: 'metaQuest3', // Эмуляция шлема в браузере при разработке
},
});
// 2. Эргономичный плавающий 3D HUD (Lazy Follow)
function LazyHUD() {
const hudRef = useRef<THREE.Group>(null);
// Вектор целевой позиции перед глазами пользователя
const targetPos = useRef(new THREE.Vector3());
const targetQuat = useRef(new THREE.Quaternion());
useFrame(({ camera }, delta) => {
if (!hudRef.current) return;
// Смещение HUD на 1.2 метра вперед по направлению взгляда и чуть вниз
const forward = new THREE.Vector3(0, -0.2, -1.2);
forward.applyQuaternion(camera.quaternion);
targetPos.current.copy(camera.position).add(forward);
// Ориентация HUD лицом к камере
targetQuat.current.copy(camera.quaternion);
// Мягкое экспоненциальное сглаживание перемещения (LERP) и вращения (SLERP)
// Предотвращает дергание интерфейса при микро-движениях головы
hudRef.current.position.lerp(targetPos.current, delta * 4);
hudRef.current.quaternion.slerp(targetQuat.current, delta * 4);
});
return (
<group ref={hudRef}>
{/* 3D подложка HUD */}
<mesh>
<planeGeometry args={[0.6, 0.25]} />
<meshStandardMaterial
color="#0f172a"
transparent
opacity={0.85}
roughness={0.2}
metalness={0.8}
/>
</mesh>
{/* Текстовые данные телеметрии */}
<Html transform position={[0, 0, 0.01]} distanceFactor={1.5}>
<div className="w-[300px] p-4 text-white font-sans select-none pointer-events-none">
<div className="flex justify-between items-center text-xs font-mono text-cyan-400">
<span>SPATIAL HUD</span>
<span className="animate-pulse">● LIVE</span>
</div>
<div className="text-sm font-semibold mt-1">Система стабилизирована</div>
<div className="text-[10px] text-slate-400 mt-2">
IPD: 64mm | Refresh: 120Hz | 6DoF Active
</div>
</div>
</Html>
</group>
);
}
// 3. Интерактивная 3D карточка с Drei Html и окклюзией
function InteractiveSpatialCard({ position }: { position: [number, number, number] }) {
const [likes, setLikes] = useState(0);
return (
<group position={position}>
{/* Физический маркер в 3D мире */}
<mesh>
<cylinderGeometry args={[0.02, 0.02, 0.8]} />
<meshStandardMaterial color="#64748b" />
</mesh>
{/* Интерактивный HTML/CSS оверлей с окклюзией */}
<Html
transform
occlude // Скрывать элемент, если перед ним находится геометрия
position={[0, 0.5, 0]}
distanceFactor={2.5}
className="pointer-events-auto"
>
<div className="bg-slate-900/90 backdrop-blur-md border border-cyan-500/40 p-4 rounded-xl shadow-2xl w-64 text-white">
<h3 className="font-bold text-sm text-cyan-300">Квантовый Сенсор v4</h3>
<p className="text-xs text-slate-300 mt-1">
Гибридный HTML компонент, проецируемый в пространство WebXR.
</p>
<div className="mt-3 flex items-center justify-between">
<button
onClick={() => setLikes((l) => l + 1)}
className="px-3 py-1 bg-cyan-600 hover:bg-cyan-500 text-xs font-semibold rounded-lg transition-colors cursor-pointer"
>
Импульс (+{likes})
</button>
<span className="text-[11px] font-mono text-emerald-400">ОККЛЮЗИЯ: ON</span>
</div>
</div>
</Html>
</group>
);
}
// 4. Корневой компонент пространственного приложения
export function SpatialApp() {
return (
<div className="w-full h-[650px] bg-black rounded-2xl overflow-hidden relative">
{/* Кнопка входа в режим виртуальной / дополненной реальности */}
<div className="absolute top-4 right-4 z-10 flex gap-2">
<button
onClick={() => xrStore.enterVR()}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold rounded-xl shadow-lg transition-colors"
>
ВОЙТИ В VR
</button>
<button
onClick={() => xrStore.enterAR()}
className="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-bold rounded-xl shadow-lg transition-colors"
>
ВОЙТИ В AR
</button>
</div>
<Canvas camera={{ position: [0, 1.6, 2], fov: 60 }}>
{/* Интеграция WebXR сессии */}
<XR store={xrStore}>
<ambientLight intensity={0.6} />
<directionalLight position={[4, 6, 4]} intensity={1.5} />
{/* Плавающий пользовательский интерфейс HUD */}
<LazyHUD />
{/* Интерактивная пространственная карточка в координатах комнаты */}
<InteractiveSpatialCard position={[-1, 1.2, -1.5]} />
{/* Препятствие для демонстрации работы свойства occlude */}
<mesh position={[-0.8, 1.2, -0.8]}>
<sphereGeometry args={[0.25, 32, 32]} />
<meshStandardMaterial color="#ec4899" roughness={0.3} />
</mesh>
{/* Опорная сетка пола комнаты */}
<gridHelper args={[10, 10, '#38bdf8', '#1e293b']} position={[0, 0, 0]} />
</XR>
</Canvas>
</div>
);
}WebXR получает 6DoF позицию шлема и ориентацию контроллеров/суставов рук из аппаратного драйвера.
Задержка трекинга составляет менее 3 мс.
Three.js конфигурирует две подкамеры (Left Eye и Right Eye) с индивидуальными проекционными матрицами.
Учитывает физическое расстояние между зрачками (IPD).
Преобразование координат трехмерного узла в 16-значную матрицу matrix3d с расчетом окклюзии через Raycaster.
DOM обновляется в том же кадре анимации.
Интерполяция положения HUD в хуке useFrame с помощью LERP/SLERP для устранения эффекта укачивания.
Сглаживает высокочастотное дрожание головы.
Команда привязала интерфейс дашборда напрямую к камере: camera.add(hudMesh), зафиксировав его строго в центре поля зрения. В тестах пользователи пожаловались на тошноту и утомление глаз уже через 3 минуты использования. В чем физиологическая причина и как правильно спроектировать этот узел?
Каждый экземпляр <Html> на каждом кадре запускает вычисления матриц проекции, конвертацию в CSS-стили и замеры экранных координат. Создание более 30–50 таких элементов перегружает Main Thread и вызывает деградацию частоты кадров ниже 60 FPS.
Решение: Ограничивайте число активных <Html> оверлеев. Для массовых маркеров (например, тысячи меток на карте) используйте единый InstancedMesh со шрифтовыми атласами MSDF (Signed Distance Field шрифты через Drei <Text>).
Размещение трехмерного интерфейса ближе 0.5–0.7 метра от глаз пользователя приводит к конфликту вергенции и аккомодации (глазные мышцы напрягаются для сведения глаз, но фокус линзы шлема зафиксирован на расстоянии 1.5–2 метров). Это вызывает резь в глазах и головную боль. Решение: Размещайте пространственные карточки и HUD на комфортной дистанции от 1.2 до 2.0 метров, масштабируя их размер для сохранения читаемости шрифта.
Стандартные события onClick мыши работают на десктопе, но в шлеме виртуальной реальности курсор отсутствует. Пользователь указывает на объекты лучом контроллера или жестом щипка.
Решение: Используйте абстракции @react-three/xr, где события клика нормализованы под пространственные лучи контроллеров и щипки трекинга рук (onPointerDown, select).
Без указания distanceFactor проецируемый HTML-элемент сохраняет свой исходный пиксельный размер на экране. При приближении к нему в 3D пространстве он не увеличивается, а при удалении — перекрывает всю сцену гигантским полотном.
Решение: Всегда явно задавайте transform и distanceFactor={...} для физически корректного перспективного масштабирования элемента в мировых координатах.
<Html transform occlude> из @react-three/drei объединяет возможности стандартной веб-верстки (Tailwind, React) с трехмерным миром через проекцию CSS matrix3d.@react-three/xr обеспечивает унифицированное управление сессиями VR/AR, лучами пространственных контроллеров и естественным трекингом рук (Hand Tracking).