Продуктовые сайты любят анимацию «крутишь колесо мыши — объект меняется на глазах». Первый инстинкт: сделать это видео с currentTime. Вот почему это почти всегда неправильный первый инстинкт, и что вместо этого используют на практике.
Эффект знаком почти всем: скроллишь страницу, и товар на экране поворачивается, открывается, меняет состояние, будто это видео, которым управляет колесо мыши. Реализовать это «в лоб» пытаются почти всегда одинаково: берут файл видео и дёргают video.currentTime от scroll‑позиции. Через пару дней разработки выясняется, почему так почти никто не делает всерьёз.
Почему видео — ловушка, а не короткий путь
Дело не во вкусе. У формата видео для этой конкретной задачи есть три структурных ограничения:
Seek — не мгновенная операция. Декодер восстанавливает кадр от ближайшего keyframe вперёд, а не читает произвольный кадр напрямую. Скролл генерирует десятки programmatic seek‑вызовов в секунду быстрее, чем большинство декодеров успевают отработать чисто, и разные браузеры в этот момент ведут себя по‑разному: где‑то кадры «проглатываются», где‑то seek тихо схлопывается с предыдущим.
Нет надёжной прозрачности. Если объект нужно вырезать и наложить на остальной контент страницы видео с альфа‑каналом до сих пор плохо и не универсально поддерживается кросс‑браузерно, а PNG/WebP с альфой работают из коробки везде.
Мобильные ограничения на автовоспроизведение и фоновый decode добавляют ещё один слой условий, которого просто нет у обычной картинки.
Поэтому для покадрового reveal индустрия чаще уходит от видео к последовательности изображений на canvas — распространённый в дев‑сообществе паттерн для product‑страниц, хотя внутреннюю реализацию конкретных чужих сайтов я не проверял и не выдаю это за факт о какой‑то одной компании.
Пять способов — и на чём каждый спотыкается
Стоит сравнить не «видео vs картинки», а весь спектр — у каждого подхода свой набор компромиссов.
Технология | Точность скраба | Альфа | Вес | Браузеры | Годится для |
|---|---|---|---|---|---|
Video + currentTime | Низкая зависит от keyframe | нет | отличный | Везде, но seek ведёт себя по‑разному | Простой ролик без построчного скраба |
Image sequence (canvas) | Высокая кадр = индекс массива | да | Плохой без оптимизации | Везде | Продуктовый reveal, вырезанный объект |
Спрайт + CSS steps() + scroll‑timeline | Высокая | да | Средний один спрайт‑лист | Chrome/Edge/Safari — да, стабильный Firefox ещё нет (только Nightly) | Короткие анимации без единой строчки JS |
WebGL / вектор (Lottie, three.js) | Высокая, интерполяция нативна | да | отличный векторные данные | Хорошая | Иллюстративный контент, не фото |
WebCodecs (ручной decode) | Высокая — полный контроль над кадром | почти нет зависит от кодека источника | отличный сжатие видео | Chrome, Edge, десктоп Firefox и Safari — кроме Firefox для Android | Уже практично кросс‑браузерно, с фоллбэком под мобильный Firefox |
Строка выделена — то, что использовали в описанном ниже случае. Не потому, что остальное хуже в принципе, а потому, что она лучше всего закрывала именно наши условия: фото‑контент, нужна альфа, нужна широкая браузерная поддержка.
Как это устроено механически
Базовая идея простая: позиция скролла — это просто индекс в массиве кадров, а не таймкод видео.

Схема 1. Вся логика — четыре шага без асинхронности: скролл напрямую превращается в индекс массива кадров. Именно эта синхронность и есть источник детерминированности, которой не даёт video‑seek.
Настоящая проблема — не точность, а вес
Как только заменяешь видео на кадры, вылезает обратная сторона: 700+ отдельных изображений в высоком качестве весят на порядок больше, чем сжатое видео той же длительности. На одном из наших проектов исходная image‑sequence анимация тянула на посещение 23 МБ — при заметном трафике это быстро упирается в лимиты бюджетного хостинга. Комбинация из трёх приёмов ужала её до 2.4 МБ без визуальной потери самой анимации.
755 → 77 кадров в секвенции · 23 → 2.4 МБ вес на посещение · ×9.6 сокращение трафика
1. Прореживание + альфа‑бленд вместо жёсткого переключения
Оставляем не каждый кадр, а каждый N‑й — и вместо резкой смены кадра при скролле кросс‑фейдим два соседних через альфа‑канал canvas. Мозг воспринимает плавный переход между близкими кадрами почти так же, как честную частоту — до определённого предела прореживания, который лучше не угадывать, а находить экспериментально.
Первая попытка — каждый 16-й кадр — визуально сломалась: между слишком далёкими друг от друга кадрами блендинг давал не плавный переход, а «двойную экспозицию», два полупрозрачных состояния объекта одновременно. Откатились до каждого 10-го — разница между соседними кадрами оказалась достаточно маленькой, чтобы бленд читался как движение, а не наложение.

Схема 2. Три плотности прореживания рядом. При каждом 10-м кадре зазор между соседями достаточно мал — бленд читается как движение. При каждом 16-м зазор слишком большой — бленд между далёкими позами даёт видимую «двойную экспозицию».
2. Порядок загрузки — не по порядку
Вместо линейной загрузки «кадр 1, 2, 3…» — сначала первый и последний кадр (задают крайние состояния), затем середина, затем середины половин, и так рекурсивно. Тот же принцип, что в конфигураторах товаров: грубый, но рабочий скраб становится доступен почти мгновенно, а детализация уточняется по мере догрузки остальных кадров.

Схема 3. Числа — очередь загрузки, не позиция в массиве. Первые два запроса дают рабочий крайний диапазон скраба почти сразу; каждый следующий уровень бисекции добавляет детализацию, а не сдвигает готовность с нуля.
3. Motion blur маскирует то, что осталось от прореживания
Финальный слой — не про вес файлов, а про восприятие: направленный CSS‑блюр, интенсивность которого привязана к скорости скролла в моменте. При быстрой прокрутке лёгкая смазанность — это ожидаемо и естественно глазу, поэтому она бесплатно маскирует то, что кадров под капотом физически меньше, чем при честных 60 fps. При остановке скролла блюр обнуляется, и последний кадр всегда чёткий.
Что стоит проверить, если решите так же
Объясните «почему не видео» в первом экране статьи, а не как сноску — иначе именно это станет первым вопросом в комментариях.
Найдите порог прореживания экспериментально, не берите число из чужой статьи — предел зависит от того, насколько объект меняется между соседними исходными кадрами.
Проверьте память, а не только сеть — держать все 77 кадров как раскодированные bitmap в памяти на слабом мобильном устройстве может стоить дороже, чем сама загрузка по сети.
Заложите деградацию для слабых устройств — на медленном соединении или слабом железе честнее показать статичный кадр или сильно урезанную секвенцию, чем рваный скраб.

