35. Angular Animations
🎬 Angular Animations
Заголовок раздела «🎬 Angular Animations»Angular Animations — это мощный механизм анимаций, встроенный во фреймворк. Он работает поверх Web Animations API и даёт декларативный синтаксис прямо в компонентах. Забудь про ручной CSS — Angular умеет анимировать вход, выход, состояния и даже дочерние элементы 🚀
Подключение модуля
Заголовок раздела «Подключение модуля»// app.config.ts (Standalone)import { provideAnimations } from '@angular/platform-browser/animations';// или для No-op (для тестов):import { provideNoopAnimations } from '@angular/platform-browser/animations';
export const appConfig: ApplicationConfig = { providers: [ provideAnimations() ]};Базовые строительные блоки
Заголовок раздела «Базовые строительные блоки»trigger()
Заголовок раздела «trigger()»trigger() — точка входа. Каждый триггер имеет имя и список state/transition:
import { trigger, state, style, animate, transition, keyframes, group, sequence, animateChild, query} from '@angular/animations';
@Component({ selector: 'app-card', animations: [ trigger('cardState', [ state('visible', style({ opacity: 1, transform: 'scale(1)' })), state('hidden', style({ opacity: 0, transform: 'scale(0.8)' })), transition('hidden => visible', animate('300ms ease-out')), transition('visible => hidden', animate('200ms ease-in')), ]) ], template: ` <div [@cardState]="isVisible ? 'visible' : 'hidden'"> Контент карточки </div> <button (click)="isVisible = !isVisible">Переключить</button> `})export class CardComponent { isVisible = true;}state() и style()
Заголовок раздела «state() и style()»state() описывает конечное состояние элемента. style() — это CSS-снимок:
state('expanded', style({ height: '*', // * означает "вычислить автоматически" opacity: 1, padding: '16px',})),state('collapsed', style({ height: '0px', opacity: 0, padding: '0px', overflow: 'hidden',})),transition() и animate()
Заголовок раздела «transition() и animate()»transition() описывает переход между состояниями:
// Конкретный переходtransition('collapsed => expanded', animate('400ms cubic-bezier(0.4, 0, 0.2, 1)')),
// Оба направления одновременноtransition('collapsed <=> expanded', animate('300ms ease-in-out')),
// Любой переход в состояниеtransition('* => expanded', animate('300ms')),
// Вход и выход (специальные псевдо-состояния)transition(':enter', [ style({ opacity: 0, transform: 'translateY(-10px)' }), animate('300ms ease-out', style({ opacity: 1, transform: 'translateY(0)' }))]),transition(':leave', [ animate('200ms ease-in', style({ opacity: 0, transform: 'translateY(10px)' }))]),Продвинутые техники
Заголовок раздела «Продвинутые техники»keyframes() — пошаговая анимация
Заголовок раздела «keyframes() — пошаговая анимация»trigger('bounce', [ transition(':enter', [ animate('600ms', keyframes([ style({ transform: 'translateY(-100%)', offset: 0 }), style({ transform: 'translateY(10px)', offset: 0.7 }), style({ transform: 'translateY(-5px)', offset: 0.85 }), style({ transform: 'translateY(0)', offset: 1.0 }), ])) ])])group() — параллельные анимации
Заголовок раздела «group() — параллельные анимации»group() запускает несколько анимаций одновременно:
transition(':enter', [ group([ animate('400ms ease-out', style({ opacity: 1 })), animate('400ms ease-out', style({ transform: 'translateX(0)' })), ])])sequence() — последовательные анимации
Заголовок раздела «sequence() — последовательные анимации»sequence() выполняет анимации одну за другой:
transition(':enter', [ sequence([ animate('200ms', style({ backgroundColor: '#dd0031' })), animate('200ms', style({ transform: 'scale(1.1)' })), animate('200ms', style({ transform: 'scale(1)' })), ])])animateChild() — анимация дочерних элементов
Заголовок раздела «animateChild() — анимация дочерних элементов»По умолчанию Angular анимирует дочерние элементы параллельно с родителем. animateChild() даёт контроль:
trigger('pageAnimation', [ transition(':enter', [ query(':enter', animateChild(), { optional: true }), // optional: true — не ломать, если дочерних элементов нет ])])AnimationBuilder — программные анимации
Заголовок раздела «AnimationBuilder — программные анимации»Когда декларативный подход не подходит, используй AnimationBuilder для создания анимаций в коде:
import { AnimationBuilder, AnimationPlayer, style, animate } from '@angular/animations';
@Component({ selector: 'app-animated', template: `<div #el>Кликни меня</div>`,})export class AnimatedComponent { @ViewChild('el') el!: ElementRef;
private player: AnimationPlayer | null = null;
constructor(private builder: AnimationBuilder) {}
playAnimation() { // Останавливаем предыдущую анимацию this.player?.destroy();
const factory = this.builder.build([ style({ transform: 'rotate(0deg)' }), animate('1000ms ease-in-out', style({ transform: 'rotate(360deg)' })), ]);
this.player = factory.create(this.el.nativeElement); this.player.play(); this.player.onDone(() => console.log('Анимация завершена!')); }}Анимации роутера
Заголовок раздела «Анимации роутера»Плавные переходы между страницами:
@Component({ template: ` <div [@routeAnimation]="getRouteAnimationData()"> <router-outlet #outlet="outlet" /> </div> `, animations: [ trigger('routeAnimation', [ transition('* <=> *', [ style({ position: 'relative' }), query(':enter, :leave', [ style({ position: 'absolute', top: 0, left: 0, width: '100%' }) ], { optional: true }), query(':enter', [ style({ left: '100%', opacity: 0 }) ], { optional: true }), group([ query(':leave', [ animate('300ms ease-out', style({ left: '-100%', opacity: 0 })) ], { optional: true }), query(':enter', [ animate('300ms ease-out', style({ left: '0%', opacity: 1 })) ], { optional: true }), ]) ]) ]) ]})export class AppComponent { getRouteAnimationData() { // Каждый роут должен иметь data.animation return this.router.routerState.snapshot.root.firstChild?.data?.['animation']; }}@HostBinding для анимаций состояний
Заголовок раздела «@HostBinding для анимаций состояний»@Component({ animations: [ trigger('highlight', [ state('active', style({ boxShadow: '0 0 20px #dd0031' })), state('inactive', style({ boxShadow: 'none' })), transition('inactive <=> active', animate('300ms')), ]) ]})export class HighlightComponent { @HostBinding('@highlight') get animationState() { return this.isActive ? 'active' : 'inactive'; }
isActive = false;}Параметры анимации
Заголовок раздела «Параметры анимации»Можно передавать параметры в анимации для гибкости:
trigger('slide', [ transition(':enter', [ style({ transform: 'translateX({{ from }})' }), animate('{{ duration }}', style({ transform: 'translateX(0)' })) ], { params: { from: '-100%', duration: '300ms' } })])
// В шаблоне<div [@slide]="{ value: '', params: { from: '-200px', duration: '500ms' } }">