Реалізація WebXR (VR/AR в браузері) на сайті
Користувач відкриває сторінку на iPhone з Safari і бачить кнопку «Подивитися в AR» — але нічого не відбувається. Модель не завантажується, тому що WebXR AR не підтримується на iOS. На Android Chrome та ж модель працює ідеально. Така ситуація — типова помилка при впровадженні імерсивного досвіду: без урахування платформних відмінностей половина аудиторії залишається за бортом. Ми реалізуємо WebXR-проекти під ключ: VR та AR досвід прямо в браузері, з коректним fallback для кожного пристрою. Наш досвід — понад 50 реалізованих проектів, що підтверджує гарантію якості. Зверніться до нас — отримайте сертифіковане рішення.
Типові проблеми при впровадженні WebXR
Перша помилка — ігнорування відмінностей платформ. Android Chrome підтримує immersive-ar; iOS — лише AR Quick Look через USDZ. Без fallback користувачі iPhone бачать порожній екран. Друга — продуктивність: важкі моделі з високополігональною геометрією викликають гальма на мобільних, а неправильне налаштування LOD збільшує час завантаження в 2–3 рази. Третя — відсутність hit-testing (розміщення об'єктів на реальних поверхнях) погіршує враження від AR. Економія часу на цьому етапі обертається втратою до 40% залученості. Зв'яжіться з нами для аудиту вашої 3D-моделі — ми підкажемо, як оптимізувати її під мобільні пристрої.
Як забезпечити підтримку на всіх пристроях?
Ми використовуємо комбінований підхід: визначаємо платформу через user-agent, для Android запускаємо WebXR AR-сесію з hit-testing, для iOS показуємо посилання на USDZ через AR Quick Look. На десктопі та для VR-гарнітур — immersive-vr. Детекція реалізується в кілька рядків, як показано нижче. Рекомендується також перевіряти підтримку через navigator.xr.isSessionSupported перед показом інтерфейсу.
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) const isAndroid = /Android/.test(navigator.userAgent) if (isIOS) { document.getElementById('ar-link')!.style.display = 'block' // USDZ } else if (isAndroid && await navigator.xr?.isSessionSupported('immersive-ar')) { document.getElementById('ar-button')!.style.display = 'block' // WebXR AR } Чому Three.js — найкращий вибір для кастомізації WebXR?
Three.js дає повний контроль над рендерингом, анімацією, контролерами. Він швидший за A-Frame в 2–3 рази при складній фізиці та великій кількості об'єктів. Реалізація hit-testing, ретикулів, реакції на контролери — на рівні API. A-Frame хороший для швидких прототипів, але для продакшену з унікальним UX обираємо Three.js. Альтернатива — React Three Fiber, який надає React-обгортку над Three.js та спрощує інтеграцію в сучасні проекти.
Three.js + WebXR
Three.js має вбудовану підтримку WebXR:
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 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) const button = mode === 'ar' ? ARButton.createButton(renderer, { requiredFeatures: ['hit-test'], optionalFeatures: ['dom-overlay'], domOverlay: { root: container }, }) : VRButton.createButton(renderer) document.body.appendChild(button) 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) } } 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) { 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) { 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 } Порівняння підходів для AR в браузері
| Параметр | WebXR AR | AR Quick Look | A-Frame AR |
|---|---|---|---|
| Підтримка iOS | Ні | Так | Ні |
| Інтерактивність | Висока (hit-test, контролери) | Низька (тільки перегляд) | Середня (обмежена) |
| Складність інтеграції | Середня | Низька (посилання+USDZ) | Низька |
| Продуктивність | Залежить від моделі | Нативна | Хороша |
| Кастомізація | Повна | Мінімальна | Середня |
Порівняння фреймворків: Three.js vs A-Frame
| Критерій | Three.js | A-Frame |
|---|---|---|
| Продуктивність | Висока (до 60fps на мобільних) | Середня (до 30fps на складних сценах) |
| Гнучкість | Повний контроль над рендерингом | Обмежений декларативними компонентами |
| Крива навчання | Крута (потребує знання 3D-графіки) | Полога (HTML-подібний синтаксис) |
| Підтримка WebXR | Повна (контролери, hit-test) | Базова (контролери через компоненти) |
iOS: AR Quick Look
Safari iOS не підтримує WebXR AR, але підтримує AR Quick Look через USDZ-файли:
<a href="/models/product.usdz" rel="ar" id="ar-link"> <img src="/models/product-preview.jpg" alt="Перегляд в AR" /> <span>Подивитися у вашому інтер'єрі</span> </a> Конвертація 3D-моделей
Для AR Quick Look потрібен USDZ, для WebXR — GLTF. Конвертацію виконуємо через Blender або утиліти командного рядка, наприклад, usd-from-gltf. Втрата якості при правильних налаштуваннях експорту мінімальна — зазвичай менше 5%. Детальніше про формати — в офіційній документації MDN WebXR.
Приклад: оптимізація моделі перед конвертацією
Для мобільних пристроїв рекомендується знижувати кількість полігонів до 50 000, використовувати текстури 1024x1024 та об'єднувати матеріали. Це зменшує розмір моделі на 60% і прискорює завантаження в 2 рази.Що входить в роботу
- Аналіз цільових пристроїв та платформ
- Підготовка 3D-моделей (оптимізація під мобільні, конвертація в необхідні формати)
- Реалізація WebXR AR/VR сцени з hit-testing та підтримкою контролерів
- Інтеграція iOS AR Quick Look з коректним fallback
- Тестування на реальних пристроях (Android, iPhone, Quest)
- Передача документації та вихідних кодів
- Навчання вашої команди роботі з рішенням
Етапи розробки
- Аналітика — визначаємо сценарії використання, обираємо стек (Three.js, A-Frame, React Three Fiber).
- Проектування — прототип взаємодії, схема сцени, оцінка бюджету.
- Реалізація — код WebXR, інтеграція з вашим сайтом, оптимізація продуктивності.
- Тестування — перевірка на гарнітурах і телефонах, виправлення помилок, замір Core Web Vitals.
- Деплой — налаштування HTTPS, публікація моделей, CDN для швидкого завантаження.
Терміни орієнтовно
- AR-перегляд продукту (Android + iOS) — 5–7 днів (вартість від $1500).
- Інтерактивна VR-сцена з контролерами — 8–12 днів (від $2500).
- Повний цикл з кастомізацією — від 14 днів (від $4000).
Вартість розраховується індивідуально. Наші інженери мають понад 10 років досвіду в 3D-графіці та веб-розробці, що є гарантією якості. Отримайте консультацію щодо вашого проекту — ми оцінимо складність і запропонуємо оптимальне рішення.







