Lekce 15 - JS requestAnimationFrame - Za lepší vykreslování

JavaScript Základní konstrukce JS requestAnimationFrame - Za lepší vykreslování American English version English version

ONEbit hosting Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Časovače a animace v JavaScriptu, jsme se věnovali animacím. Spolu s HTML5 přišlo nové javascriptové API - requestAnimati­onFrame. Jedná se o technologii, která nám umožňuje plynuleji a s vyšším výkonem vykreslovat animace v prohlížeči a my si ji v tomto tutoriálu představíme.

Pokud jste někdy zkoušeli napsat např. nějakou jednoduchou hru v prohlížeči, jistě vaše hlavní smyčka vypadala nějak takto.

setInterval(function() {
        posun();
        vykresli();
}, 1000 / FPS);

Naše dosavadní animace vypadaly velmi podobně, pouze jsme spojili posouvání a vykreslování do jedné funkce. Alternativní vykreslovací smyčka může vypadat i např. takto:

function smycka() {
        setTimeout(smycka, 1000 / FPS);
        posun();
        vykresli();
}
smycka();

Kód funguje, dokonce si můžeme i omezit FPS. Co je na něm ale špatně?

Plýtvání výkonem počítače

Problém výše uvedeného kódu je ten, že prohlížeč jej vykonává, i když se uživatel na danou stránku zrovna nedívá (má překliknuto na jinou záložku, případně je prohlížeč minimalizovaný). Prohlížeč Google Chrome tyto situace řeší omezením takovýchto smyček pouze na 1 FPS. To je ale pouze jeho dobrovolné chování a v ostatních prohlížečích to tak vůbec být nemusí, takže na mobilních zařízeních může docházet zbytečně k poklesu výkonu a vybíjení baterie.

requestAnimati­onFrame to řeší!

S použitím funkce requestAnimationFrame() místo setTimeout() bude náš kód vypadat velmi podobně.

function smycka() {
        requestAnimationFrame(smycka);
        posun();
        vykresli();
}
smycka();

Tuto funkci vymyslela Mozilla a později ji převzal a vylepšil tým WebKitu. Pomocí funkce se může vykreslovat CSS, DOM elementy, WebGL nebo na canvas.

Výhody

requestAnimationFrame() nám zajistí, že všechny naše animace se budou vykreslovat najednou, s vyšším výkonem a nižší spotřebou baterie.

Pokud v prohlížeči překliknete na jinou stránku, nebo prohlížeč minimalizujete, vykreslování se zastaví, aby se šetřil výkon a bude se pokračovat, jakmile bude stránka opět viditelná.

Mohli jste si také všimnout, že u použití requestAnimationFrame() jsme nikde nenastavovali počet FPS. Funkce ve výchozím nastavení používá 60 FPS, záleží ale na implementaci v prohlížeči. Nemělo by se to ale příliš lišit a spíše záleží na výkonu PC, než na rozhodnutí výrobce prohlížeče. Také podpora v prohlížečích je poměrně dobrá.

Demo

Na závěr si ukážeme jednoduchou aplikaci, ve které se čtverec pohybuje po canvasu a implementujeme jí jak pomocí setInterval(), tak pomocí requestAnimationFrame(). Plátno schválně tentokrát nebudeme mazat, čímž bude vidět stopa čtverce.

Řešení pomocí setInterval()

window.onload = function() {
        let platno = document.querySelector("#platno");
        let kontext = platno.getContext("2d");
        let ctverec = {
                "x": 25,
                "y": 25,
                "smerX": -1,
                "smerY": 1,
                "rychlost": 2,
                "sirka": 50,
                "vyska": 50,
                "barva": "red"
        };

        function smycka() {
                posun();
                prekresli();
        }
        setInterval(smycka, 1000 / 60);
        smycka();

        function posun() {
                if (ctverec.x + ctverec.sirka + ctverec.rychlost * ctverec.smerX > platno.width)
                        ctverec.smerX = -1;
                if (ctverec.x + ctverec.rychlost * ctverec.smerX < 0)
                        ctverec.smerX = 1;
                if (ctverec.y + ctverec.vyska + ctverec.rychlost * ctverec.smerY > platno.height)
                        ctverec.smerY = -1;
                if (ctverec.y + ctverec.rychlost * ctverec.smerY < 0)
                        ctverec.smerY = 1;

                zmenBarvu();
                ctverec.x += ctverec.rychlost * ctverec.smerX;
                ctverec.y += ctverec.rychlost * ctverec.smerY;
        }

        function prekresli() {
                kontext.fillStyle = ctverec.barva;
                kontext.fillRect(ctverec.x, ctverec.y, ctverec.sirka, ctverec.vyska);
        }

        function zmenBarvu() {
                barvy = ['green', 'blue', 'red', 'yellow'];
                ctverec.barva = barvy[Math.floor(Math.random() * barvy.length)];
        }
}

Výsledek:

Animace pomocí setInterval
localhost

Řešení pomocí requestAnimati­onFrame()

window.onload = function() {
        let platno = document.querySelector("#platno");
        let kontext = platno.getContext("2d");
        let ctverec = {
                "x": 25,
                "y": 25,
                "smerX": -1,
                "smerY": 1,
                "rychlost": 2,
                "sirka": 50,
                "vyska": 50,
                "barva": "red"
        };

        function smycka() {
                posun();
                prekresli();
                requestAnimationFrame(smycka);
        }
        smycka();

        function posun() {
                if (ctverec.x + ctverec.sirka + ctverec.rychlost * ctverec.smerX > platno.width)
                        ctverec.smerX = -1;
                if (ctverec.x + ctverec.rychlost * ctverec.smerX < 0)
                        ctverec.smerX = 1;
                if (ctverec.y + ctverec.vyska + ctverec.rychlost * ctverec.smerY > platno.height)
                        ctverec.smerY = -1;
                if (ctverec.y + ctverec.rychlost * ctverec.smerY < 0)
                        ctverec.smerY = 1;

                zmenBarvu();
                ctverec.x += ctverec.rychlost * ctverec.smerX;
                ctverec.y += ctverec.rychlost * ctverec.smerY;
        }

        function prekresli() {
                kontext.fillStyle = ctverec.barva;
                kontext.fillRect(ctverec.x, ctverec.y, ctverec.sirka, ctverec.vyska);
        }

        function zmenBarvu() {
                barvy = ['green', 'blue', 'red', 'yellow'];
                ctverec.barva = barvy[Math.floor(Math.random() * barvy.length)];
        }
}

Výsledek:

Animace pomocí requestAnimati­onFrame
localhost

Doufám, že vám byla lekce užitečná a že od teď budete pro animace v prohlížeči používat requestAnimationFrame() :)


 

Stáhnout

Staženo 24x (3.42 kB)
Aplikace je včetně zdrojových kódů v jazyce JavaScript

 

 

Článek pro vás napsal Neaktivní uživatel
Avatar
Jak se ti líbí článek?
10 hlasů
Tento uživatelský účet již není aktivní na základě žádosti jeho majitele.
Aktivity (8)

 

 

Komentáře

Avatar
Pavel Vosyka
Člen
Avatar
Pavel Vosyka:7.2.2014 22:18

Díky! To je mi novinka, určitě to použiju. Chtěl jsem to porovnat v chrome, ale jak už jsi říkal, on si i ten loop teda nějak pozastavuje, takže rozdíl jsem nezpozoroval. Tak jsem zapl IE11 a tam to neběží :D .. Alespoň mě to donutí nainstalovat jiné prohlížeče.. :) (mám teď zrovna čistou instalaci..)

Odpovědět  +1 7.2.2014 22:18
"nikdy nepiš nic 2x"
Avatar
Odpovídá na Pavel Vosyka
Neaktivní uživatel:7.2.2014 22:45

Díky za pozitivní ohlas. Ale k tomu IE. IE11 to 100% umí, chyba je tedy někde v kódu nebo na mé straně. V Dartu jsem si zkoušel udělat jednoduchý pingpong s pomocí právě requestAnimati­onFrame a normálně to funguje (http://grelek.maweb.eu/pingpong (prosím, je to pouze demo)).

BTW - IE 11 a už ani IE 10 nejsou nezbytně špatné prohlížeče.

Editováno 7.2.2014 22:46
Odpovědět 7.2.2014 22:45
Neaktivní uživatelský účet
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 2 zpráv z 2.