Анимация. Часть 2¶
Переиспользование анимации¶
В Angular анимации можно определить стили один раз и использовать их в нескольких компонентах при создании для них анимированных смен состояний (подобно переиспользованию компонентов, директив и др.).
Используя метод animation()
опишите анимацию и экспортируйте ее.
export const reusableAnimation = animation([
style({
backgroundColor: '{{ backgroundColor }}',
fontSize: '{{ fontSize }}',
width: '{{ width }}',
}),
animate('{{ time }}'),
])
В двойных фигурных скобках описаны параметры, передаваемые анимации при ее вызове через useAnimation()
. Также допустимо использование константных значений.
Пример такой Angular анимации.
@Component({
selector: 'reusable-animation',
templateUrl: './reusable-animation.component.html',
animations: [
trigger('reusableAnimation', [
transition('initial => expanded', useAnimation(reusableAnimation, {
params: {
backgroundColor: '#fff',
fontSize: '16px',
time: '0.3s',
width: '100%'
}
}))
])
]
})
Функция useAnimation()
принимает два параметра: первый - анимация, определенная для переиспользования, второй - объект, в свойстве params которого указываются значения параметров.
Сложная анимация¶
Под сложной анимацией в Angular понимается одновременная или последовательная работа нескольких простых анимаций. В частности, например, она позволяет сделать анимированным появление/исчезание связанной последовательности элементов (пункты списка, строки таблицы).
Реализуется подобное с помощью следующих функций:
query()
- находит один и более дочерних HTML-элементов по заданному критерию в пределах элемента, к которому применяется анимация, и применяет ее к каждому из них;stagger()
- устанавливает задержку для найденных функциейquery()
элементов;group()
- запускает все составляющие анимации параллельно;sequence()
- запускает все составляющие последовательно.
Рассмотрим применение сложной анимации одновременно к нескольким элементам, используя query()
и stagger()
.
animations: [
trigger('appearingItems', [
transition(':enter', [
query('ul.users li', [
style({
opacity: 0,
transform: 'translateY(-100px)',
}),
stagger(-50, [
animate(
'300ms',
style({ opacity: 1, transform: 'none' })
),
]),
]),
]),
]),
]
Здесь Angular анимация appearingItems
определяется для появляющихся элементов списка (состояние :enter
) с классом стилей .users
.
Первым параметром query()
передается селектор (критерий поиска элементов), а вторым - массив с описанием анимации, где первое использование функции style()
задает исходные стили для элементов, попадающих под критерий поиска.
Теперь перейдем к примеру с использованием group()
.
animations: [
trigger('groupAnimation', [
transition(':enter', [
style({
transform: 'translateX(-100px)',
opacity: 0,
}),
group([
animate(
'0.3s ease',
style({ transform: 'translateX(0)' })
),
animate('0.2s 0.15 ease', style({ opacity: 1 })),
]),
]),
]),
]
Сразу стоит отметить, что функция group()
группирует не элементы, а стадии составной анимации применительно к одному элементу, которые работают одновременно и независимо друг от друга.
Как и в предыдущем примере, использование style()
задает исходные стили элемента. Далее с помощью group()
для каждого свойства задается своя конфигурация анимирования.
Для выполнения этой же Angular анимации последовательно без использования задержки, используется функция sequence()
.
animations: [
trigger('sequenceAnimation', [
transition(':enter', [
style({
transform: 'translateX(-100px)',
opacity: 0,
}),
sequence([
animate(
'0.3s ease',
style({ transform: 'translateX(0)' })
),
animate('0.2s 0.15 ease', style({ opacity: 1 })),
]),
]),
]),
]
Анимированная смена маршрутов¶
Анимация маршрутов требует понимания работы модуля маршрутизации Angular.
Фактически переход с одного URL приложения на другой - это просто смена представлений (change views). Используя анимацию Angular можно сделать смену маршрутов анимированной.
app-routing.module.ts
const routes: Routes = [
{
{path: 'page1', component: Page1Component, data: {animation: 'page1'}},
{path: 'page2', component: Page2Component, data: {animation: 'page2'}}
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
app.component.html
<div
[@routeChangeAnimation]="getRouteAnimationState(outlet)"
>
<router-outlet #outlet="outlet"></router-outlet>
</div>
app.component.ts
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
animations: [routeChangeAnimation],
})
export class AppComponent {
constructor() {}
getRouteAnimationState(outlet: RouterOutlet) {
return (
outlet &&
outlet.activatedRouteData &&
outlet.activatedRouteData['animation']
)
}
}
change-route-animation.ts
export const routeChangeAnimation = trigger(
'routeChangeAnimation',
[
transition('page1 <=> page2', [
style({ position: 'relative' }),
query(':enter, :leave', [
style({
position: 'absolute',
top: 0,
left: 0,
width: '100%',
}),
]),
query(':enter', [style({ left: '-100%' })]),
query(':leave', animateChild()),
group([
query(':leave', [
animate(
'300ms ease-out',
style({ left: '100%' })
),
]),
query(':enter', [
animate('300ms ease-out', style({ left: '0%' })),
]),
]),
query(':enter', animateChild()),
]),
]
)
Теперь по порядку. Для определения анимированной смены представления при смене URL используется свойство animation
(название может быть другим), указанное в свойстве маршрута data
. В качестве значения свойству animation
задается имя состояния анимации.
В шаблоне компонента (app.component.html
), в котором будет происходить загрузка представления по запрашиваемому URL, элемент <router-outlet>
является дочерним по отношению к элементу <div>
, для которого определяется routeChangeAnimation
. Для определения имени состояния используется метод getRouteAnimationState()
, извлекающий значение свойства animation
, заданное для текущего маршрута.
Определение самой Angular анимации ограничивается в данном случае описанием смены между собой пары состояний page1
и page2
.
Поскольку в момент смены состояний новое представление вставляется сразу же после предыдущего, элементу, для которого определен триггер, задается относительное позиционирование, а дочерним по отношению к нему элементам - абсолютное. Это нужно для избежания одновременного появления на странице двух представлений.
style({ position: 'relative' }),
query(':enter, :leave', [
style({
position: 'absolute',
top: 0,
left: 0,
width: '100%',
}),
])
Здесь query()
используется для задания исходных стилей выборке элементов.
Далее представление маршрута, на который осуществляется переход, скрывается сдвигом влево.
query(':enter', [style({ left: '-100%' })])
А в представлении, с которого происходит переход, инициируется с помощью функции animateChild()
вызов его дочерних анимаций.
query(':leave', animateChild())
Для понимания, в коде ниже анимация childAnimation
является дочерней по отношению к анимации parentAnimation
:
<div [@parentAnimation]="getState()">
<div [@childAnimation]="getChildState()"></div>
</div>
Таким образом, если функция childAnimate()
вызывается в parentAnimation
, то будет запущена анимация childAnimation
.
После функция group()
запускает одновременно анимированную смену представлений. Старое представление сдвигается за пределы окна вправо, а новое, которое было изначально спрятано слева, появляется.
group([
query(':leave', [
animate('300ms ease-out', style({ left: '100%' })),
]),
query(':enter', [
animate('300ms ease-out', style({ left: '0%' })),
]),
])
И в конце инициируется запуск дочерних Angular анимаций нового представления.
query(':enter', animateChild())