JS requestAnimationFrame - za lepší vykreslování

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

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

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 :)


 

 

Č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.
Miniatura
Předchozí článek
Regulární výrazy v JavaScriptu
Miniatura
Všechny články v sekci
JavaScript
Miniatura
Následující článek
Označovač akordů v JavaScriptu
Aktivity (1)

 

 

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.