Внести вклад
Расширенная экосистема
Устаревшие анимации

Animation transitions и triggers

ВАЖНО: Пакет @angular/animations теперь устарел. Команда Angular рекомендует использовать нативный CSS с animate.enter и animate.leave для анимаций во всём новом коде. Подробнее в новом руководстве по enter и leave анимациям. Также см. Migrating away from Angular's Animations package, чтобы узнать, как начать миграцию на чистые CSS-анимации в приложениях.

Это руководство подробно рассматривает специальные состояния переходов, такие как wildcard * и void. Также показывает, как эти состояния используются для элементов, входящих в view и покидающих его. Этот раздел также исследует несколько animation triggers, animation callbacks и sequence-based анимацию с использованием keyframes.

Предопределённые состояния и wildcard matching

В Angular состояния переходов можно определить явно через функцию state() или используя предопределённые состояния * wildcard и void.

Wildcard-состояние

Звёздочка * или wildcard совпадает с любым состоянием анимации. Это полезно для определения переходов, которые применяются независимо от начального или конечного состояния HTML-элемента.

Например, переход open => * применяется, когда состояние элемента меняется из open на что угодно ещё.

wildcard state expressions

Ниже ещё один пример кода, использующий wildcard-состояние вместе с предыдущим примером с состояниями open и closed. Вместо определения каждой пары переходов state-to-state любой переход в closed занимает 1 секунду, а любой переход в open — 0.5 секунды.

Это позволяет добавлять новые состояния без необходимости включать отдельные переходы для каждого.

open-close.ts

animations: [
    trigger('openClose', [
      // ...
      state(
        'open',
        style({
          height: '200px',
          opacity: 1,
          backgroundColor: 'yellow',
        }),
      ),
      state(
        'closed',
        style({
          height: '100px',
          opacity: 0.8,
          backgroundColor: 'blue',
        }),
      ),
      transition('* => closed', [animate('1s')]),
      transition('* => open', [animate('0.5s')]),
    ]),
  ],

Используйте синтаксис двойной стрелки для указания переходов state-to-state в обоих направлениях.

open-close.ts

transition('open <=> closed', [animate('0.5s')]),

Использование wildcard-состояния с несколькими состояниями перехода

В примере кнопки с двумя состояниями wildcard не так полезен, потому что есть только два возможных состояния — open и closed. В общем случае используйте wildcard-состояния, когда у элемента несколько потенциальных состояний, в которые он может перейти. Если кнопка может перейти из open либо в closed, либо во что-то вроде inProgress, использование wildcard-состояния может уменьшить объём необходимого кода.

wildcard state with 3 states

open-close.ts

animations: [
    trigger('openClose', [
      // ...
      state(
        'open',
        style({
          height: '200px',
          opacity: 1,
          backgroundColor: 'yellow',
        }),
      ),
      state(
        'closed',
        style({
          height: '100px',
          opacity: 0.8,
          backgroundColor: 'blue',
        }),
      ),
      transition('open => closed', [animate('1s')]),
      transition('closed => open', [animate('0.5s')]),
      transition('* => closed', [animate('1s')]),
      transition('* => open', [animate('0.5s')]),
      transition('open <=> closed', [animate('0.5s')]),
      transition('* => open', [animate('1s', style({opacity: '*'}))]),
      transition('* => *', [animate('1s')]),

Переход * => * применяется, когда происходит любое изменение между двумя состояниями.

Переходы сопоставляются в порядке, в котором они определены. Таким образом, можно применять другие переходы поверх перехода * => *. Например, определите изменения стилей или анимации, которые применялись бы только к open => closed, затем используйте * => * как fallback для пар состояний, которые иначе не указаны.

Для этого перечислите более специфичные переходы перед * => *.

Использование wildcards со стилями

Используйте wildcard * со стилем, чтобы сообщить анимации использовать текущее значение стиля и анимировать с ним. Wildcard — fallback-значение, используемое, если анимируемое состояние не объявлено внутри trigger.

open-close.ts

transition('* => open', [animate('1s', style({opacity: '*'}))]),

Состояние void

Используйте состояние void для настройки переходов для элемента, входящего на страницу или покидающего её. См. Animating entering and leaving a view.

Комбинирование wildcard и void состояний

Комбинируйте wildcard и void состояния в переходе, чтобы запускать анимации входа и выхода со страницы:

  • Переход * => void применяется, когда элемент покидает view, независимо от того, в каком состоянии он был до ухода
  • Переход void => * применяется, когда элемент входит в view, независимо от того, какое состояние он принимает при входе
  • Wildcard-состояние * совпадает с любым состоянием, включая void

Анимация входа и выхода из view

Этот раздел показывает, как анимировать элементы, входящие на страницу или покидающие её.

Добавьте новое поведение:

  • Когда вы добавляете hero в список heroes, он кажется влетающим на страницу слева
  • Когда вы удаляете hero из списка, он кажется улетающим вправо

hero-list-enter-leave.ts

animations: [
    trigger('flyInOut', [
      state('in', style({transform: 'translateX(0)'})),
      transition('void => *', [style({transform: 'translateX(-100%)'}), animate(100)]),
      transition('* => void', [animate(100, style({transform: 'translateX(100%)'}))]),
    ]),
  ],

В предыдущем коде вы применили состояние void, когда HTML-элемент не прикреплён к view.

Алиасы :enter и :leave

:enter и :leave — алиасы для переходов void => * и * => void. Эти алиасы используются несколькими функциями анимации.

transition ( ':enter', [ … ] ); // alias for void => _
transition ( ':leave', [ … ] ); // alias for _ => void

Сложнее таргетировать элемент, входящий в view, потому что его ещё нет в DOM. Используйте алиасы :enter и :leave для таргетинга HTML-элементов, вставляемых в view или удаляемых из него.

Использование *ngIf и *ngFor с :enter и :leave

Переход :enter выполняется, когда любые views *ngIf или *ngFor помещаются на страницу, а :leave выполняется, когда эти views удаляются со страницы.

ВАЖНО: Поведение входа/выхода иногда может быть запутанным. Как правило, считайте, что любой элемент, добавляемый в DOM Angular, проходит через переход :enter. Только элементы, напрямую удаляемые из DOM Angular, проходят через переход :leave. Например, view элемента удаляется из DOM, потому что его родитель удаляется из DOM.

В этом примере есть специальный trigger для анимации enter и leave под названием myInsertRemoveTrigger. HTML-шаблон содержит следующий код.

insert-remove.html

@if (isShown) {
  <div @myInsertRemoveTrigger class="insert-remove-container">
    <p>The box is inserted</p>
  </div>
}

В файле компонента переход :enter задаёт начальную opacity 0. Затем анимирует её до изменения opacity на 1, когда элемент вставляется в view.

insert-remove.ts

trigger('myInsertRemoveTrigger', [
      transition(':enter', [style({opacity: 0}), animate('100ms', style({opacity: 1}))]),
      transition(':leave', [animate('100ms', style({opacity: 0}))]),
    ]),

Обратите внимание, что этому примеру не нужно использовать state().

Переходы :increment и :decrement

Функция transition() принимает другие значения селектора — :increment и :decrement. Используйте их, чтобы запустить переход, когда числовое значение увеличилось или уменьшилось.

ПОЛЕЗНО: Следующий пример использует методы query() и stagger(). Подробнее об этих методах см. на странице complex sequences.

hero-list-page.ts

trigger('filterAnimation', [
      transition(':enter, * => 0, * => -1', []),
      transition(':increment', [
        query(
          ':enter',
          [
            style({opacity: 0, width: 0}),
            stagger(50, [animate('300ms ease-out', style({opacity: 1, width: '*'}))]),
          ],
          {optional: true},
        ),
      ]),
      transition(':decrement', [
        query(':leave', [stagger(50, [animate('300ms ease-out', style({opacity: 0, width: 0}))])]),
      ]),
    ]),

Boolean-значения в переходах

Если trigger содержит Boolean-значение как значение привязки, то это значение можно сопоставить с помощью выражения transition(), сравнивающего true и false, или 1 и 0.

open-close.html

<div [@openClose]="isOpen ? true : false" class="open-close-container">
</div>

В фрагменте кода выше HTML-шаблон привязывает элемент <div> к trigger с именем openClose с выражением статуса isOpen и возможными значениями true и false. Этот паттерн — альтернатива практике создания двух именованных состояний вроде open и close.

Внутри метаданных @Component в свойстве animations: когда состояние вычисляется в true, высота ассоциированного HTML-элемента — wildcard-стиль или значение по умолчанию. В этом случае анимация использует ту высоту, которая уже была у элемента до начала анимации. Когда элемент closed, элемент анимируется до высоты 0, что делает его невидимым.

open-close.ts

animations: [
    trigger('openClose', [
      state('true', style({height: '*'})),
      state('false', style({height: '0px'})),
      transition('false <=> true', animate(500)),
    ]),
  ],

Несколько animation triggers

Можно определить более одного animation trigger для компонента. Прикрепляйте animation triggers к разным элементам, и parent-child отношения между элементами влияют на то, как и когда выполняются анимации.

Parent-child анимации

Каждый раз, когда анимация запускается в Angular, parent-анимация всегда получает приоритет, а child-анимации блокируются. Чтобы child-анимация выполнилась, parent-анимация должна сделать query каждого из элементов, содержащих child-анимации. Затем она позволяет анимациям выполняться с помощью функции animateChild().

Отключение анимации на HTML-элементе

Специальную привязку управления анимацией @.disabled можно поместить на HTML-элемент, чтобы отключить анимации на этом элементе, а также на любых вложенных элементах. Когда true, привязка @.disabled предотвращает отрисовку всех анимаций.

Следующий пример кода показывает, как использовать эту возможность.

Когда привязка @.disabled равна true, trigger @childAnimation не запускается.

Когда у элемента в HTML-шаблоне анимации отключены с помощью host binding @.disabled, анимации отключаются и на всех внутренних элементах. Нельзя выборочно отключить несколько анимаций на одном элементе.

Выборочные child-анимации всё ещё могут выполняться на отключённом родителе одним из следующих способов:

  • Parent-анимация может использовать функцию query() для сбора внутренних элементов, расположенных в отключённых областях HTML-шаблона. Эти элементы всё ещё могут анимироваться.
  • Child-анимация может быть найдена query родителем и затем позже анимирована функцией animateChild()

Отключение всех анимаций

Чтобы отключить все анимации для приложения Angular, поместите host binding @.disabled на самый верхний компонент Angular.

app.ts

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrls: ['app.css'],
  imports: [RouterLink, RouterOutlet],
  animations: [
    slideInAnimation,
  ],
})
export class AppComponent {
  @HostBinding('@.disabled')
  public animationsDisabled = false;
}

ПОЛЕЗНО: Отключение анимаций на уровне всего приложения полезно во время end-to-end (E2E) тестирования.

Callbacks анимаций

Функция анимации trigger() испускает callbacks, когда начинается и когда заканчивается. Следующий пример демонстрирует компонент, содержащий trigger openClose.

open-close.ts

@Component({
  selector: 'app-open-close',
  animations: [
    trigger('openClose', [
      // ...
    ]),
  ],
  templateUrl: 'open-close.html',
  styleUrls: ['open-close.css'],
})
export class OpenClose {
  onAnimationEvent(event: AnimationEvent) {
  }
}

В HTML-шаблоне событие анимации передаётся обратно через $event как @triggerName.start и @triggerName.done, где triggerName — имя используемого trigger. В этом примере trigger openClose выглядит так.

open-close.html

<div
  [@openClose]="isOpen ? 'open' : 'closed'"
  (@openClose.start)="onAnimationEvent($event)"
  (@openClose.done)="onAnimationEvent($event)"
  class="open-close-container"
>
</div>

Потенциальное использование animation callbacks — прикрыть медленный API-вызов, например поиск в базе данных. Например, кнопку InProgress можно настроить так, чтобы у неё была собственная looping-анимация, пока операция backend-системы завершается.

Другую анимацию можно вызвать, когда текущая анимация завершится. Например, кнопка переходит из состояния inProgress в состояние closed, когда API-вызов завершён.

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

Callbacks могут служить инструментом отладки — например, в сочетании с console.warn() для просмотра прогресса приложения в Developer JavaScript Console браузера. Следующий фрагмент кода создаёт вывод console log для исходного примера — кнопки с двумя состояниями open и closed.

open-close.ts

export class OpenClose {
  onAnimationEvent(event: AnimationEvent) {
    // openClose is trigger name in this example
    console.warn(`Animation Trigger: ${event.triggerName}`);

    // phaseName is "start" or "done"
    console.warn(`Phase: ${event.phaseName}`);

    // in our example, totalTime is 1000 (number of milliseconds in a second)
    console.warn(`Total time: ${event.totalTime}`);

    // in our example, fromState is either "open" or "closed"
    console.warn(`From: ${event.fromState}`);

    // in our example, toState either "open" or "closed"
    console.warn(`To: ${event.toState}`);

    // the HTML element itself, the button in this case
    console.warn(`Element: ${event.element}`);
  }
}

Keyframes

Чтобы создать анимацию с несколькими шагами, выполняемыми последовательно, используйте keyframes.

Функция Angular keyframe() позволяет несколько изменений стилей в одном сегменте timing. Например, кнопка вместо затухания могла бы несколько раз менять цвет в течение одного 2-секундного промежутка времени.

keyframes

Код для этого изменения цвета может выглядеть так.

status-slider.ts

transition('* => active', [
        animate(
          '2s',
          keyframes([
            style({backgroundColor: 'blue'}),
            style({backgroundColor: 'red'}),
            style({backgroundColor: 'orange'}),
          ]),
        ),

Offset

Keyframes включают offset, который определяет точку в анимации, где происходит каждое изменение стиля. Offsets — относительные меры от нуля до единицы, отмечающие начало и конец анимации. Их следует применять к каждому шагу keyframe, если они используются хотя бы один раз.

Определение offsets для keyframes опционально. Если их опустить, автоматически назначаются равномерно распределённые offsets. Например, три keyframes без предопределённых offsets получают offsets 0, 0.5 и 1. Указание offset 0.8 для среднего перехода в предыдущем примере может выглядеть так.

keyframes with offset

Код с указанными offsets был бы следующим.

status-slider.ts

transition('* => active', [
        animate(
          '2s',
          keyframes([
            style({backgroundColor: 'blue', offset: 0}),
            style({backgroundColor: 'red', offset: 0.8}),
            style({backgroundColor: '#754600', offset: 1.0}),
          ]),
        ),
      ]),
      transition('* => inactive', [
        animate(
          '2s',
          keyframes([
            style({backgroundColor: '#754600', offset: 0}),
            style({backgroundColor: 'red', offset: 0.2}),
            style({backgroundColor: 'blue', offset: 1.0}),
          ]),
        ),
      ]),

Можно комбинировать keyframes с duration, delay и easing в одной анимации.

Keyframes с пульсацией

Используйте keyframes для создания эффекта пульсации в анимациях, определяя стили на конкретных offsets на протяжении анимации.

Вот пример использования keyframes для создания эффекта пульсации:

  • Исходные состояния open и closed с исходными изменениями высоты, цвета и opacity, происходящими в течение 1 секунды
  • Последовательность keyframes, вставленная в середину, которая заставляет кнопку казаться пульсирующей нерегулярно в течение того же 1-секундного промежутка времени
keyframes with irregular pulsation

Фрагмент кода для этой анимации может выглядеть так.

open-close.ts

trigger('openClose', [
      state(
        'open',
        style({
          height: '200px',
          opacity: 1,
          backgroundColor: 'yellow',
        }),
      ),
      state(
        'close',
        style({
          height: '100px',
          opacity: 0.5,
          backgroundColor: 'green',
        }),
      ),
      // ...
      transition('* => *', [
        animate(
          '1s',
          keyframes([
            style({opacity: 0.1, offset: 0.1}),
            style({opacity: 0.6, offset: 0.2}),
            style({opacity: 1, offset: 0.5}),
            style({opacity: 0.2, offset: 0.7}),
          ]),
        ),
      ]),
    ]),

Animatable-свойства и единицы

Анимации Angular построены поверх web animations, поэтому можно анимировать любое свойство, которое браузер считает animatable. Это включает позиции, размеры, transforms, цвета, borders и многое другое. W3C поддерживает список animatable-свойств на странице CSS Transitions.

Для свойств с числовым значением определите единицу, предоставив значение как строку в кавычках с соответствующим суффиксом:

  • 50 пикселей: '50px'

  • Относительный размер шрифта: '3em'

  • Процент: '100%'

Также можно предоставить значение как число. В таких случаях Angular предполагает единицу по умолчанию — пиксели, или px. Выражение 50 пикселей как 50 то же самое, что сказать '50px'.

ПОЛЕЗНО: Строка "50" вместо этого не считалась бы валидной).

Автоматический расчёт свойств с wildcards

Иногда значение dimensional style property неизвестно до runtime. Например, у элементов часто есть ширины и высоты, зависящие от их контента или размера экрана. Эти свойства часто сложно анимировать с помощью CSS.

В этих случаях можно использовать специальное значение свойства wildcard * в style(). Значение этого конкретного style property вычисляется в runtime и затем подставляется в анимацию.

Следующий пример имеет trigger под названием shrinkOut, используемый, когда HTML-элемент покидает страницу. Анимация берёт ту высоту, которая есть у элемента до ухода, и анимирует от этой высоты до нуля.

hero-list-auto.ts

animations: [
    trigger('shrinkOut', [
      state('in', style({height: '*'})),
      transition('* => void', [style({height: '*'}), animate(250, style({height: 0}))]),
    ]),
  ],

Сводка по keyframes

Функция keyframes() в Angular позволяет указать несколько промежуточных стилей в одном переходе. Опциональный offset можно использовать для определения точки в анимации, где должно происходить каждое изменение стиля.

Ещё об анимациях Angular

Вас также могут заинтересовать: