Zprovoznění animace

Máte problém s programově spouštěnými CSS3 animacemi, které se v některých prohlížečích (typicky Firefox) neanimují správně? A teď nemluvím o tom, že pomalé počítače nezvládnou složitá prolínání a posuny.

Zobrazení před změnou

Častým problémem bývá to, že máte skrytý prvek, který v určitý okamžik zobrazíte a okamžitě po něm chcete, aby se animoval – např. do stránky najel z boku nebo do stránky prolnul (postupně se zobrazil).

.slideIn {
    display: none;
    position: absolute;
    top: 0;
    left: -1000px;
    transition: left 1s;
}
.fadeIn {
    display: none;
    opacity: 0;
    position: absolute;
    top: 0;
    right: 0
    transition: opacity 1s;
}
function slideIn() {
    $('.slideIn').show().css('left', 0);
}
function fadeIn() {
    $('.fadeIn').show().css('opacity', 1);
}

Logika příkladu se zdá neomylná: prvky slideIn („najetí“) a fadeIn („prolnutí“) jsou skryté a připravené na požadované pozici k zobrazení. V požadovaném okamžiku použijete Javascript k jejich zobrazení a spuštění animace tak, aby se do 1 sekundy zobrazili v levém horním rohu.

Jenže problém, prvkům se nějak nechce animovat a v rohu se prostě jen tak objeví, jako kdyby žádná animace nebyla.

Problém je v tom, že prvek zobrazíte a animujete v jeden okamžik. DOM renderer pak celý proces zoptimalizuje a animaci vynechá – protože animace začíná u neviditelného prvku a tím pádem se zdá, že je nesmysl něco animovat.

Optimalizace DOM rendereru totiž probíhá tak, že si postupně pouze zapamatuje, co všechno musí udělat (tedy 1. zobraz prvek, 2. animuj prvek) a pak (když skončí výkon JS kódu) vše najednou naplánuje a vyrenderuje, přičemž ale vychází ze stavu 0, tedy před tím, než byly všechny akce naplánovány.

Co tedy musíte udělat, je nějak donutit DOM renderer, aby vaše akce prováděl postupně a nejprve prvek zobrazil a pak teprve vypočítal animaci.

Obvyklý postup je použít timer a akce o něco zpozdit:

function slideIn() {
    var el = $('.slideIn');
    el.show();
    el.css.defer(10, el, ['left', 0]);
}
function fadeIn() {
    var el = $('.slideIn');
    el.show();
    el.css.defer(10, el, ['opacity', 0]);
}
//jednoduchá funkce defer(); námět z ExtJS
Function.prototype.defer =
               function(delay, scope, params) {
    var f = this;
    return window.setTimeout(function() {
        f.apply(scope || window, params || []);
    }, delay);
};

Plán vypadá dobře, ale jen do okamžiku, kdy narazíte na pomalý počítač, který během poskytnutých 10 milisekund nestihne prvek zobrazit a animace opět neproběhne. Většina programátorů by se spokojila s tím, že nastaví timeout na nějakých 100 – 200 ms a řekne, že počítač, který za tu dobu nestihne zobrazit prvek, nebude stíhat ani animaci.

Pokud ale chcete opravdu udělat vše správně a co nejlépe, bude potřeba Javascript nějak donutit, aby skutečně počkal na zobrazení prvku a pak teprve spustil animaci. Časem totiž můžete narazit na to, že už nepůjde jen o animace, ale o něco podstatného, co nemůžete jen tak vypustit.

Trikem je to, že pokud se z Javascriptu zeptáte na nějakou vlastnost DOM prvku, DOM renderer neprve provede všechny naplánované změny a pak teprve vypočte novou hodnotu požadované vlasnosti. A to je přesně to, co v tomhle okamžiku potřebujeme.

function slideIn() {
    var el = $('.slideIn');
    el.show();
    el.height();
    el.css('left', 0);
}
function fadeIn() {
    var el = $('.slideIn');
    el.show();
    el.height();
    el.css('opacity', 0);
}

V uvedeném kódu nejprve zobrazíme prvek a pak se zeptáme na jeho výšku (zavoláním metody height() bez parametru). Výška nás vlastně vůbec nezajímá, ale donutíme tím DOM, aby ji spočítal, k čemuž potřebuje prvek nejprve zobrazit. Když pak máme jistotu, že je prvek zobrazený, můžeme již bez problémů spustit animaci.

Ještě jak by to vypadalo ve vanilla (čistém) Javascriptu:

function slideIn() {
    var el = document.getElementById('slideIn');
    el.style.display = 'block';
    (el.clientHeight);
    el.style.left = 0;
}
function fadeIn() {
    var el = document.getElementById('fadeIn');
    el.style.display = 'block';
    (el.clientHeight);
    el.style.opacity = 0;
}

V čistém JS se na výšku zeptáte jednoduše přečtením vlastnosti clientHeight. Aby validátory, jako je JSlint, nezobrazovali chybu, obalíme hodnotu do závorek, čímž vytvoříme validní příkaz.

Návaznost CSS definicí

Dalším problémem, proč se animace nezobrazují nebo proč se zobrazují špatně, bývá to, že animaci nastavíte ke špatné CSS třídě.

#popup {
    position: absolute;
    right: 0; top: 0; z-index: 999;
    opacity: 0;
}
.fadeIn { //fade in 0.5 seconds
    opacity: 1;
    transition: opacity 0.5s;
}
.fadeOut { //fade out in 2 seconds
    opacity: 0;
    transition: opacity 2s;
} 
window.fadeIn = function() {
    $('#popup')
        .removeClass('fadeOut')
        .addClass('fadeIn');
    window.fadeOut.defer(5000); //hide in 5s
}
window.fadeOut = function() {
    $('#popup')
        .removeClass('fadeIn')
        .addClass('fadeOut');
}

Chceme, aby se popup zpráva po zavolání fadeIn() během půl sekundy zobrazila, zůstala zobrazena 5 sekund a pak další 2 sekundy mizela.

Ale ono si to tak nějak funguje jak chce – někdy se zobrazí hned, někdo se zobrazuje hodně dlouho, mizí téměř okamžitě apod.

Problém je podobný jako u předchozího případu, tedy že animace vychází ze stavu platícího před změnou způsobující animaci. Pokud tedy chcete, aby se nad prvkem provedla nějaké animace, CSS vlastnost transition musí být nadefinována již před tím, než požadovanou vlastnost změníte.

Pokud tedy chcete měnit hodnotu opacity, musíte hodnotu transition: opacity nadefinovat před tím, že k samotné změně dojde:

#popup {
    position: absolute;
    right: 0; top: 0; z-index: 999;
    opacity: 0;
    //animace pro fadeIn
    transition: opacity 0.5s;
}
.fadeIn {
    opacity: 1;
    //animace pro fadeOut
    transition: opacity 2s;
}
.fadeOut {
    opacity: 0;
    //použije animaci z #popup
}

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *