Глибоке занурення у веб-анімацію з Angular

626

Від автора: рух – важливий аспект при створенні сучасного веб-додатки. Насправді, вона важлива для будь-якого роду, в якому присутній інтерфейс і взаємодія. Хороші інтерфейси з продуманої анімацією допомагають користувачам зрозуміти потік між двома станами. Уявіть, що ми знаходимося на простому сайті з однією кнопкою. Ми натискаємо на неї, і без усякого руху перед нами з’являється блок. Хіба це не нудно?

Ми, як користувачі, можемо думати, що блок з’явився з-за наших дій. Однак він міг з’явитися і через http-запиту на тлі. Крім того, Angular анімації можна використовувати в інтерфейсі, щоб зробити його швидше і адаптивнее. Анімація пояснює зміни в розташуванні елементів на екрані, так як деякі дії користувачів можуть міняти UI.

Наші роздуми можуть вивести на абсолютно нову дискусію про UX і про важливість руху. Основна думка тут в тому, що рух робить сайт не тільки зручніше для використання, але і більш веселим. Анімації розповідають історії, додають відчутне вимір часу і покращують загальний UX додатків.

У цій статті ми коротко поговоримо про способи створення анімації в сучасних веб-додатках, зокрема про імперативною і декларативної анімації. Розберемо основи CSS і JavaScript анімації перш ніж поринемо в складну анімацію в контексті програми на Angular.

Давайте спочатку розглянемо загальні принципи роботи веб-анімації.

Поняття переходів між станами

Анімація направляємо користувачів між уявленнями, щоб їм було зручно користуватися сайтом, привертає увагу до окремих частин програми, покращує просторову орієнтацію, показує завантаження даних і, можливо, найважливіше – плавно переводить користувачів між різними станами. Все це можна зробити на CSS (декларативний спосіб) або JavaScript (імперативний спосіб, в основному).

Ви можете запитати, що ж таке переходи? Дуже гарне питання! Словник Oxford дає наступне визначення: «Процес або період зміни одного стану або умови інше»

Застосовно до анімації, перехід – це візуалізація зміни стану в часі. Станом може бути людина, що сидить в аеропорту і чекає посадки на літак. Умова – щось або хтось знаходиться у певному місці в певний час. Кнопка на сайті має 4 стану idle, hover, focus і pressed, де останнім це комбінація focus і active. Для візуалізації принципу роботи можна взяти кінцевий автомат або просту систему переходів між станами.

Глибоке занурення у веб-анімацію з Angular

Точка – це «система» або якийсь елемент на сторінці, який може приймати безліч станів. Замість простого руху з точки А В нас цікавлять значення в межах цього маршруту. Нижче в цьому пості ми побачимо, як з допомогою системи анімації Angular можна домогтися красивою анімації. Для цього необхідно зрозуміти концепцію станів і автоматів. З допомогою transitions ми можемо відслідковувати зміни станів і реагувати.

За допомогою чого можна анімувати UI?

Зараз в сучасних браузерах доступно безліч технологій для анімації UI, в тому числі CSS3 і JS. Сила CSS-анімації (transition або keyframes) в тому, що з її допомогою можна обійтися без JS. CSS-анімація досить швидка і має апаратне прискорення. Однак у цього підходу є свої обмеження. Наприклад, ви не можете анімувати елемент по певному шляху, використовувати фізичні руху або анімувати скрол. CSS-анімація добре підходить для простих переходів між станами (наприклад, hover ефекти), а JS-анімація дає більше гнучкості.

В JS також можна задіяти апаратне прискорення. Це так само легко, як поставити CSS властивість з 3D характеристикою, наприклад, translate3d() або matrix3d(). Це виштовхне елемент на інший шар, який потім обробляється за допомогою GPU. GPU – добре оптимізований під рух пікселів, що робить його ефективним для анімації порівняно з CPU.

CSS-анімація не вимагає сторонніх бібліотек. Тим не менш, є кілька інструментів, які можуть полегшити вам життя, наприклад, бібліотеки із заздалегідь заданими keyframe-анімаціями типу Animate.css.

В JS ж можна використовувати як чистий код, так і jQuery для анімації UI. Працювати з чистою JS-анімацією і вручну встановлювати елементи досить складно. Тому багато переключилися на jQuery. jQuery полегшує пошук елементів на сторінці. Далі для додавання анімації потрібно лише викликати .animate() і вказати властивості (наприклад, opacity або transform) для анімації. Ось так можна пересунути div на 200px вправо, анимировав властивість left:

$(“button”).click(function(){
$(“div”).animate({
left: ‘200px’
}, ‘slow’);
});

Анімація буде працювати, однак краще використовувати або transform, або opacity. Тільки поодинці браузер може легко їх анімувати. Зверніть увагу на рядок slow, за допомогою якої ми визначаємо тривалість анімації. Це еквівалент 600ms.

Виявляється, є ще один новий інструмент — GreenSock – високопродуктивна бібліотека HTML5-анімації для сучасного вебу. З її допомогою можна створити просту анімацію, так і складні тимчасові композиції, drag and drop властивості або навіть плавно змінювати форму SVG фігур. За даними GreenSock, GSAP в 20 разів швидше jQuery. Є хороше порівняння швидкості різних JS-бібліотек, в тому числі jQuery, GSAP або Web Animations.

Давайте створимо таку саму анімацію, як і раніше, але з допомогою GSAP. Використовуємо TweenLite – легкий інструмент анімації, який є основою GSAP:

var button = document.querySelector(‘button’);
button.addEventListener. (‘click’, () => {
TweenLite.to(‘div’, 0.6, {
left: 200
});
});

Для роботи коду зверху необхідний плагін CSSPlugin. Цей плагін дозволяє анімувати майже будь CSS властивість.

При роботі з frontend фреймворками типу Angular для когось може стати відкриттям те, що деякі з них по-різному обробляють анімацію.

Це не означає, що вони використовують якісь інші базові концепції. У фреймворках зазвичай є вбудовані системи анімації. Наприклад, візьмемо систему анімації Angular: вона побудована на Web Animations API (WAAPI). Досить новий інструмент, який зараз працює у Chrome і Firefox. Його завдання – об’єднати CSS, JS і SVG. Цей інструмент бере все найкраще від продуктивності CSS, додає гнучкість JS і SVG, а всю головну роботу залишає браузера без необхідності додавати залежності.

Більш детально про Web Animation API можете прочитати в серії статей, де докладно розповідається про просунуті функції паралельного типу/послідовного запуску декількох анімацій, анімуванні елементів по визначеному шляху або контроль анімації з допомогою AnimationPlayer.

Приклад роботи WAAPI:

var button = document.querySelector(‘button’);
var wrapper = document.querySelector(‘div’);
wrapper.style.position = ‘relative’;
button.addEventListener. (‘click’, () => {
wrapper.animate([
{ left: getComputedStyle(elem).left },
{ left: ‘200px’ }
], { duration: 600, /* and more like easing, delay etc. */ });
});

Не забувайте, що WAAPI досі розробляється, і речі типу аддитиной анімації поки що повністю не підтримуються. Ось чому ми використовуємо getComputedStyle() для обчислення першого KeyframeEffect. KeyframeEffect використовується для встановлення значень властивостей анімації. Кожен ефект представляє один keyframe, а значення екстрапольовані за часом. Іншими словами, масив – це колекція keyfram’ів. Еквівалент CSS keyframe-анімації:

@keyframes moveToRight {
from {
left: 0px;
}
to {
left: 200px;
}
}
div {
position: relative;
animation: moveToRight 600ms forwards;
}

Так само як в WAAPI, нам потрібно задати перше значення при анімуванні властивості left. Цього не потрібно, якщо б ми переміщували елемент по осі Х за допомогою властивості transform. CSS keyframe анімація зазвичай задає, коли зміни відбудуться з процентними значеннями або ключовими словами типу from і to, що прирівнюється до 0% і 100%.

У WAAPI для цього необхідно задавати offset для кожного набору значень властивості (keyframe). Keyframe без зсуву отримують його автоматично, наприклад, перший keyframe отримує 0, останній 1.

На даний момент ми познайомилися з CSS і JS анімацією, а також дізналися, як вона інтегрується в додаток. У наступному розділі ми розглянемо реальний приклад анімації в профілі користувача. Мета – створити однакову анімацію імперативно і декларативно з допомогою GSAP та вбудованої системи Angular. Так, у цій статті буде багато Angular!

Приклад: анімований профіль користувача в модальному вікні

Вистачить теорії! Давайте створимо профіль користувача в модальному вікні і застосуємо до нього анімацію, щоб поліпшити UX та привернути увагу до вікна. Превью:

Глибоке занурення у веб-анімацію з Angular

Наше додаток буде дуже простим і буде складатися з двох компонентів:

DashboardComponent

ProfileDetailsComponent

DashboardComponent – точка входу (кореневої компонент) додатка. У ньому майже немає логіки, це просто обгортка для ProfileDetailsComponent.

У DashboardComponent ми ініціалізуємо дані профілю користувача і перемикаємо видимість вікна. ngIf показує і ховає шаблон. Це важливий момент для анімації, так як ми використовуємо умову як тригер.

Шаблон DashboardComponent:

Dashboard

Глибоке занурення у веб-анімацію з Angular

Шаблон ProfileDetailsComponent:

Глибоке занурення у веб-анімацію з Angular

{{ user.name }}
{{ user.title }}

  • {{ user.views }}

  • {{ user.location }}

  • {{ user.hearts }}

Щоб досягти бажаної анімації, нам потрібно вказати первинні CSS-властивості, щоб активувати 3D простір для дочірніх елементів всередині ProfileDetailsComponent. Для цього необхідно задати perspective на host. CSS host селектори – чудовий спосіб стилі вставки додаткового контейнера.

Однак для анімації нам все одно знадобиться обгортка, так як властивість perspective не впливає на рендеринг елемента host, воно просто активує 3D простір для дочірніх елементів.

:host {
perspective: 500px;

}
.wrapper {
transform-origin: top center;

}

Перспектива впливає тільки на дочірні елементи, причому трансформовані в 3d просторі. Наприклад, при повороті по осі Х, зсуву по осі У. Значення перспективи визначається силою 3D ефекту. Іншими словами, воно описує відстань від об’єкта до спостерігача. З іншого боку, якщо значення велике, відстань між об’єктом і спостерігачем буде великим, і анімація буде виглядати досить добре. Тобто для досягнення 3D ефекту нам потрібно задати властивість perspective.

Також нам потрібно задати початок координат для майбутніх трансформацій. За замовчуванням початок координат – центр елемента. Решта просто стилі.

Тепер давайте реалізуємо імперативну анімацію за допомогою функції timeline GreenSocks.

Імперативний варіант на GreenSocks

Щоб створити анімацію в GSAP, нам потрібно використовувати функцію timeline. Можете використовувати TweenMax або окремо додати проект TimelineLite.

Таймлайн – контейнер, усередині якого часу розміщуються tween’и. Твінінг – процес генерації проміжних кадрів між двома станами. З допомогою таймлайна ми можемо з легкістю створити послідовність анімацій і анімувати елемент .to() або .from(). Також ми отримуємо контроль над анімацією. Ми можемо зупиняти, ставити на паузу, продовжувати або навіть розвертати її у зворотному напрямі. Простий приклад:

window.onload = function () {
var timeline = new TimelineLite();
var h1 = document.getElementById(‘first’);
timeline
.add(‘start’)
.from(h1, 0.7, { opacity: 0, ease: Power2.easeIn }, ‘start’)
.from(h1, 1, { x: 200, ease: Bounce.easeOut }, ‘start’)
.to(‘#second’, 0.3, { backgroundColor: ‘#ffeb3b’ })
.to(‘#third’, 0.3, { x: 200, repeat: 1, yoyo: true }, ‘-=0.3’)
.play();
var button = document.getElementsByTagName(‘button’);
button[0].addEventListener. (‘click’, function() {
timeline.restart();
});
}

Подивіться демо!

З TimelineLite ми отримуємо повний контроль на розміщенням твінів на часовій лінії, також вони можуть перекривати один одного як завгодно. Зверніть увагу, як ми з допомогою .add додаємо лейбл таймлайну. З допомогою лейблів можна запускати кілька анімацій за раз. Наприклад, ми використовуємо цей механізм для запуску двох паралельних анімацій. Заголовок h1 буде одночасно плавно з’являтися і рухатися. Обидві анімації з легкістю можна об’єднати в одну, однак у них різні тимчасові функції. Цей приклад демонструє роботу лейблів.

Давайте подивимося, як зробити це в додатку Angular. Перш за все, ми отримуємо всі елементи за допомогою вбудованих в Angular декораторів @ViewChild() і @ViewChildren(). З їх допомогою ми запитуємо певні елементи всередині подання компонента.

@ViewChild() повертає ElementRef, а @ViewChildren() повертає QueryList. Це об’єкт, в якому зберігається список елементів і який імплементує інтерфейс iterable. Тому його можна використовувати разом з ngFor. Круто, що він заснований на Observables. Тобто ми можемо підписатися на зміни та отримувати повідомлення, коли елемент додається, видаляється або переміщується. Ось так в Angular можна отримати елементи:

@ViewChild(‘wrapper’) wrapper: ElementRef;
@ViewChild(‘main’) main: ElementRef;

Декоратор приймає тип або посилальну змінну на шаблон. У більшості випадків посилальна змінна посилається на елемент DOM усередині шаблону компонента. Наступний код показує, як отримати посилання на елемент wrapper:

Бачите #wrapper? Ось так оголошується посилання на локальний шаблон для певного елемента. Посилання оголошуються на всі елементи, які потрібно анімувати. Тепер можна створити екземпляр таймлайна.

Зазвичай використовується для ініціалізації ngOnInit. Проте в рамках життєвого циклу компонента це занадто рано, так як нам потрібно дочекатися повної ініціалізації компонента, перш ніж використовувати DOM елементи. Є хук ngAfterViewInit – ідеальний момент в процесі ініціалізації компонента, в якому є все, що потрібно для підняття таймлайна.

ngAfterViewInit() {
this.timeline = new TimelineLite();

}

Круто! Але перш ніж будувати таймлайн для анімації профілю, необхідно зробити ще дещо. Нам потрібно застосувати первинну трансформацію до елемента wrapper з допомогою CSS для досягнення прикольного 3D ефекту:

.wrapper {
transform: rotateX(-90deg) translateY(150px) translateZ(50px);

}

Тепер можна застосувати вивчені концепції для побудови таймлайна:

this.timeline
.add(‘start’)
.from(this.wrapper.nativeElement, .15, { opacity: 0 }, ‘start’)
.to(this.wrapper.nativeElement, .3, { rotationX: 0, y: 0, z: 0, ease: Power3.easeIn}, ‘start’)
.add(‘image’, ‘-=0.1’)
.add(‘main’, ‘-=0.15’)
.add(‘icons’, ‘-=0.1’)
.add(‘text’, ‘-=0.05’)
.from(this.profileImageBorder.nativeElement, .3, { scale: 0 }, ‘image’)
.from(this.profileImage.nativeElement, .3, { scale: 0, delay: .05 }, ‘image’)
.from(this.main.nativeElement, .4, { y: ‘100%’ }, ‘main’)
.staggerFrom([this.username.nativeElement, this.title.nativeElement], .3, { opacity: 0, left: 50 }, 0.1, ‘image’)
.staggerFrom(this.statsIcons, .3, { opacity: 0, top: 10 }, 0.1, ‘icons’)
.staggerFrom(this.statsTexts, .3, { opacity: 0 }, 0.1, ‘text’)
.play();

Вау! На перший погляд виглядає приголомшливо. Нам лише потрібно диригувати анімацією з допомогою GreenSock timeline API. З допомогою лейблів можна запускати кілька анімацій паралельно і точно контролювати таймінг. Ми жодного разу не сказали про .staggerFrom(). Stagger – анімація з затримкою після кожної анімації. Вся анімація являє собою:

Глибоке занурення у веб-анімацію з Angular

Подивіться повне демо, можете погратися з ним.

Декларативний варіант анімації Angular

У попередньому розділі ми дізналися, як створити анімацію профілю на GreenSock імперативним способом. У цього підходу є недоліки. По-перше, він шаблонний і збирає всі елементи і вручну задає таймлайн. Тобто для платформи GSAP потрібно, щоб DOM був повністю завантажений. Фреймворк може робити набагато більше припущень щодо інструкцій (анімації) і оточенню (додаток і браузер) перед самою анімацією. По-друге, це корисно, якщо є фреймворк, що підтримує движок анімації типу Angular. GSAP і інші бібліотеки анімації не можуть так просто працювати в DOM ін’єкціями і вилученнями, так як не володіють транзакціями DOM. Angular ж має повний контроль над DOM.

Якщо ви раніше взагалі не працювали з анімацією в Angular, прочитайте пост від Thomas Burleson. У ньому розповідаються основи, а також показується приклад складно анімації появи.

Давайте отрефакторим анімацію профілю з допомогою останніх функцій анімації, представлених у Angular 4.2. Спочатку необхідно імпортувати BrowserAnimationsModule з @angular/platform-browser/animations і додати в imports додатка:

@NgModule({
imports: [
BrowserAnimationsModule

],

})
export class DashboardModule {}

Не забувайте, що анімація в Angular заснована на WAAPI і працює в браузерах з підтримкою, в тому чисто Chrome і Firefox. В даний час підтримка відсутня в IE і Safari. В такому випадку вам знадобиться полифил. Після імпорту та активації анімації ми можемо перейти до визначення анімації профілю.

Повторимо: анімація задається з допомогою властивостей метаданих всередині декоратора @Component(). Кожна анімація визначається за допомогою trigger, який приймає ім’я і список state і transition. Движок анімації Angular працює, як автомат. Звучить знайомо. Пам’ятайте, що ми бачили на початку статті? Перший аргумент transition дозволяє визначити напрямок з одного стану в інший або state-change-expression. Загальні значення:

* => * захоплює зміни між станами

void => * захоплює введення елементів

* => void захоплює відхід з елементів

Останні 2 настільки поширені, що у них є свої скорочення:

:enter для void => *

:leave для * => void

Angular 4.2 представив кілька нових функцій анімації і розширив Angular animation DSL. Огляд нововведень:

query() можна використовувати для пошуку одного або більше елементів всередині елемента анимируемого

stagger() анімує набір елементів з затримкою межу кожної анімацією

group() задає список анімацій для паралельного запуску

sequence() задає список анімацій, які запускаються послідовно

animation() використовується для створення повторно використовуваної анімації з вхідними параметрами

useAnimation() виконує повторно використовувані анімації, створені з допомогою animation()

animateChild() виконує дочірні анімації, які зазвичай блокуються

Давайте використаємо їх для переробки анімації профілю на Angular animation DSL. Щоб продемонструвати більшу частину хелперів зверху, особливо animateChild(), нам потрібно отрефакторить додаток.

Насамперед, ми створимо новий компонент ProfileStatsComponent, в якому буде ul, який до цього був у DashboardComponent. Шаблон DashboardComponent тепер виглядає наступним чином:

Глибоке занурення у веб-анімацію з Angular

{{ user.name }}
{{ user.title }}

Тепер панель містить ProfileStatsComponent, який пізніше задасть свою анімацію. А зараз давайте зосередимося на анімації профілю і поговоримо про дочірньої анімації.

Ось так ми визначаємо наш profileAnimation:

animations: [
trigger(‘profileAnimation’, [
transition(‘:enter group([

]))
])
]

Всередині profileAnimation ми задаємо один transition та по :enter (коли діалогове вікно вставляється в DOM) ми запускаємо паралельно кілька анімацій. Далі з допомогою query() ми беремо DOM елементи і задаємо початкові стилі з допомогою хелперу styles:

animations: [
trigger(‘profileAnimation’, [
transition(‘:enter group([
query(‘.wrapper’, style({ opacity: 0, transform: ‘rotateX(-90deg) translateY(150px) translateZ(50px)’ })),
query(‘.profile-image-border, .profile-image’, style({ transform: ‘scale(0)’ })),
query(‘.username, .username-title’, style({ opacity: 0, transform: ‘translateX(50px)’ })),
query(‘main’, style({ transform: ‘translateY(100%)’ }))
]))
])
]

Пам’ятаєте, як ми збирали DOM елементи за допомогою @ViewChild() і @ViewChildren()? Більше цього не потрібно. Також ми можемо позбутися всіх посилань на локальні шаблони, так як тепер все обробляється query(). Потужно, так?

Перш ніж робити анімацію профілю давайте створимо повторно використовується анімацію fade, яку можна буде використовувати в будь-якому місці з повною підтримкою вхідних параметрів:

export const fadeAnimation = animation([
animate(‘{{ duration }}’, style({ opacity: ‘{{ to }}’ }))
], { params: { duration: ‘1s’, to: 1 }});

fadeAnimation тепер можна імпортувати в наш додаток, налаштувавши через вхідні параметри і запустивши з допомогою useAnimation(). Вхідні значення – значення за замовчуванням.

Тепер давайте додамо відсутні шматочки анімації:

animations: [
trigger(‘profileAnimation’, [
transition(‘:enter group([
// Initial Styles

query(‘.wrapper’, group([
useAnimation(fadeAnimation, {
params: {
duration: ‘150ms’,
to: 1
}
}),
animate(‘300ms cubic-bezier(0.68, 0, 0.68, 0.19)’, style({ transform: ‘matrix(1, 0, 0, 1, 0, 0)’ }))
])),
query(‘.profile-image-border’, [
animate(‘200ms 250ms ease-out’, style(‘*’))
]),
query(‘.profile-image’, [
animate(‘200ms 300ms ease-out’, style(‘*’))
]),
query(‘.username, .username-title’, stagger(‘100ms’, [
animate(‘200ms 250ms ease-out’, style(‘*’))
])),
query(‘main’, [
animate(‘200ms 250ms ease-out’, style(‘*’))
])

]))
])
]

У коді зверху ми запитуємо набір елементів і використовуємо кілька хелперів для досягнення бажаного ефекту. Всі анімації запускаються паралельно, так як вони визначені всередині group(). Також відсутні лейбли або щось схоже на те, що GreenSock надає с .add(). Виходить, Angular досі не підтримує таймлайн, і нам доведеться використовувати затримки для налаштування анімації.

Якщо придивитися ближче, то можна помітити, що це набагато краще. Наприклад, для wrapper ми запускаємо 2 анімації паралельно. Одна анімація повторно використовується. Її можна запустити за допомогою методу useAnimation(). AnimationOptions необов’язкові, ми ставимо їх для перезапису значень за замовчуванням.

Більше того, ми можемо визначити це властивість стилю style(‘*’). Воно видалить всі додані стилі (наприклад, первинні) і скине стан елемента. Це еквівалент установки всіх значень *. Це означає, що Angular дізнається значення в момент виконання. Крім цього, використовуйте допоміжний метод анімації stagger() для декількох елементів з тимчасовими перепустками між аніміруемим елементом.

Застосування анімації з допомогою HostBinding()

Добре, але як використовувати анімацію? Для цього можна прикріпити тригер на елемент усередині шаблону компонента або використовувати @HostBinding(). Ми використовуємо @HostBinding(), так як нам потрібно прикріпити тригер до елемента host:

export class ProfileStatsComponent {

@HostBinding(‘@profileAnimation’)
public animateProfile = true;

}

Поняття дочірньої анімації

У реальному сценарії у вас, швидше за все, в кінцевому підсумку буде кілька компонентів анімації на різних рівнях, наприклад, батьківська і дочірня анімації. Батьківська анімація завжди має вищий пріоритет і блокує будь-яку дочірню. Ганьба. Але не ховайте голову в пісок, Angular прикриє! Ми можемо запитувати внутрішні елементи і використовувати animateChild() для запуску дочірньої анімації. Суть в тому, що робити це можна з будь-якої точки анімації всередині transition.

У нашому прикладі ми створили компонент ProfileStatsComponent. Давайте подивимося його в дії і створимо дочірню анімацію для компонента з допомогою все, що вивчили:

animations: [
trigger(‘statsAnimation’, [
transition(‘* => *’, group([
query(‘.stats-icon’, style({ opacity: 0, transform: ‘scale(0.8) translateY(10px)’ })),
query(‘.stats-text’, style({ opacity: 0 })),
query(‘.stats-icon’, stagger(‘100ms’, [
animate(‘200ms 250ms ease-out’, style(‘*’))
])),
query(‘.stats-text’, stagger(‘100ms’, [
animate(‘200ms 250ms ease-out’, style(‘*’))
])),
])
])
]

Легко, правда? Тепер можна використовувати animateChild() в profileAnimation:

animations: [
trigger(‘profileAnimation’, [
transition(‘:enter group([
// Initial Styles

// Animation

query(‘profile-stats’, animateChild())
]))
])
]

От і все. Ми повністю переробили анімацію профілю за допомогою вбудованої в Angular системи анімації. Все дуже інтуїтивно, легко і декларативно.

Демо. Можете погратися з ним.

Хочете ще більше дізнатися про нові особливості анімації в Angular 4.2, прочитайте цей чудовий пост від Matias Niemela.

Особлива подяка

Особлива подяка Matias Niemelä за приголомшливу роботу над системою анімації в Angular!