Пользователь открывает страницу на iPhone с Safari и видит кнопку «Посмотреть в AR» — но ничего не происходит. Модель не загружается, потому что WebXR AR не поддерживается на iOS. На Android Chrome та же модель работает идеально. Такая ситуация — типичная ошибка при внедрении иммерсивного опыта: без учёта платформенных различий половина аудитории остаётся за бортом. Мы реализуем WebXR-проекты под ключ: VR и AR опыт прямо в браузере, с корректным fallback для каждого устройства. За 5 лет мы сделали более 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. Потери качества при правильных настройках экспорта минимальны. Подробнее о форматах — в официальной документации WebXR.
Пример: оптимизация модели перед конвертацией
Для мобильных устройств рекомендуется снижать количество полигонов до 50 000, использовать текстуры 1024x1024 и объединять материалы. Это уменьшает размер модели на 60% и ускоряет загрузку.Что входит в работу
- Анализ целевых устройств и платформ
- Подготовка 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 дней.
- Интерактивная VR-сцена с контроллерами — 8–12 дней.
- Полный цикл с кастомизацией — от 14 дней.
Стоимость рассчитывается индивидуально. Наши инженеры имеют 10+ лет опыта в 3D-графике и веб-разработке. Получите консультацию по вашему проекту — мы оценим сложность и предложим оптимальное решение.







