Реалізація 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) або онлайн-сервіси
# 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 — користувач бачить товар у своєму інтер'єрі. Паралельно готуємо USDZ-файли для iOS. Тестуємо на реальних пристроях.
Строк: AR-переглядання товару (Android + iOS) — 5–7 днів. Інтерактивна VR-сцена з контролерами — 8–12 днів.







