JS requestAnimationFrame - za lepší vykreslování

JavaScript JS requestAnimationFrame - za lepší vykreslování

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 článku 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.

var interval = setInterval(function() {
    update();
    render();
}, 1000 / FPS);

Nebo takto?

function loop() {
    setTimeout(loop, 1000 / FPS);
    update();
    render();
}
loop();

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

Žere to zbytečně výkon prohlížeče

Problém výše uvedeného kódu je ten, že prohlížeč jej vykonává, i když uživatel na danou stránku zrovna nekouká (má překliknuto na jiný tab, případně je prohlížeč minimalizovaný). Google Chrome toto řeší a takovéto smyčky omezuje pouze na 1 FPS, to je ale pouze jeho dobrovolné chování a v ostatních prohlížečích to vůbec být nemusí, takže to na mobilních zařízeních může dost slušně žrát baterii a výkon.

Vykreslování více věcí najednou

Jedním řešením je vložit všechen kód, který provádí vykreslování, do hlavní smyčky. Berte ale na vědomí, že se budou vykreslovat a obnovovat i prvky, které to aktuálně nepotřebují. Opět to může dost nepěkně zapůsobit na výdrž baterie.

requestAnimati­onFrame to řeší!

S použitím requestAnimati­onFrame bude náš kód vypadat dost podobně jako s použitím setTimeout. Ukázka použití na PasteBinu.

function loop() {
    requestAnimationFrame(loop);
    update();
    render();
}
loop();

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

Výhody

requestAnimati­onFrame 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í requestAnimati­onFrame 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 docela dobrá. Avšak, třeba IE 10 a i starší verze ostatních prohlížečů potřebují vendor prefixy.

Nevýhody

Já jsem na žádné, kromě těch vendor prefixů (což se dá ale velmi snadno vyřešit), nepřišel.

Demo

Na závěr si ještě ukážeme jednoduchou aplikaci, ve které se budeme moct pohybovat po canvasu a implementujeme jí jak pomocí setInterval, tak pomocí requestAnimati­onFrame.

Řešeno pomocí setInterval

Použil jsem funkci ready z knihovny jQuery. Link na JSFiddle.

$(function() {
        var canvas = document.querySelector("#canvas");
        var ctx = canvas.getContext("2d");
        var square = {
            "x": 25,
            "y": 25,
            "dirX": -1,
            "dirY": 1,
            "speed": 2,
            "sizeX": 50,
            "sizeY": 50,
            "color": "red"
        };
        var loop;
        start();

    function start() {
        loop = setInterval(function() {
            update();
            render();
        }, 1000 / 60);
    }

    function stop() {
        clearInterval(loop);
    }

    function update() {
        if (square.x + square.sizeX + square.speed * square.dirX > canvas.width) square.dirX = -1;
        if (square.x + square.speed * square.dirX < 0) square.dirX = 1;
        if (square.y + square.sizeY + square.speed * square.dirY > canvas.height) square.dirY = -1;
        if (square.y + square.speed * square.dirY < 0) square.dirY = 1;

        changeColor();
        square.x += square.speed * square.dirX;
        square.y += square.speed * square.dirY;
    }

    function render() {
        ctx.fillStyle = square.color;
        ctx.fillRect(square.x, square.y, square.sizeX, square.sizeY);
    }

    function changeColor() {
        colors = ['orange', 'blue', 'red', 'yellow'];
        square.color = colors[Math.floor(Math.random() * colors.length) + 1];
    }
});
Demo requestAnimationFrame

Řešeno pomocí requestAnimati­onFrame

Opět jsem použil funkci ready z knihovny jQuery. Link na JSFiddle.

$(function() {
    var canvas = document.querySelector("#canvas");
        var ctx = canvas.getContext("2d");
        var square = {
            "x": 25,
            "y": 25,
            "dirX": -1,
            "dirY": 1,
            "speed": 2,
            "sizeX": 50,
            "sizeY": 50,
            "color": "red"
        };

    function animLoop(render, element) {
        var running, lastFrame = +new Date,
            raf = window.mozRequestAnimationFrame    ||
                  window.webkitRequestAnimationFrame ||
                  window.msRequestAnimationFrame     ||
                  window.oRequestAnimationFrame;
        function loop(now) {
            if (running !== false) {
                raf(loop, element);
                var deltaT = now - lastFrame;
                if (deltaT < 160) running = render(deltaT);
            lastFrame = now;
            }
        }
        loop(lastFrame);
    }

    animLoop(function( deltaT ) {
        update();
        render();
    });

    function update() {
        if (square.x + square.sizeX + square.speed * square.dirX > canvas.width) square.dirX = -1;
        if (square.x + square.speed * square.dirX < 0) square.dirX = 1;
        if (square.y + square.sizeY + square.speed * square.dirY > canvas.height) square.dirY = -1;
        if (square.y + square.speed * square.dirY < 0) square.dirY = 1;

        changeColor();
        square.x += square.speed * square.dirX;
        square.y += square.speed * square.dirY;
    }

    function render() {
        ctx.fillStyle = square.color;
        ctx.fillRect(square.x, square.y, square.sizeX, square.sizeY);
    }

    function changeColor() {
        colors = ['orange', 'blue', 'red', 'yellow'];
        square.color = colors[Math.floor(Math.random() * colors.length) + 1];
    }
});

Doufám, že vám byl článek alespoň k něčemu užitečný a že od teď budete pro animace v prohlížeči používat requestAnimati­onFrame :)


 

  Aktivity (1)

Článek pro vás napsal Radim Sückr
Avatar
Autor se specializuje na programování webových aplikací převážně v PHP s Nette Frameworkem, JS a Dartu. Není mu však cizí ani Java, C# nebo C++. A studuje Soukromou střední školu výpočetní techniky v Praze.

Jak se ti líbí článek?
Celkem (10 hlasů) :
4.94.94.94.94.9


 


Miniatura
Všechny články v sekci
JavaScript
Miniatura
Následující článek
Označovač akordů v JavaScriptu

 

 

Komentáře

Avatar
Pavel Vosyka
Člen
Avatar
Pavel Vosyka:

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
Radim Sückr
Redaktor
Avatar
Odpovídá na Pavel Vosyka
Radim Sückr:

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
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.