Spec-Zone.ru › Angular

Анимация приложений с помощью animate.enter и animate.leave

Хорошо продуманные анимации делают приложение более интуитивным и привлекательным, но они нужны не только для красоты. Анимации могут улучшить приложение и пользовательский интерфейс несколькими способами:

  • Без анимаций переходы между страницами могут казаться резкими и неприятными
  • Движение значительно улучшает пользовательский интерфейс, поэтому анимации позволяют пользователям видеть реакцию приложения на их действия
  • Хорошие анимации могут плавно направлять внимание пользователя на протяжении всего рабочего процесса

Angular предоставляет animate.enter и animate.leave для анимации элементов приложения. Эти две возможности применяют CSS-классы входа и выхода в подходящий момент или вызывают функции для применения анимаций из сторонних библиотек. animate.enter и animate.leave — это не директивы. Это специальные API, поддерживаемые непосредственно компилятором Angular. Их можно использовать непосредственно для элементов, а также в привязке к хосту.

animate.enter

Вы можете использовать animate.enter для анимации элементов при их добавлении в DOM. Анимации входа можно задать с помощью CSS-классов с переходами или покадровыми анимациями.

import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-enter',
  templateUrl: 'enter.html',
  styleUrls: ['enter.css'],
})
export class Enter {
  isShown = signal(false);

  toggle() {
    this.isShown.update((isShown) => !isShown);
  }
}
<h2><code>animate.enter</code> Example</h2>

<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>

@if (isShown()) {
  <div class="enter-container" animate.enter="enter-animation">
    <p>The box is entering.</p>
  </div>
}
:host {
  display: block;
  height: 200px;
}

.enter-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px;
  font-weight: bold;
  font-size: 20px;
}

.enter-container p {
  margin: 0;
}

.enter-animation {
  animation: slide-fade 1s;
}

@keyframes slide-fade {
  from {
    opacity: 0;
    transform: translateY(20px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}

После завершения анимации Angular удаляет из DOM класс или классы, указанные в animate.enter. Классы анимации присутствуют только во время выполнения анимации.

ПРИМЕЧАНИЕ: Если для элемента используются несколько покадровых анимаций или свойств перехода, Angular удаляет все классы только после завершения самой длительной анимации.

Вы можете использовать animate.enter с любыми другими возможностями Angular, например с потоком управления или динамическими выражениями. animate.enter принимает строку с одним классом (несколько классов разделяются пробелами) или массив строк с классами.

Краткое примечание об использовании CSS-переходов: если вместо покадровых анимаций вы решите использовать переходы, классы, добавленные к элементу с помощью animate.enter, задают состояние, к которому будет выполняться переход. Базовый CSS элемента определяет его внешний вид, когда анимации не выполняются, и, вероятно, будет похож на конечное состояние CSS-перехода. Поэтому для работы перехода вам всё равно потребуется использовать @starting-style, чтобы задать подходящее исходное состояние.

import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-enter-binding',
  templateUrl: 'enter-binding.html',
  styleUrls: ['enter-binding.css'],
})
export class EnterBinding {
  isShown = signal(false);

  toggle() {
    this.isShown.update((isShown) => !isShown);
  }

  enterClass = signal('enter-animation');
}
<h2><code>animate.enter</code> Binding Example</h2>

<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>

@if (isShown()) {
  <div class="enter-container" [animate.enter]="enterClass()">
    <p>The box is entering.</p>
  </div>
}
:host {
  display: block;
  height: 200px;
}

.enter-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px;
  font-weight: bold;
  font-size: 20px;
}

.enter-container p {
  margin: 0;
}

.enter-animation {
  animation: slide-fade 1s;
}

@keyframes slide-fade {
  from {
    opacity: 0;
    transform: translateY(20px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}

animate.leave

Вы можете использовать animate.leave для анимации элементов при их удалении из DOM. Анимации выхода можно задать с помощью CSS-классов с преобразованиями или покадровыми анимациями.

import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-leave',
  templateUrl: 'leave.html',
  styleUrls: ['leave.css'],
})
export class Leave {
  isShown = signal(false);

  toggle() {
    this.isShown.update((isShown) => !isShown);
  }
}
<h2><code>animate.leave</code> Example</h2>

<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>

@if (isShown()) {
  <div class="leave-container" animate.leave="leaving">
    <p>Goodbye</p>
  </div>
}
:host {
  display: block;
  height: 200px;
}

.leave-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px;
  font-weight: bold;
  font-size: 20px;
  opacity: 1;
  transition: opacity 200ms ease-in;

  @starting-style {
    opacity: 0;
  }
}

.leave-container p {
  margin: 0;
}

.leaving {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 500ms ease-out,
    transform 500ms ease-out;
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}

После завершения анимации Angular автоматически удаляет анимированный элемент из DOM.

ПРИМЕЧАНИЕ: Если для элемента используются несколько покадровых анимаций или свойств перехода, Angular ждёт завершения самой длительной анимации, и только после этого удаляет элемент.

animate.leave также можно использовать с сигналами и другими привязками. animate.leave можно использовать с одним или несколькими классами. Укажите их в виде простой строки с пробелами или массива строк.

import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-leave-binding',
  templateUrl: 'leave-binding.html',
  styleUrls: ['leave-binding.css'],
})
export class LeaveBinding {
  isShown = signal(false);

  toggle() {
    this.isShown.update((isShown) => !isShown);
  }

  farewell = signal('leaving');
}
<h2><code>animate.leave</code> Binding Example</h2>

<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>

@if (isShown()) {
  <div class="leave-container" [animate.leave]="farewell()">
    <p>Goodbye</p>
  </div>
}
:host {
  display: block;
  height: 200px;
}

.leave-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px;
  font-weight: bold;
  font-size: 20px;
  opacity: 1;
  transition: opacity 200ms ease-in;

  @starting-style {
    opacity: 0;
  }
}

.leave-container p {
  margin: 0;
}

.leaving {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 500ms ease-out,
    transform 500ms ease-out;
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}

Порядок удаления элементов

При выполнении анимаций animate.leave и определении момента запуска анимации есть некоторые нюансы. animate.leave работает, если он размещён на удаляемом элементе. Если же animate.leave размещён на элементе, который является потомком удаляемого элемента, анимации дочерних элементов выполняются до удаления родительского узла из DOM. Это позволяет вам уверенно анимировать удаление дочерних элементов, не опасаясь, что родительский узел исчезнет раньше времени.

import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-leave-parent',
  templateUrl: 'leave-parent.html',
  styleUrls: ['leave-parent.css'],
})
export class LeaveParent {
  isShown = signal(false);

  toggle() {
    this.isShown.update((isShown) => !isShown);
  }
}
<h2><code>animate.leave</code> Parent Sub-tree Animation Example</h2>

<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>

@if (isShown()) {
  <div class="leave-parent">
    <!-- the child node with `animate.leave` will animate first
         before the parent node is removed from the DOM. -->
    <div class="leave-container" animate.leave="leaving">
      <p>Goodbye</p>
    </div>
  </div>
}
:host {
  display: block;
  height: 200px;
}

.leave-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px;
  font-weight: bold;
  font-size: 20px;
  opacity: 1;
  transition: opacity 200ms ease-in;

  @starting-style {
    opacity: 0;
  }
}

.leave-container p {
  margin: 0;
}

.leaving {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 500ms ease-out,
    transform 500ms ease-out;
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}

Привязки событий, функции и сторонние библиотеки

И animate.enter, и animate.leave поддерживают синтаксис привязки событий, позволяющий вызывать функции. Вы можете использовать этот синтаксис для вызова функции в коде компонента или применять сторонние библиотеки анимации, например GSAP, anime.js или любые другие библиотеки анимации JavaScript.

import {AnimationCallbackEvent, Component, signal} from '@angular/core';

@Component({
  selector: 'app-leave-binding',
  templateUrl: 'leave-event.html',
  styleUrls: ['leave-event.css'],
})
export class LeaveEvent {
  isShown = signal(false);

  toggle() {
    this.isShown.update((isShown) => !isShown);
  }

  leavingFn(event: AnimationCallbackEvent) {
    // Example of calling GSAP
    // gsap.to(event.target, {
    //   duration: 1,
    //   x: 100,
    //   // arrow functions are handy for concise callbacks
    //   onComplete: () => event.animationComplete()
    // });
    event.animationComplete();
  }
}
<h2><code>animate.leave</code> Function Example</h2>

<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>

@if (isShown()) {
  <div class="leave-container" (animate.leave)="leavingFn($event)">
    <p>Goodbye</p>
  </div>
}
:host {
  display: block;
  height: 200px;
}

.leave-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px;
  font-weight: bold;
  font-size: 20px;
  opacity: 1;
  transition: opacity 200ms ease-in;

  @starting-style {
    opacity: 0;
  }
}

.leave-container p {
  margin: 0;
}

.leaving {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 500ms ease-out,
    transform 500ms ease-out;
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}

Объект $event имеет тип AnimationCallbackEvent. Он содержит элемент в свойстве target и предоставляет функцию animationComplete(), которая позволяет сообщить фреймворку о завершении анимации.

ВАЖНО: При использовании animate.leave необходимо вызвать функцию animationComplete(), чтобы Angular удалил элемент.

Если при использовании animate.leave не вызвать animationComplete(), Angular автоматически вызовет эту функцию через четыре секунды. Вы можете настроить длительность задержки в миллисекундах, указав токен MAX_ANIMATION_TIMEOUT.

  { provide: MAX_ANIMATION_TIMEOUT, useValue: 6000 }

Совместимость с устаревшими анимациями Angular

Нельзя использовать устаревшие анимации вместе с animate.enter и animate.leave в одном компоненте. Это может привести к тому, что классы входа останутся на элементе или узлы выхода не будут удалены. Однако вполне допустимо использовать как устаревшие анимации, так и новые анимации animate.enter и animate.leave в одном приложении. Единственное ограничение касается проецирования содержимого. Если вы проецируете содержимое из компонента с устаревшими анимациями в другой компонент с animate.enter или animate.leave либо наоборот, поведение будет таким же, как при их использовании в одном компоненте. Такой вариант не поддерживается.

Тестирование

TestBed предоставляет встроенную поддержку включения и отключения анимаций в тестовой среде. Для выполнения CSS-анимаций требуется браузер, а многие API недоступны в тестовой среде. По умолчанию TestBed отключает анимации в тестовой среде.

Если вы хотите проверить выполнение анимаций в браузерном тесте, например в сквозном тесте, настройте TestBed так, чтобы анимации были включены, указав animationsEnabled: true в конфигурации теста.

TestBed.configureTestingModule({animationsEnabled: true});

Это настроит анимации в тестовой среде для работы в обычном режиме.

ПРИМЕЧАНИЕ: Некоторые тестовые среды не генерируют события анимации, например animationstart и animationend, а также соответствующие им события перехода.

Подробнее об анимациях Angular

Вам также могут быть интересны следующие материалы:

Сложные анимации с помощью CSS Анимации переходов между маршрутами

Super-powered by Google ©2010–2025.
Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0.
https://angular.dev/guide/animations

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API