Animacje css

Animacje w CSS3

Ah animacje, piękne animacje. Ileż to można o nich pisać. Całe książki. Wbrew pozorom jednak wcale aż tyle tego nie ma. Przynajmniej w teorii, bo w praktyce można całkiem fajnie poczarować.

Animacje w CSS3 dzielimy na sztuk dokładnie 2.
Jedne to transition – płynne zmiany właściwości elementów. Drugie to animacje „poklatkowe”.

Transition

Transition czyli płynne zmiany właściwości z jednej wartości na drugą. Przykładowo gdy wartość width elementu zmieni się z 50px na 100px, zmiana ta będzie płynna, co spowoduje, że element na stronie będzie się płynnie rozszerzał.

Ogólna konstrukcja transition ma postać:

/* wersja rozbita na poszczególne właściwości */
transition-property: width; /* właściwość, której zmiana ma być płynna, albo punktujemy po przecinku, albo używamy all - wszystkie właściwości */
transition-duration: 2s; /* jak długa ma być ta zmiana */
transition-timing-function: linear; /* ** - sposób animacji - ease, ease-in, ease-out, ease-in-out, cubic-bezier */
transition-delay: 1s; /* ** - opóźnienie zmiany */

/* lub wersja mix */
transition: width 2s linear 1s; 
transition: width 2s;
...

/* ** - to opcjonalny parametr */

Właściwość transition najczęściej stosowana jest z :hover i :focus:

.button {
    display: inline-block;
    padding: 2rem 4rem;
    border: 0;
    border-radius: 5px;
    background: #F92672;
    color: #fff;
    font-family: sans-serif;
    font-size: 1.2rem;
    box-shadow: 0 0 0 0 #5D0524;

    /* jeżeli zmieni się background niech nastąpi to przez 0.5s, a box-shadow niech zmienia się płynnie przez 1s */
    transition: 0.5s background, 1s box-shadow; 
}

.button:hover, 
.button:focus {
    background: #E35A0F;
    box-shadow:0 10px 0 0 #5D0524;
}

https://codepen.io/kartofelek007/pen/YxGNrq

Ale tak naprawdę może zależeć od każdej rzeczy, która zmienia daną właściwość.
Bardzo częstym przykładem jest dodatnie dodatkowej klasy, która zmienia wygląd danego elementu:

/* przykład bardzo często stosowany np przy pokazywaniu bocznego menu */
.main-nav {
    right:-320px;
    width:320px;
    height:100%;
    position:fixed;
    top:0;    
    background:#000;

    /* zamiast wypunktowywać właściwości po przecinku, mogę zapisać all - wszystkie właściwości */
    transition:1s ease-in-out all; 
}

.main-nav.show {
    transform: translateX(-100%); /* wsuwam menu na ekran */
}

https://codepen.io/kartofelek007/pen/QMEeLY

Powyższe przykłady mogą być nieco bardziej zaawansowane. W poniższym przykładzie animuję nie tylko dany element, ale też jego dzieci wraz z ::before. Wszystkie te zmiany stylowane są przez element rodzica, którego pobieram za pomocą selektora :hover:

.element {
    background: #eee;
    display:inline-block;   
    overflow:hidden; 
    position:relative;
    margin:1px 0;
    padding:0;
}

.element .element-img {
    position:relative;
    transition:2s transform;
    display:block;
}

.element .element-description {
    position:absolute;
    left:0;
    top:0;
    width:100%;
    height:100%;
    background: rgba(242,57,41, 0.7);    
    display: flex;
    justify-content: center;
    align-items: center;    
    color:#fff;
    font-family:sans-serif;
  
    /* nie da się zrobić transition dla display:block/none - bo niby jak - trzeba zastosować opacity i ewentualnie visibility:hidden/visible */    
    opacity:0; 
    transition: 0.5s opacity ease-in-out;
}

.element .element-description:before {
    position:absolute;
    left:-5%;
    top:-20%;
    width:55%;
    height:130%;
    background:rgba(255,255,255,0.1);
    content:'';
    transform:rotate(15deg) translateX(-100%);
    transition:2s all;
}



/* po hover animuj różne elementy w .element */
.element:hover .element-description:before {
    transform:rotate(15deg) translateX(0);
}

.element:hover .element-img {
    transform: scale(1.2, 1.2) rotate(10deg);
}

.element:hover .element-description {
    opacity: 1;    
}

https://codepen.io/kartofelek007/pen/eEdgvQ

A wiesz, że powyższą technikę z dodaniem nowej klasy już kiedyś używaliśmy? Dokładnie przy robieniu slidera w css3.

I w zasadzie tyle.
Jeżeli chcesz się więcej dowiedzieć o transition, poczytaj o nich na jednej z wielu stron: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions.

Czy potrzeba to wkuwać na pamięć? Moim zdaniem nie. Powyższe przykłady – całkiem proste wystarczają do praktycznie 100% zastosowań. Przy ich wyborze najlepiej wzorować się gotowcami, ewentualnie coś podbadać czy podwędzić:

https://tympanus.net/Development/CreativeLinkEffects/
https://tympanus.net/Development/HoverEffectIdeas/
https://tympanus.net/Development/CreativeButtons/

Animacje

Jeżeli transition odpalają się tylko wtedy, gdy dana właściwość elementu się zmienia, tak animation odpalane są wtedy kiedy po prostu je zdefiniujemy w elemencie.
Przy transition animacja danej właściwości (np. width) trwa zawsze od początku animacji do jej końca. Przy animation mamy o wiele większe możliwości, bo zmiany danych właściwości możemy w dowolnym momencie animacji rozpocząć lub zakończyć (coś jak timeline w Photoshopie czy Flashu).

Ogólna definicja animacji ma postać:


/* rozbita jak ktoś lubi dużo pisać */
.element {
    animation-name: animationName; /* nazwa animacji, którą definiujemy poniżej */
    animation-duration: 1s; /* dlugosc animacji - podobnie jak w transition */
    animation-delay: 2s; /* ** - poczatkowe opóźnienie animacji - domyślnie 0s */
    animation-iteration-count: 3; /* ** - liczba powtórzeń - konkretna liczba lub infinite, domyślnie 1 */
    animation-direction: alternate; /* ** - kierunek animacji - alternate, normal. Domyślnie jest normal. Alternate grane jest od początku do końca, a potem od końca do przodu. */
    animation-timing-function: ease-out; /* ** - sposób animacji - ease, ease-in, ease-out, ease-in-out, linear, steps(4), frames(10) */
    animation-fill-mode: forwards; /* ** - po skonczeniu animacji zaaplikuj style z danego kierunku - forwards, backwards, both, none */
    
    /* skrócony zapis */
    animation: animationName 1s 2s 3 alternate forwards;
}    

/* definicja klatek animacji - taki niby timeline z Flasha */
@keyframes animationName {
    0% { 
        width:100px; ... 
    }
    45% { 
        width:200px; 
        background:red; ... 
    }
    100% { 
        width:100px; 
        height:200px; ...  
    }
}

/* ** - parametry opcjonalne */

Definicję @keyframes możemy zapisać w postaci:

@keyframes animationName {
    from { width:100px; ... }
    to { width:200px; height:200px; ...  }
}

ale spokojnie możemy zostać przy procentowych definicjach klatek, bo dają więcej możliwości (w powyższym nie zdefiniujemy klatek pośrednich)

Tak samo jak w przypadku transition, tak i w przypadku animation animację możemy dodawać dla hover, za pomocą dodatkowej klasy itp.
Tyle wiedzy nam wystarczy. Jeżeli chcesz wiedzieć więcej poszperaj np tutaj: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations

Przerwa na kawę

Zauważ, że zarówno przy transition jak i przy animacjach mamy parametr ...-delay, który określa początkowe opóźnienie, z jakim wykona się dana animacja.
Nie mamy natomiast parametru, który by określał przerwę między kolejnymi powtórzeniami animacji. Przy transition nie ma to znaczenia, bo przecież przejście wykonuje się jeden raz. Ale przy animacjach może to zaboleć – i boli.
Wyobraź sobie, że chcesz zrobić animację piłeczki, która odbija się od podłogi:

https://codepen.io/kartofelek007/pen/LjOjeQ

Jeżeli teraz chcielibyśmy dodać przerwę między kolejnymi odbiciami – zwyczajnie mamy problem.
Rozwiązania tego problemu są przynajmniej dwa. Jedno z nich to przeliczenie długości animacji, a następnie zostawienie „nie animowanego kawałka na końcu”

@keyframes animationWithPause {
    0% { bottom:20px; }
    10% { bottom:100px; }
    20% { bottom:20px; }
    100% { bottom: 20px; }
}

Czyli nasza animacja wykona się w 20% czasu animacji, a pozostałe 80% będzie w stanie „spoczynku”. Jest to metoda bardzo niewygodna, bo wszystko trzeba przeskalowywać w głowie, co u niejednego webmastera sprawiło, że wyłysiał.
Przychodzą nam z pomocą mixiny dla SCSS takie jak http://waitanimate.wstone.io/#!/, które automatycznie przeliczają proporcje animacji, ale to też nie jest idealne rozwiązanie, bo nie zakładają, że możemy mieć kilka definicji animacji po przecinku (a możemy – tak samo jak przy kilku tłach, które podajemy po przecinku). Tak czy siak – twórcy CSS – macie wielkiego minusa za brak tego parametru…

JS na ratunek

Drugim sposobem jest wykorzystanie JS.
Domyślnie nasza piłeczka nie będzie animowana. Animację będziemy nadawać za pomocą dodatkowej klasy.
Usuwamy więc definicję animacji z elementu .ball i przenosimy ją do .ball.animate:

.ball {
    ...
}

.ball.animate {
    animation: ballAnimation 0.8s 0s infinite ease-in-out alternate;
}

Gdy pojedyncza animacja się skończy, usuniemy klasę .animate poczekamy odpowiednią liczbę sekund (2s) i ponownie ją dodamy. Do nasłuchania końca animacji wykorzystamy event animationend:

let element = document.querySelector('.ball');
element.classList.add('animate');

element.addEventListener('animationend', function() {
    let element = this;
    element.classList.remove('animate');

    setTimeout(function() {
        element.classList.add('animate');
    }, 1000 * 2); //2 sekundy
})

Dotychczas animacja naszej piłeczki korzystała z właściwości animation-direction: alternate;, czyli animacja wykonywała się od początku do końca, a gdy do niego doszła, automatycznie zawracała odtwarzając się od końca do przodu (i tak w koło Macieju). Żeby nasz powyższy kod JS zadziałał prawidłowo, całość animacji (wraz z powrotem) musimy zdefiniować ręcznie (czyli koniec będzie w okolicach 50%, a następnie do 100% definiujemy powrót), ponieważ animationend nie działa z alternate (a właściwie działa, ale nie bierze pod uwagę animacji automatycznie powracającej – co jest całkiem logiczne).

W definicji animacji dodatkowo zmieniamy alternate na normal, a liczbę powtórzeń animacji zmieniamy z infinite na 1 (bo gdyby było infinite, to animationend musiał by czekać na nieskończoność powtórzeń, co pewnie chwilkę by trało…):

.ball {
    ...
}
.ball.animate {
    animation: ballAnimation 0.8s 0s 1 ease-in-out normal;
}
Tutaj mała uwaga. Istnieje znany problem z usuwaniem i ponownym nadawaniem animacji dla obiektu. Okazuje się, że jeżeli zdejmiemy z obiektu klasę z animacją, a następnie od razu ją dla niego nadamy, wcale nie sprawi, że animacja ponownie się odtworzy.
Problem ten rozwiązuje użycie setTimeout do nadania klasy, lub wymuszenie dla elementu tak zwanego „przerysowania” (reflow). Przerysowanie takie uzyskuje się poprzez „zmianę” jednej z właściwości, której zmiana wywołuje reflow np. element.offsetWidth = element.offsetWidth;
Opisane to jest dokładnie w artykule https://css-tricks.com/restart-css-animation/

Praktyka

I w zasadzie tyle. Miał to być artykuł o zupełnie czymś innym, ale swawolne palce poniosły mnie tam, gdzie im pasowało.

Do napisania powyższego tekstu skłoniła mnie pewna sytuacja.
Jakiś czas temu miałem przyjemność prowadzić zajęcia, na których omawiałem właśnie podstawy animacji css. Sucha teoria jest dla książek, więc wymyśliłem mini projekty do wykonania na zajęciach. Chciałem kursantom pokazać, że nawet najprostszymi animacjami można uzyskać fajny efekt.

Tak powstały tańczące portwory i Marian

Banner z potworami korzysta z najprostszych rozwiązań.
Potwory są zbudowane z zaokrąglonych divów, które posklejane są za pomocą position:relative/absolute, transform-origin (która służy do wyznaczania miejsca-zaczepu, wokół którego ma się wykonywać transformacja – u nas rotacja) i transform:rotate. Cała animacja to zapętlote zmiany transform:rotate, ewentualnie height dla powiek.
Jeżeli chcesz dokładniej sprawdzić jak są zbudowane poszczególne elementy, przejdź do debugera, wejdź w zakładkę Sources i odnajdź style.css. Dopisz w nich na początku * {border:1px solid red;}

Mario takżde zbudowany jest z najprostszych animacji polegających na zapętlonej zmianie właściwości left (jasne – można tutaj bawić się właściwością translate dla lepszej optymalizacji czy spradzać pozycję left rury, ale nie to było celem treningu). Sama animacja Mariana jest zrobiona za pomocą animation-timing-function:frames(...) i korzysta z techniki opisanej pod tym adresem: http://blog.teamtreehouse.com/css-sprite-sheet-animations-steps. Skok Mariana – dość niewygodne do wykonania w klasyczny sposób (ale jednemu kursantowi się to udało) – wykonany jest techniką JS, którą opisałem powyżej.

Swego czasu podobne, bardzo prosciutkie animacje polegające na zmianie opacity, pozycji absolutnej czy lekkim rotate wykorzystałem przy współtworzeniu strony http://spinhouse.pl. Kilka prostych rozwiązań, a efekt może być ciekawy…

Zadanie domowe

Dla treningu polecam zrobić kilka własnych potworów i spróbować wrzucić je na parkiet. Zacznijcie bez animacji, zbudujcie ich sylwetkę – może jedno duże oko na środku („Potwory i spółka” oglądali?), może jakieś rogi, czułki, nibynóżki… (ಠ_ಠ)
Animację zróbcie na samym końcu.
A jak nie macie totalnie pomysłu, to inspiracji możecie szukać np. tutaj?
Nie dajcie się prosić – potwory też lubią bansować…

Komentarze