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 }

Animace během skrolování na prvek

Další problém může nastat s (novou) JS funkcí scrollIntoView(). Tato funkce zajistí, že daný prvek bude pro uživatele viditelný (tedy uvnitř view).

Pokud ale tuhle funkce použijete ve spojení s nastavením stylu (ať už přímo nebo přes třídu), který mění pozici prvku, prohlížeč nebude vědět, zda má naskrolovat na starou nebo novou pozici a tak to vyřeší tím, že animaci přeskočí a naskroluje na novou pozici.

Řešení záleží na situaci. Pokud chcete, aby prvek najel do stránky ze strany a zároveň stránka vertikálně najela na danou pozici, stačí prvek obalit a pak scrolovat na jeho rodičovský prvek:

var form = document.getElementById('myForm');
//spustí animaci
form.classname += ' show';
//spustí skrolování
form.parentnNode
        .scrollIntoView({block: start})
;

Pokud se prvek pouze posouvá po stránce a nelze prostor obalit rodičem, budete muset sami spočítat, kam se má prvek posunout a použít window.scrollTo(X, Y). Tím obejdete to, aby prohlížeč musel znát pozici prvku pro jeho zobrazení.

Napsat komentář

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

Tato stránka používá Akismet k omezení spamu. Podívejte se, jak vaše data z komentářů zpracováváme..