API requestAnimationFrame — теперь с точностью до миллисекунды

Ilmari Heikkinen

Если вы использовали requestAnimationFrame вам нравилось видеть, как ваши краски синхронизируются с частотой обновления экрана, что приводит к максимально возможной высококачественной анимации. Кроме того, вы экономите шум вентилятора процессора и расход заряда батареи ваших пользователей, когда они переключаются на другую вкладку.

Однако скоро будут внесены изменения в часть API . Метка времени, передаваемая в вашу функцию обратного вызова, меняется с типичной метки времени, похожей на Date.now() на измерение миллисекунд с плавающей запятой с высоким разрешением с момента открытия страницы. Если вы используете это значение, вам нужно будет обновить свой код , основываясь на объяснении ниже.

Чтобы внести ясность, вот о чем я говорю:

// assuming requestAnimationFrame method has been normalized for all vendor prefixes..
requestAnimationFrame(function(timestamp){
    // the value of timestamp is changing
});

Если вы используете общую прокладку requestAnimFrame представленную здесь , вы не используете значение метки времени. Вы сошли с крючка. :)

Почему

Почему? Что ж, rAF помогает вам получить идеальные 60 кадров в секунду, а 60 кадров в секунду соответствуют 16,7 мс на кадр. Но измерение целыми миллисекундами означает, что у нас есть точность 1/16 для всего, что мы хотим наблюдать и нацеливаться.

Сравнение графиков 16 мс и 16 целых мс.

Как вы можете видеть выше, синяя полоса представляет максимальное количество времени, которое вам понадобится, чтобы выполнить всю работу, прежде чем рисовать новый кадр (со скоростью 60 кадров в секунду). Вероятно, вы делаете более 16 действий, но с целыми миллисекундами у вас есть возможность планировать и измерять только с этими очень короткими приращениями. Это недостаточно хорошо.

Таймер высокого разрешения решает эту проблему, предоставляя гораздо более точные цифры:

Date.now()         //  1337376068250
performance.now()  //  20303.427000007

Таймер высокого разрешения в настоящее время доступен в Chrome как window.performance.webkitNow() , и это значение обычно равно новому значению аргумента, передаваемому в обратный вызов rAF. Как только спецификация пройдет дальше по стандартам, метод уберет префикс и станет доступен через performance.now() .

Вы также заметите, что два приведенных выше значения различаются на много порядков. performance.now() — это измерение миллисекунд с плавающей запятой с момента начала загрузки конкретной страницы (точнее, performance.navigationStart ).

В использовании

Ключевой проблемой, которую обрезают, являются библиотеки анимации, использующие этот шаблон проектирования:

function MyAnimation(duration) {
    this.startTime = Date.now();
    this.duration = duration;
    requestAnimFrame(this.tick.bind(this));
}
MyAnimation.prototype.tick = function(time) {
    var now = Date.now();
    if (time > now) {
        this.dispatchEvent("ended");
        return;
    }
    ...
    requestAnimFrame(this.tick.bind(this));
}

Отредактировать это довольно просто... увеличьте startTime и now используйте window.performance.now() .

this.startTime = window.performance.now ?
                    (performance.now() + performance.timing.navigationStart) :
                    Date.now();

Это довольно наивная реализация, она не использует метод now() с префиксом, а также предполагает поддержку Date.now() , которой нет в IE8.

Обнаружение функций

Если вы не используете приведенный выше шаблон и просто хотите определить, какой тип значения обратного вызова вы получаете, вы можете использовать этот метод:

requestAnimationFrame(function(timestamp){

    if (timestamp < 1e12){
        // .. high resolution timer
    } else {
        // integer milliseconds since unix epoch
    }

    // ...

Проверка того, является if (timestamp < 1e12) быстрым тестом на утиную реакцию, чтобы увидеть, с каким большим числом мы имеем дело. Технически это может быть ложное срабатывание, но только если веб-страница открыта непрерывно в течение 30 лет. Но мы не можем проверить, является ли это числом с плавающей запятой (а не преобразованным в целое число). Попросите таймеры с достаточно высоким разрешением, и в какой-то момент вы обязательно получите целочисленные значения .

Мы планируем внести это изменение в Chrome 21, поэтому, если вы уже используете этот параметр обратного вызова, обязательно обновите свой код!