Canvas animacje

Aby animować cokolwiek na canvasie musimy zastosować podejście znane chociażby z gier. Robimy więc główną pętlę (intervał), w której każdorazowo czyścimy cały canvas, rysujemy daną klatkę animacji, po czym powtarzamy rysując kolejną. I tak w koło Macieju...

Aby animować, musimy nasz kod rysujący wywoływać cyklicznie. Możemy do tego wykorzystać funkcję setInterval():

function draw() {
    ...kod rysujący
}


setInterval(draw, 1000/60);

lub funkcję setTimeout:

function draw() {
    ...kod rysujący

    setTimeout(draw, 1000/60);
}


setTimeout(draw, 1000/60);

Problem z zastosowaniem tych metod w przypadku animacji jest taki, że nie mamy pewności, czy rysowanie pojedynczej klatki zakończy się na czas. Może to powodować, że odpalony interwał zacznie się dławić, a nasza animacja nie będzie należycie płynna. Moglibyśmy to próbować obchodzić przez samo wywołujący się setTimeout, ale wtedy nasza animacja mogła by nie być optymalnie płynna.

Jeżeli zależy nam na tym, by nasza animacja działała optymalnie, wtedy do takiego cyklicznego wywoływania kodu powinniśmy użyć metody requestAnimationFrame(fn). Metoda ta wywoła dany kod przed kolejną klatką rysowania strony. Dzięki temu przeglądarka optymalnie dobierze moment kiedy taki kod ma być odpalony, a nasza animacja będzie płynniejsza.

function draw() {
    ...kod rysujący

    requestAnimationFrame(draw);
}

requestAnimationFrame(draw);

Moment taki zależy od kilku czynników. Obciążenie procesora czy chociażby to, czy nasza strona jest w aktywnej zakładce, czy działa gdzieś w tle. Jeżeli w tle - wtedy animacja nie musi być odgrywana.

Każde użycie requestAnimationFrame zwraca ID, które możemy wykorzystać do przerwania tak trwającej animacji:

Aby zatrzymać tak odpaloną animację skorzystamy z metody cancelAnimationFrame(id):

Niestety w przeciwieństwie do setInterval nie podajemy tutaj szybkości działania takiej animacji. Częstotliwość wykonywania funkcji wynosi zazwyczaj ok 60 razy na sekundę, jednakże według rekomendacji W3C w większości przeglądarek odpowiada częstotliwości odświeżania ekranu. Przy monitorach z dużą częstotliością oznacza to, że nasz kod będzie odpalany np. 120 razy na sekundę. W większości sytuacji sprawi to, że nasza animacja albo będzie super szybka, albo będzie klatkować, bo komputer nie wyrobi obliczeń.

W moich prywatnych tworach zauważyłem, że w Chromie "efekt" ten nie był tak bardzo zauważalny jak w przypadku Firefoxa, w którym użycie "czystego" requestAnimationFrame najczęściej sprawiało dławienie się animacji.

Aby to naprawić możemy skorzystać z przepisu np. z tej strony (chociaż tak naprawdę wystarczy w google wpisać requestAnimationFrame control frames). Metoda często używana jest w grach, gdzie chcemy uniezależnić naszą animację od sztywnych fps (których komputer może nie zdążyć wyświetlić), a chcemy bazować na czasie delta, który oznacza czas jaki minął między dwoma klatkami.

Prosta implementacja takiej funkcji może mieć postać:

I użyjmy, na razie do wypisywania tekstu:

Mając już podstawowy kontroler do obsługi głównej pętli animacji możemy przejść do sedna rozdziału - animowania.

Animowanie pojedynczego obiektu

Do czyszczenia klatki wykorzystamy znaną nam metodę clearRect(), którą będziemy czyścić całą zawartość całego canvasa.

Przypuśćmy, że chcemy animować odbijającą się od krawędzi gwiazdkę. Wpierw musimy wczytać grafikę gwiazdki:

Następnie dla naszej gwiazdki musimy stworzyć zmienne dla jej pozycji. W każdej klatce animacji będziemy zmieniać jej pozycję oraz wyliczać czy gwiazdka nie dotknęła krawędzi canvas. Jeżeli dotknęła, wtedy kierunek ruchu na danej osi (x lub y) zmieniamy na odwrotny:

Animowanie kilku obiektów

Dla większej liczby obiektów będziemy musieli nieco zmodyfikować nasz kod. Wszystkie gwiazdki będziemy trzymać w tablicy. Podczas pojedynczej klatki zrobimy pętlę po tej tablicy, obliczymy nową pozycję dla każdej gwiazdki i narysujemy każdy obiekt.

Do generowania randomowych liczb użyjemy funkcji, którą napisaliśmy w poprzednim rozdziale.

Animacja poklatkowa

Jak już wiemy, metoda drawImage w postaci drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) służy do rysowania na płótnie wyciętego kawałka grafiki. Metoda ta idealnie się nadaje do stworzenia poklatkowej animacji, która polega na wyświetlaniu kolejnych klatek animacji, które są ułożone obok siebie na jednej grafice z przezroczystym tłem.

W internecie jest bardzo dużo grafik typu "sprites", które są "wyciągnięte" ze starych gier 2d. Wystarczy w Google poszukać sformułowań typu "game sprites", by dostać ich pokaźną kolekcję. Przykładowe linki: http://www.spriters-resource.com/ http://sdb.drshnaps.com/

W poniższym przykładzie skorzystamy z poniższego sprite:

Aby teraz animować taką grafikę, powinniśmy kolejno wyświetlać jej wycięte fragmenty. Aby to zrobić, powinniśmy znać numer wyświetlanej klatki, i za pomocą tej liczby obliczać w każdej klatce animacji przesunięcie wycinania.

W ramach treningu możemy też dodać do naszej animacji kreski symbolizujące pęd. Podobnie jak w przypadku gwiazdek - możemy tutaj użyć tablicy, w której będziemy przechowywać każdą kreskę:

Hola, hola! Podobne rzeczy jeszcze łatwiej zrobisz w czystym CSS. Przeczytaj sobie moje artykuły na ten temat:

animacje css i baner animowany

Jedno jednak drugiego nie wyklucza. Powyższe podejście bardziej się sprawdzi przy grach, gdzie musimy reagować na poczynania gracza...

Last updated