Зачем вообще нужен requestVideoFrameCallback
Если вы хоть раз пытались делать «умную» работу с видео в браузере на JavaScript, вы наверняка упирались в одну и ту же проблему: точной привязки к кадрам нет, таймеры врут, `timeupdate` дергается слишком редко, а `setInterval` живёт своей жизнью.
Вот тут и выезжает на сцену новый герой HTML5‑видео — метод `requestVideoFrameCallback`.
Это специальный API для `
Где это работает
На момент написания, `requestVideoFrameCallback` поддерживается в современных версиях Chromium‑браузеров (Chrome, Edge, Opera, Brave) и Safari. Firefox пока отстаёт, поэтому для продакшена стоит предусмотреть запасной вариант.
Простейшая проверка:
```js
const video = document.querySelector('video');
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
console.log('Поддержка есть!');
} else {
console.log('Нужно использовать полифилл или таймеры.');
}
```
Базовая идея: как работает requestVideoFrameCallback
Метод вешается на конкретный `
```js
const video = document.querySelector('video');
let handle;
function onVideoFrame(now, metadata) {
// ваша логика тут
handle = video.requestVideoFrameCallback(onVideoFrame);
}
handle = video.requestVideoFrameCallback(onVideoFrame);
```
Он принимает колбэк с двумя аргументами:
- `now` — высокоточная отметка времени (аналогично `performance.now()`),
- `metadata` — объект с кучей полезной информации: `mediaTime`, `presentedFrames`, `expectedDisplayTime` и т.д.
Главное отличие от обычной `javascript работа с видео html5` в том, что вы получаете событие на каждый кадр, а не раз в сколько-то миллисекунд как повезёт.
Чем это лучше setInterval и timeupdate
Коротко:
- `timeupdate` вызывается примерно 4–5 раз в секунду — для точных эффектов бесполезно.
- `setInterval` и `requestAnimationFrame` живут по своему расписанию и не знают, когда браузер реально показал кадр.
- `requestVideoFrameCallback` сообщает точную, синхронизированную с рендером кадра информацию.
Результат:
обработка видео в браузере JavaScript становится предсказуемой и аккуратной, без «прыжков» и рассинхрона.
Практика: типичный паттерн использования
Шаг 1. Запускаем колбэк только когда видео играет
Не стоит вызывать колбэк, когда видео на паузе — это лишняя нагрузка. Привяжем старт и остановку к событиям плеера:
```js
const video = document.querySelector('video');
let frameHandle = null;
function onFrame(now, metadata) {
// Пример: логируем время кадра
// console.log(metadata.mediaTime, metadata.presentedFrames);
// Планируем следующий кадр, только если видео реально играет
if (!video.paused && !video.ended) {
frameHandle = video.requestVideoFrameCallback(onFrame);
}
}
video.addEventListener('play', () => {
frameHandle = video.requestVideoFrameCallback(onFrame);
});
video.addEventListener('pause', () => {
if (frameHandle !== null) {
video.cancelVideoFrameCallback(frameHandle);
frameHandle = null;
}
});
video.addEventListener('ended', () => {
if (frameHandle !== null) {
video.cancelVideoFrameCallback(frameHandle);
frameHandle = null;
}
});
```
Короткий вывод: всегда контролируйте подписки, иначе колбэк будет висеть впустую.
Шаг 2. Достаём полезные данные из metadata
Самое вкусное — объект `metadata`. В разных браузерах он может содержать чуть разный набор полей, но обычно есть:
- `mediaTime` — текущее время воспроизведения (секунды, с плавающей точкой);
- `presentedFrames` — сколько кадров уже показано;
- `expectedDisplayTime` — когда этот кадр попадёт на экран (наносекунды).
Пример извлечения:
```js
function onFrame(now, metadata) {
const time = metadata.mediaTime; // секунды
const frameCount = metadata.presentedFrames;
// Например, выводим в кастомный оверлей
overlay.textContent = `Кадр: ${frameCount}, время: ${time.toFixed(3)}s`;
frameHandle = video.requestVideoFrameCallback(onFrame);
}
```
Такой доступ к кадрам сильно упрощает оптимизацию воспроизведения видео на сайте: можно подстраивать анимацию, загрузку превью, подгрузку сегментов и т.п. именно тогда, когда это надо.
Синхронизация анимации и видео
Простая синхронизация эффектов
Самый частый сценарий — синхронизация анимации с видео JavaScript: подсветка субтитров, появление текста, анимационные элементы, привязанные к таймкодам.
Пусть у нас есть массив «ключевых точек»:
```js
const cues = [
{ time: 1.0, action: () => showCaption('Начинаем!') },
{ time: 3.5, action: () => highlightElement('#block1') },
{ time: 7.2, action: () => showCaption('Важный момент') },
];
```
Используем `requestVideoFrameCallback`, чтобы выполнять действия максимально точно по кадру:
```js
let nextCueIndex = 0;
function onFrame(now, metadata) {
const t = metadata.mediaTime;
while (nextCueIndex < cues.length && t >= cues[nextCueIndex].time) {
cues[nextCueIndex].action();
nextCueIndex++;
}
if (!video.paused && !video.ended) {
frameHandle = video.requestVideoFrameCallback(onFrame);
}
}
```
В отличие от таймеров, это не будет «гулять» при смене скорости воспроизведения или при паузах — всё жёстко привязано к фактическому `mediaTime`.
Привязка canvas-графики к видео
Другой популярный сценарий — отрисовывать поверх видео кастомную графику через `
- рамка вокруг лица в видеопотоке;
- подсветка объекта;
- визуализация данных (графики, прогресс-бары).
Базовый пример:
```html
```
```js
const video = document.getElementById('video');
const canvas = document.getElementById('overlay');
const ctx = canvas.getContext('2d');
function resizeCanvas() {
canvas.width = video.clientWidth;
canvas.height = video.clientHeight;
}
window.addEventListener('resize', resizeCanvas);
video.addEventListener('loadedmetadata', resizeCanvas);
function onFrame(now, metadata) {
// Чистим холст
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Пример: рисуем простую рамку, зависящую от времени
const t = metadata.mediaTime;
const padding = 20 + Math.sin(t) * 10;
ctx.strokeStyle = 'rgba(255, 0, 0, 0.8)';
ctx.lineWidth = 4;
ctx.strokeRect(
padding,
padding,
canvas.width - padding * 2,
canvas.height - padding * 2
);
if (!video.paused && !video.ended) {
frameHandle = video.requestVideoFrameCallback(onFrame);
}
}
```
Так вы получаете плавную, кадр‑в‑кадр синхронизацию: рамка двигается или изменяется ровно в такт видео, без лагов.
Оптимизация: как не убить производительность

Если вы хотите, чтобы веб разработка и работа с видео плеером не превращались в борьбу с тормозами, нужно соблюдать пару простых правил.
Что не стоит делать в onFrame
Внутри колбэка не должно быть тяжёлой логики. Избегайте:
- сложных DOM‑операций (создание/удаление множества элементов);
- больших вычислений (распознавание лиц, сложная физика);
- синхронных запросов и блокирующего кода.
Правильнее:
- считать тяжёлое в `Web Worker` и в onFrame только читать результаты;
- батчить обновления DOM (менять текст/классы за один проход);
- использовать флаги, чтобы не делать одно и то же действие каждый кадр.
Минимальный «здоровый» паттерн
```js
let lastRenderedFrame = -1;
function onFrame(now, metadata) {
const frameNumber = metadata.presentedFrames;
// Если кадр тот же, можно ничего не делать
if (frameNumber === lastRenderedFrame) {
frameHandle = video.requestVideoFrameCallback(onFrame);
return;
}
lastRenderedFrame = frameNumber;
// Лёгкие операции обновления
overlay.textContent = `Кадр: ${frameNumber}`;
frameHandle = video.requestVideoFrameCallback(onFrame);
}
```
Короткий абзац, но важная мысль: не трогайте DOM и сложные структуры, если кадр не изменился.
Адаптация под слабые устройства
Иногда логично обрабатывать не каждый кадр. Например, видео 60 fps, а вам достаточно 15 fps для вычислений.
```js
const TARGET_FPS = 15;
let lastTime = 0;
function onFrame(now, metadata) {
const t = metadata.mediaTime;
if (t - lastTime >= 1 / TARGET_FPS) {
lastTime = t;
// ваша логика 15 fps
doLightWork(t);
}
frameHandle = video.requestVideoFrameCallback(onFrame);
}
```
Так вы снижаете нагрузку, но при этом остаетесь синхронизированными с реальным ходом видео.
Практические сценарии применения
1. Точный прогресс-бар и маркеры
Обычный прогресс‑бар завязан на `timeupdate`, и он может подёргиваться. С `requestVideoFrameCallback` вы обновляете полоску каждый кадр, делая интерфейс плавным.
Алгоритм:
1. В обработчике кадра берёте `mediaTime` и `duration`.
2. Считаете прогресс: `progress = mediaTime / duration`.
3. Применяете ширину/трансформации к индикатору.
4. В нужные моменты подсвечиваете маркеры (главы, рекламные блоки, главы курса).
2. Скролл, зависящий от видео

Иногда нужно, чтобы прокрутка страницы или параллакс‑эффекты были связаны с видео: например, пока видео идёт, фон плавно меняется или блоки текста проявляются.
Простой паттерн:
- в `onFrame` считаете процент просмотра;
- по этому проценту обновляете CSS‑переменные;
- в стилях используете эти переменные для анимаций.
```js
function onFrame(now, metadata) {
const progress = metadata.mediaTime / video.duration;
document.documentElement.style.setProperty(
'--video-progress',
progress.toFixed(4)
);
frameHandle = video.requestVideoFrameCallback(onFrame);
}
```
А в CSS:
```css
.section {
opacity: calc(var(--video-progress) * 1.5);
}
```
3. Интерактивные обучающие видео
Для образовательного контента обработка видео в браузере JavaScript с точностью до кадра — прям подарок: можно показывать тестовые вопросы ровно в тот момент, когда в видео что‑то объяснили, затем останавливать ролик и ждать ответа.
Простой сценарий:
- заведите массив таймкодов вопросов;
- когда `mediaTime` переваливает за нужное значение — ставите видео на паузу, показываете модальное окно с вопросом;
- после ответа продолжаете воспроизведение и снова подписываетесь на кадры.
requestVideoFrameCallback + HTML5: реальная работа с видео
Если вы уже работали с `canvas`, `requestAnimationFrame` и знаете, как устроена JavaScript работа с видео HTML5, то переход на `requestVideoFrameCallback` будет логичным следующим шагом.
Ключевые выводы:
- API даёт вам доступ к точным таймкодам и номеру кадра;
- вы можете синхронизировать анимации, эффекты и интерфейс с видео без «дрожи»;
- за счёт того, что всё крутится вокруг фактических кадров, оптимизация воспроизведения видео на сайте становится более контролируемой: меньше лишней работы, меньше лагов.
Небольшой чек-лист перед внедрением
Номерованный список для закрепления:
1. Проверяйте поддержку: добавьте условие, чтобы на старых браузерах всё не ломалось.
2. Не нагружайте onFrame: держите внутри только лёгкую логику и быстрые операции.
3. Привязывайте подписки к состоянию плеера: запускайте колбэк на `play`, останавливайте на `pause` и `ended`.
4. Тестируйте разные скорости проигрывания: убедитесь, что синхронизация работает и на 0.5x, и на 2x.
5. Следите за UX: если добавляете много визуальных эффектов, проверяйте, не проседает ли FPS на слабых устройствах.
Итог

`requestVideoFrameCallback` — это не «ещё один таймер», а недостающий кусок пазла для тех, кому нужна тонкая, детальная работа с видео в браузере.
Используя его вместе с аккуратным кодом и привычными инструментами веб‑разработки, вы получаете более точную веб разработку и работу с видео плеером: синхронные анимации, интерактивные подсказки, кастомные оверлеи и любое другое «волшебство» на уровне кадра.



