От автора: движение – важный аспект при создании современного веб-приложения. На деле, оно важно для ПО любого рода, в котором присутствует интерфейс и взаимодействие. Хорошие интерфейсы с продуманной анимацией помогают пользователям понять поток между двумя состояниями. Представьте, что мы находимся на простом сайте с одной кнопкой. Мы кликаем на нее, и без всякого движения перед нами появляется блок. Разве это не скучно?
Мы, как пользователи, можем думать, что блок появился из-за наших действий. Однако он мог появиться и из-за http-запроса на фоне. Кроме того, Angular анимации можно использовать в интерфейсе, чтобы сделать его быстрее и адаптивнее. Анимация объясняет изменения в расположении элементов на экране, так как некоторые действия пользователей могут менять UI.
Наши размышления могут вывести в абсолютно новую дискуссию о UX и о важности движения. Основная мысль здесь в том, что движение делает сайт не только удобнее для использования, но и более веселым. Анимации рассказывают истории, добавляют ощутимое измерение времени и улучшают общий UX приложений.
В этой статье мы кратко поговорим о способах создания анимации в современных веб-приложениях, в частности об императивной и декларативной анимации. Разберем основы CSS и JavaScript анимации прежде чем погрузимся в сложную анимацию в контексте приложения на Angular.
Давайте сначала разберем общие принципы работы веб-анимации.
Понятие переходов между состояниями
Анимация направляем пользователей между представлениями, чтобы им было удобно пользоваться сайтом, привлекает внимание к отдельным частям приложения, улучшает пространственную ориентацию, показывает загрузку данных и, возможно, самое важное – плавно переводит пользователей между разными состояниями. Все это можно сделать на CSS (декларативный способ) или JavaScript (императивный способ, в основном).
Вы можете спросить, что же такое переходы? Очень хороший вопрос! Словарь Oxford дает следующее определение: «Процесс или период изменения одного состояния или условия в другое»
Применимо к анимации, переход – это визуализация изменения состояния во времени. Состоянием может быть человек, сидящий в аэропорту и ждущий посадки на самолет. Условие – что-то или кто-то, находящийся в определенном месте в определенное время. Кнопка на сайте имеет 4 состояния idle, hover, focus и pressed, где последнее это комбинация focus и active. Для визуализации принципа работы можно взять конечный автомат или простую систему переходов между состояниями.
Точка – это «система» или некий элемент на странице, который может принимать множество состояний. Вместо простого движения из точки А в В нас интересуют значения внутри этого маршрута. Ниже в этом посте мы увидим, как с помощью системы анимации 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:
1 2 3 4 5 |
$("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:
1 2 3 4 5 6 7 |
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:
1 2 3 4 5 6 7 8 9 10 11 |
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-анимации:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@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 и привлечь внимание к окну. Превью:
Наше приложение будет очень простым и будет состоять из двух компонентов:
DashboardComponent
ProfileDetailsComponent
DashboardComponent – точка входа (корневой компонент) приложения. В нем почти нет логики, это просто обертка для ProfileDetailsComponent.
В DashboardComponent мы инициализируем данные профиля пользователя и переключаем видимость окна. ngIf показывает и прячет шаблон. Это важный момент для анимации, так как мы используем условие как триггер.
Шаблон DashboardComponent:
1 2 3 4 5 6 7 8 9 10 |
<div> <header> <span class="title">Dashboard</span> <div class="image-container" (click)="toggleProfileDetails()" data-tooltip="Profile" > <img class="profile-button" src="..." /> </div> </header> <profile-details [user]="user" *ngIf="showProfileDetails"></profile-details> </div> |
Шаблон ProfileDetailsComponent:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<div class="wrapper"> <header> <div class="profile-image-wrapper"> <div class="profile-image-border"></div> <img class="profile-image" src="..." /> </div> <div class="profile-header-content"> <span class="username">{{ user.name }}</span> <span class="username-title">{{ user.title }}</span> </div> </header> <main> <ul class="stats"> <li class="stats-item"> <span class="stats-icon icon-eye"></span> <span>{{ user.views }}</span> </li> <li class="stats-item"> <span class="stats-icon icon-location"></span> <span>{{ user.location }}</span> </li> <li class="stats-item"> <span class="stats-icon icon-heart"></span> <span>{{ user.hearts }}</span> </li> </ul> </main> </div> |
Чтобы достичь желаемой анимации, нам нужно задать первичные CSS-свойства, чтобы активировать 3D пространство для дочерних элементов внутри ProfileDetailsComponent. Для этого необходимо задать perspective на host. CSS host селекторы – замечательный способ применять стили без вставки дополнительного контейнера.
Однако для анимации нам все равно понадобится обертка, так как свойство perspective не влияет на рендеринг элемента host, оно просто активирует 3D пространство для дочерних элементов.
1 2 3 4 5 6 7 8 9 |
:host { perspective: 500px; ... } .wrapper { transform-origin: top center; ... } |
Перспектива влияет только на дочерние элементы, причем трансформированные в 3d пространстве. Например, при повороте по оси Х, сдвиге по оси У. Значение перспективы определяется силой 3D эффекта. Другими словами, оно описывает расстояние от объекта до наблюдателя. С другой стороны, если значение большое, расстояние между объектом и наблюдателем будет большим, и анимация будет выглядеть довольно хорошо. Т.е. для достижения 3D эффекта нам нужно задать свойство perspective.
Также нам нужно задать начало координат для будущих трансформаций. По умолчанию начало координат – центр элемента. Остальное просто стили.
Теперь давайте реализуем императивную анимацию с помощью функции timeline GreenSocks.
Императивный вариант на GreenSocks
Чтобы создать анимацию в GSAP, нам нужно использовать функцию timeline. Можете использовать TweenMax или отдельно добавить проект TimelineLite.
Таймлайн – контейнер, внутри которого по времени размещаются tween’ы. Твининг – процесс генерации промежуточных кадров между двумя состояниями. С помощью таймлайна мы можем с легкостью создать последовательность анимаций и анимировать элемент .to() или .from(). Также мы получаем контроль над анимацией. Мы можем останавливать, ставить на паузу, продолжать или даже разворачивать ее в обратном направлении. Простой пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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 можно получить элементы:
1 2 3 |
@ViewChild('wrapper') wrapper: ElementRef; @ViewChild('main') main: ElementRef; ... |
Декоратор принимает тип или ссылочную переменную на шаблон. В большинстве случаев ссылочная переменная ссылается на элемент DOM внутри шаблона компонента. Следующий код показывает, как получить ссылку на элемент wrapper:
1 2 3 |
<div class="wrapper" #wrapper> ... </div> |
Видите #wrapper? Вот так объявляется ссылка на локальный шаблон для определенного элемента. Ссылки объявляются на все элементы, которые необходимо анимировать. Теперь можно создать экземпляр таймлайна.
Обычно для инициализации используется ngOnInit. Однако в рамках жизненного цикла компонента это слишком рано, так как нам нужно дождаться полной инициализации компонента, прежде чем использовать DOM элементы. Есть хук ngAfterViewInit – идеальный момент в процессе инициализации компонента, в котором есть все, что нужно для поднятия таймлайна.
1 2 3 4 |
ngAfterViewInit() { this.timeline = new TimelineLite(); ... } |
Круто! Но прежде чем строить таймлайн для анимации профиля, необходимо сделать еще кое-что. Нам нужно применить первичную трансформацию к элементу wrapper с помощью CSS для достижения прикольного 3D эффекта:
1 2 3 4 |
.wrapper { transform: rotateX(-90deg) translateY(150px) translateZ(50px); ... } |
Теперь можно применить выученные концепции для построения таймлайна:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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
В предыдущем разделе мы узнали, как создать анимацию профиля на GreenSock императивным способом. У этого подхода есть недостатки. Во-первых, он шаблонный и собирает все элементы и вручную задает таймлайн. Т.е. для платформы GSAP нужно, чтобы DOM был полностью загружен. Фреймворк может делать гораздо больше предположений по инструкциям (данные для анимации) и окружению (приложение и браузер) перед самой анимацией. Во-вторых, это полезно, если есть фреймворк, поддерживающий движок анимации типа Angular. GSAP и другие библиотеки анимации не могут так просто работать в DOM инъекциями и удалениями, так как не владеют транзакциями DOM. Angular же имеет полный контроль над DOM.
Если вы раньше вообще не работали с анимацией в Angular, прочитайте пост от Thomas Burleson. В нем рассказываются основы, а также показывается пример сложно анимации появления.
Давайте отрефакторим анимацию профиля с помощью последних функций анимации, представленных в Angular 4.2. Сначала необходимо импортировать BrowserAnimationsModule из @angular/platform-browser/animations и добавить в imports приложения:
1 2 3 4 5 6 7 8 |
@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 теперь выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<div class="wrapper"> <header> <div class="profile-image-wrapper"> <div class="profile-image-border"></div> <img class="profile-image" src="//api.adorable.io/avatars/90/me@you.com.png" /> </div> <div class="profile-header-content"> <span class="username">{{ user.name }}</span> <span class="username-title">{{ user.title }}</span> </div> </header> <main> <profile-stats [user]="user"></profile-stats> </main> </div> |
Теперь панель содержит ProfileStatsComponent, который позже задаст свою анимацию. А сейчас давайте сосредоточимся на анимации профиля и поговорим о дочерней анимации.
Вот так мы определяем наш profileAnimation:
1 2 3 4 5 6 7 |
animations: [ trigger('profileAnimation', [ transition(':enter', group([ ... ])) ]) ] |
Внутри profileAnimation мы задаем один transition и по :enter (когда диалоговое окно вставляется в DOM) мы запускаем параллельно несколько анимаций. Далее с помощью query() мы берем DOM элементы и задаем первоначальные стили с помощью хелпера styles:
1 2 3 4 5 6 7 8 9 10 |
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, которую можно будет использовать в любом месте с полной поддержкой входных параметров:
1 2 3 |
export const fadeAnimation = animation([ animate('{{ duration }}', style({ opacity: '{{ to }}' })) ], { params: { duration: '1s', to: 1 }}); |
fadeAnimation теперь можно импортировать в наше приложение, настроив через входные параметры и запустив с помощью useAnimation(). Входные значения – значения по умолчанию.
Теперь давайте добавим недостающие кусочки анимации:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
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:
1 2 3 4 5 6 7 8 |
export class ProfileStatsComponent { ... @HostBinding('@profileAnimation') public animateProfile = true; ... } |
Понятие дочерней анимации
В реальном сценарии у вас, скорее всего, в конечном итоге будет несколько компонентов анимации на разных уровнях, например, родительская и дочерняя анимации. Родительская анимация всегда имеет высший приоритет и блокирует любую дочернюю. Позор. Но не прячьте голову в песок, Angular прикроет! Мы можем запрашивать внутренние элементы и использовать animateChild() для запуска дочерней анимации. Суть в том, что делать это можно из любой точки анимации внутри transition.
В нашем примере мы создали компонент ProfileStatsComponent. Давайте посмотрим его в действии и создадим дочернюю анимацию для компонента с помощью всего, что изучили:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 |
animations: [ trigger('profileAnimation', [ transition(':enter', group([ // Initial Styles ... // Animation ... query('profile-stats', animateChild()) ])) ]) ] |
Вот и все. Мы полностью переделали анимацию профиля с помощью встроенной в Angular системы анимации. Все крайне интуитивно, легко и декларативно.
Демо. Можете поиграться с ним.
Хотите еще больше узнать о новых особенностях анимации в Angular 4.2, прочитайте этот замечательный пост от Matias Niemela.
Особая благодарность
Особая благодарность Matias Niemelä за потрясающую работу над системой анимации в Angular!
Автор: Dominic Elm
Источник: //blog.thoughtram.io/
Редакция: Команда webformyself.