Чтобы 3D-сайт работал быстро, нужны три решения. Ещё до моделирования задайте жёсткие лимиты по числу треугольников, размеру файлов и вызовам отрисовки. Сожмите все материалы: геометрию с помощью Draco или meshopt, текстуры с помощью KTX2. Загружайте 3D только после первого отображения HTML-страницы, а для слабых телефонов предусмотрите статичное изображение или видео. Тогда 3D в реальном времени может укладываться в хорошие показатели Google Core Web Vitals.
Рассказываем, как мы делаем это в ASFARI digital.
Сначала задайте ограничения для 3D
Требования к скорости закладывают в задание, а не оставляют на последнюю неделю. Для сцены на первом экране мобильного сайта мы используем такие ориентиры. Это наши рабочие ограничения, а не отраслевой стандарт.
| Параметр | Рабочий ориентир ASFARI digital |
|---|---|
| Треугольники в сцене первого экрана на смартфоне | ≤ 100 000 |
| Модель первого экрана, сжатый GLB | ≤ 2 МБ |
| Текстуры | 1K-2K, KTX2 |
| Вызовы отрисовки | ≤ 50 |
| Готовность к первому действию на смартфоне среднего класса | Менее 3 с |
| 3D-модуль | Загружается после первого отображения страницы |

Вызов отрисовки - это одна команда от браузера графическому процессору: «нарисуй эту сетку с этим материалом». Несколько крупных вызовов эффективнее множества мелких. Поэтому объединение сеток и инстансинг, то есть отрисовка повторяющихся объектов по одному образцу, важнее экономии нескольких тысяч треугольников.
Сжатие glTF: Draco, meshopt и KTX2
glTF - стандартный формат 3D для веба (Khronos). Файлы после экспорта бывают большими, но сжатие часто существенно уменьшает их размер.
- Draco сжимает геометрию сетки. Хорошо подходит для плотных сеток, но требует немного времени на декодирование.
- meshopt - другой способ сжатия геометрии с очень быстрым декодированием. Стоит сравнить его с Draco на вашей модели.
- KTX2 / Basis Universal сжимает текстуры в формат, с которым графический процессор работает напрямую. JPG или PNG могут мало весить при загрузке, но в памяти GPU разворачиваются до полного размера. KTX2 остаётся сжатым и там, что помогает избежать сбоев на телефонах.
С открытым инструментом командной строки glTF-Transform первая обработка выглядит так (команды из документации):
npm install --global @gltf-transform/cli
gltf-transform inspect hero.glb # треугольники, текстуры, размеры
gltf-transform optimize hero.glb hero-opt.glb --compress draco --texture-compress webp
Для KTX2 используйте соответствующие команды обработки текстур вместо WebP. Для них потребуется установленный KTX-Software. Затем настройте декодирование обоих форматов в Three.js:
const draco = new DRACOLoader().setDecoderPath('/draco/');
const ktx2 = new KTX2Loader().setTranscoderPath('/basis/').detectSupport(renderer);
const loader = new GLTFLoader().setDRACOLoader(draco).setKTX2Loader(ktx2);
Запустите inspect до и после обработки. Всё ещё выходите за лимиты? Скорее всего, нужно доработать модель в Blender, а не сильнее сжимать файл.
Загружайте 3D после первого отображения страницы
Библиотека 3D вместе с моделями может занимать много места. По данным Utsubo, минифицированное ядро Three.js весит около 600 КБ, а с вспомогательными модулями, постобработкой и GLB - более 3 МБ. Если всё это блокирует загрузку страницы, ухудшается показатель Largest Contentful Paint.

Порядок действий:
- Сформируйте HTML на сервере, включая заголовок и призыв к действию.
- Покажите статичное изображение в блоке с фиксированным соотношением сторон, чтобы появление canvas не сдвигало содержимое.
- Импортируйте код 3D, когда браузер не занят другими задачами или пользователь прокрутит до нужного блока.
- Когда первый кадр готов, плавно замените статичное изображение на canvas.
const start = () => import('./hero-scene.js').then(m => m.mount(canvas));
'requestIdleCallback' in window ? requestIdleCallback(start) : setTimeout(start, 200);
Так же устроен глобус на главной странице GitHub. Пока первый кадр не готов, видна SVG-заглушка. Затем она плавно сменяется canvas за 600 мс. Этот подход важен и для поиска; подробнее в статье «SEO для WebGL-сайтов».
Скорость WebGL на смартфонах: режимы для разных устройств
Не нужно показывать одну и ту же сцену на всех телефонах. Предусмотрите как минимум три режима:
- Высокое качество: полная сцена, коэффициент плотности пикселей ограничен значением 2 (
renderer.setPixelRatio(Math.min(devicePixelRatio, 2))). - Низкое качество: меньше частиц, без постобработки, коэффициент плотности пикселей 1-1,5.
- Запасной вариант: заранее отрендеренное зацикленное видео или статичное изображение. Видео без звука с атрибутом
playsinlineможет автоматически воспроизводиться внутри страницы на iOS.
Адаптируйте качество и во время работы. Глобус GitHub отслеживает частоту кадров и снижает качество, если не удерживает 55,5 FPS за последние 50 кадров. Он последовательно проходит четыре уровня качества: снижает плотность пикселей, реже проверяет пересечения лучей и уменьшает число точек примерно на треть.
Чтобы оптимизировать Three.js во время разработки, записывайте значения renderer.info.render.calls и renderer.info.render.triangles для каждого кадра и сравнивайте с лимитами. Учитывайте настройку prefers-reduced-motion: пользователю, который выбрал уменьшение движения, показывайте статичное изображение вместо вращающейся сцены.
Проверяйте показатели Core Web Vitals
Google считает хорошими показатели LCP ≤ 2,5 с, INP ≤ 200 мс и CLS ≤ 0,1. На странице с 3D самым крупным элементом для LCP часто становится canvas или статичное изображение. Долгая компиляция шейдеров ухудшает INP.

По оценке Utsubo, компиляция одного шейдера занимает 50-300 мс, а декодирование крупной текстуры - 50-200 мс. Поэтому подготавливайте шейдеры до первого действия пользователя. Общие вопросы анимации разбираем в статье «Замедляет ли анимация сайт?». Если нужна отдельная модель товара, а не целая сцена, читайте «Как добавить интерактивную 3D-модель на сайт».



