API requestAnimationFrame – teraz z dokładnością do mniej niż milisekundy

Ilmari Heikkinen

Jeśli używasz programu requestAnimationFrame, podoba Ci się, że Twoje farby są zsynchronizowane z częstotliwością odświeżania ekranu, co pozwala uzyskać najlepszą jakość animacji. Poza tym gdy użytkownicy przełączają się na inną kartę, oszczędzają szum wentylatora procesora i energię baterii.

Wkrótce nastąpi jednak zmiana w części interfejsu API. Sygnatura czasowa przekazywana do funkcji wywołania zwrotnego zmienia się z typowej sygnatury czasowej (takiej jak Date.now()) na pomiar w wysokiej rozdzielczości z milisekundami zmiennoprzecinkowym od otwarcia strony. Jeśli używasz tej wartości, musisz zaktualizować kod, zgodnie z poniższym wyjaśnieniem.

Dla jasności – oto co mówię:

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

Jeśli używasz typowej podkładki requestAnimFrame podanej tutaj, nie używasz wartości sygnatury czasowej. Jesteś na dobrej drodze. :)

Dlaczego

Dlaczego? Cóż, rAF pomaga uzyskać idealną prędkość 60 klatek na sekundę, która odpowiada idealnej wartości 60 klatek na sekundę, czyli 16,7 ms na klatkę. Pomiar w milisekundach oznacza, że z dokładnością do wszystkich danych, które chcemy obserwować i kierować, z dokładnością wynosi 1/16.

Porównanie wykresu 16 ms i 16 całkowitych ms.

Jak widać powyżej, niebieski pasek reprezentuje maksymalny czas do wykonania całej pracy przed namalowaniem nowej klatki (przy 60 kl./s). Prawdopodobnie robisz więcej niż 16 czynności, ale dzięki całkowitym wymiarom w milisekundach możesz tylko planować i mierzyć postępy. To za mało.

Minutnik w wysokiej rozdzielczości rozwiązuje ten problem, pokazując znacznie dokładniejsze wartości:

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

Licznik w wysokiej rozdzielczości jest obecnie dostępny w Chrome jako window.performance.webkitNow(). Ta wartość zazwyczaj jest równa nowej wartości argumentu przekazanej do wywołania zwrotnego rAF. Gdy specyfikacja pójdzie dalej pod względem standardów, metoda usunie prefiks i będzie dostępna na platformie performance.now().

Zwróć uwagę, że 2 powyższe wartości znacznie różnią się rzędami wielkości. performance.now() to wartość mierzona w milisekundach w przeliczeniu zmiennoprzecinkowym od momentu rozpoczęcia wczytywania danej strony (performance.navigationStart, aby była dokładna).

Używany

Głównym problemem przy przycinaniu są biblioteki animacji korzystające z tego wzorca projektowego:

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));
}

Zmiana, która pomoże Ci go poprawić, jest dość prosta... Powiększ startTime i now, aby używać window.performance.now().

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

Jest to dość naiwna implementacja, ponieważ nie korzysta z metody now() z prefiksem i zakłada obsługę Date.now(), której nie ma w IE8.

Wykrywanie funkcji

Jeśli nie korzystasz z powyższego wzorca i chcesz tylko sprawdzić, jaką wartość wywołania zwrotnego otrzymujesz, skorzystaj z tej techniki:

requestAnimationFrame(function(timestamp){

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

    // ...

Sprawdzanie if (timestamp < 1e12) to szybki test sprawdzający, z jaką liczbą mamy do czynienia. Technicznie rzecz biorąc, taki wynik może być fałszywy, ale tylko wtedy, gdy strona jest otwarta nieprzerwanie przez 30 lat. Jednak nie jesteśmy w stanie sprawdzić, czy jest to liczba zmiennoprzecinkowa (a nie liczba całkowita). Jeśli poprosisz o ustawienie wystarczającej liczby liczników czasu w wysokiej rozdzielczości, w pewnym momencie uzyskasz wartości całkowite.

Planujemy wprowadzić tę zmianę w Chrome 21, więc jeśli już korzystasz z tego parametru wywołania zwrotnego, nie zapomnij zaktualizować swojego kodu.