Реализация WebXR (VR/AR в браузере) на сайте
WebXR Device API позволяет запускать VR и AR опыт прямо в браузере — без установки приложений. Пользователь открывает страницу в Chrome на Android или Safari на iPhone и попадает в дополненную реальность. На десктопе с VR-гарнитурой (Meta Quest Browser, Valve Index + SteamVR) — в виртуальную.
Поддержка браузерами
AR (immersive-ar): Chrome Android 90+, Samsung Internet 14+. iOS/Safari — через WebXR Viewer или нативный AR Quick Look (USDZ-файлы).
VR (immersive-vr): Chrome Android с гарнитурой Cardboard, Meta Quest Browser, Firefox Reality, Valve Index через SteamVR.
Inline (3D в странице без гарнитуры): все браузеры с WebGL.
Проверка поддержки:
const isARSupported = await navigator.xr?.isSessionSupported('immersive-ar')
const isVRSupported = await navigator.xr?.isSessionSupported('immersive-vr')
Three.js + WebXR
Three.js имеет встроенную поддержку WebXR:
npm install three @types/three
import * as THREE from 'three'
import { ARButton } from 'three/examples/jsm/webxr/ARButton'
import { VRButton } from 'three/examples/jsm/webxr/VRButton'
import { XRControllerModelFactory } from 'three/examples/jsm/webxr/XRControllerModelFactory'
function WebXRScene({ mode }: { mode: 'ar' | 'vr' }) {
const mountRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const container = mountRef.current!
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(container.clientWidth, container.clientHeight)
renderer.xr.enabled = true // Включаем WebXR
container.appendChild(renderer.domElement)
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(70, container.clientWidth / container.clientHeight, 0.01, 100)
// Освещение
scene.add(new THREE.AmbientLight(0xffffff, 1))
const dirLight = new THREE.DirectionalLight(0xffffff, 2)
dirLight.position.set(0, 5, 3)
scene.add(dirLight)
// Кнопка AR/VR
const button = mode === 'ar'
? ARButton.createButton(renderer, {
requiredFeatures: ['hit-test'], // Обнаружение поверхностей
optionalFeatures: ['dom-overlay'], // UI поверх AR
domOverlay: { root: container },
})
: VRButton.createButton(renderer)
document.body.appendChild(button)
// Контроллеры VR
if (mode === 'vr') {
const controllerModelFactory = new XRControllerModelFactory()
for (let i = 0; i < 2; i++) {
const controller = renderer.xr.getController(i)
controller.addEventListener('selectstart', onSelectStart)
controller.addEventListener('selectend', onSelectEnd)
scene.add(controller)
const controllerGrip = renderer.xr.getControllerGrip(i)
controllerGrip.add(controllerModelFactory.createControllerModel(controllerGrip))
scene.add(controllerGrip)
}
}
// Hit testing для AR (размещение объектов на поверхностях)
let hitTestSource: XRHitTestSource | null = null
let hitTestSourceRequested = false
const reticle = createReticle()
scene.add(reticle)
renderer.xr.addEventListener('sessionstart', async () => {
if (mode !== 'ar') return
const session = renderer.xr.getSession()!
const viewerSpace = await session.requestReferenceSpace('viewer')
hitTestSource = await session.requestHitTestSource!({ space: viewerSpace })!
})
renderer.setAnimationLoop((timestamp, frame) => {
if (mode === 'ar' && frame) {
// Hit test — находим поверхность под камерой
const referenceSpace = renderer.xr.getReferenceSpace()!
const hitTestResults = frame.getHitTestResults(hitTestSource!)
if (hitTestResults.length > 0) {
const hit = hitTestResults[0]
const pose = hit.getPose(referenceSpace)
if (pose) {
reticle.visible = true
reticle.matrix.fromArray(pose.transform.matrix)
}
} else {
reticle.visible = false
}
}
renderer.render(scene, camera)
})
function onSelectStart(event: THREE.Event) {
// При нажатии на контроллер в AR — размещаем объект на поверхности
if (reticle.visible) {
const geometry = new THREE.BoxGeometry(0.1, 0.1, 0.1)
const material = new THREE.MeshStandardMaterial({ color: 0x2563eb })
const mesh = new THREE.Mesh(geometry, material)
mesh.position.setFromMatrixPosition(reticle.matrix)
mesh.quaternion.setFromRotationMatrix(reticle.matrix)
scene.add(mesh)
}
}
return () => {
renderer.setAnimationLoop(null)
renderer.dispose()
button.remove()
container.removeChild(renderer.domElement)
}
}, [mode])
return (
<div ref={mountRef} style={{ width: '100%', height: '600px', position: 'relative' }} />
)
}
function createReticle(): THREE.Mesh {
const geometry = new THREE.RingGeometry(0.05, 0.07, 32).rotateX(-Math.PI / 2)
const material = new THREE.MeshBasicMaterial({ color: 0xffffff, side: THREE.DoubleSide })
const reticle = new THREE.Mesh(geometry, material)
reticle.matrixAutoUpdate = false
reticle.visible = false
return reticle
}
A-Frame: декларативный WebXR
Для более простых VR-сцен без глубокой кастомизации:
npm install aframe
<!-- Полноценная VR-сцена в HTML -->
<a-scene>
<a-sky color="#1a1a2e"></a-sky>
<!-- Окружение -->
<a-plane position="0 0 0" rotation="-90 0 0" width="20" height="20" color="#0a0a1a"></a-plane>
<!-- Интерактивный объект -->
<a-box
position="-1 1 -3"
rotation="0 45 0"
color="#2563eb"
animation="property: rotation; to: 0 405 0; loop: true; dur: 4000; easing: linear"
event-set__mouseenter="color: #60a5fa"
event-set__mouseleave="color: #2563eb"
cursor-listener
></a-box>
<!-- Камера с курсором для гарнитур без контроллеров -->
<a-camera>
<a-cursor
animation__click="property: scale; startEvents: click; from: 0.1 0.1 0.1; to: 1 1 1; dur: 150"
></a-cursor>
</a-camera>
</a-scene>
iOS: AR Quick Look
Safari iOS не поддерживает WebXR AR, но поддерживает AR Quick Look через USDZ-файлы:
<!-- Нативный AR на iOS через USDZ -->
<a
href="/models/product.usdz"
rel="ar"
id="ar-link"
>
<img src="/models/product-preview.jpg" alt="Просмотр в AR" />
<span>Посмотреть в вашем интерьере</span>
</a>
// Определяем платформу и показываем нужную кнопку
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
const isAndroid = /Android/.test(navigator.userAgent)
if (isIOS) {
// Показываем ссылку на USDZ
document.getElementById('ar-link')!.style.display = 'block'
} else if (isAndroid && await navigator.xr?.isSessionSupported('immersive-ar')) {
// WebXR AR для Android Chrome
document.getElementById('ar-button')!.style.display = 'block'
}
Конвертация 3D-моделей
Для AR Quick Look нужен USDZ (iOS), для WebXR — GLTF. Конвертация через Blender или CLI:
# GLTF → USDZ через Apple Reality Converter (macOS) или online-сервисы
# GLB → USDZ через usd-from-gltf (npm)
npm install -g usd-from-gltf
gltf-to-usd model.glb model.usdz
Что делаем
Оцениваем целевые устройства (Android WebXR, iOS USDZ, VR-гарнитуры). Реализуем AR-просмотр продукта с hit-testing — пользователь видит товар в своём интерьере. Для iOS параллельно готовим USDZ-файлы. Тестируем на реальных устройствах.
Срок: AR-просмотр продукта (Android + iOS) — 5–7 дней. Интерактивная VR-сцена с контроллерами — 8–12 дней.







