Анимация создает иллюзию движения: элементы HTML со временем меняют стиль. Хорошо продуманная анимация может сделать ваше приложение более увлекательным и простым в использовании, но это не просто косметический эффект. Анимация может улучшить ваше приложение и взаимодействие с пользователем несколькими способами: Без анимации переходы веб-страниц могут казаться резкими и неприятными. Движение значительно улучшает взаимодействие с пользователем, поэтому анимация дает пользователям возможность определить реакцию приложения на их действия. Хорошие анимации интуитивно привлекают внимание пользователя туда, где это необходимо. Как правило, анимация включает несколько преобразований стилей с течением времени. HTML-элемент может перемещаться, изменять цвет, увеличиваться или уменьшаться, исчезать или соскальзывать со страницы. Эти изменения могут происходить одновременно или последовательно. Вы можете контролировать время каждого преобразования. Система анимации Angular построена на функциональности CSS, что означает, что вы можете анимировать любое свойство, которое браузер считает анимируемым. Это включает в себя позиции, размеры, преобразования, цвета, границы и многое другое. W3C поддерживает список анимируемых свойств на своей странице CSS Transitions.

Начиная

Основными модулями Angular для анимации являются @angular/animations и @angular/platform-browser. Когда вы создаете новый проект с помощью CLI, эти зависимости автоматически добавляются в ваш проект. Чтобы начать добавлять анимацию Angular в свой проект, импортируйте модули, относящиеся к анимации, вместе со стандартными функциями Angular.

Шаг 1. Включение модуля анимации

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({
  imports: [
    BrowserModule,
    BrowserAnimationsModule
  ],
  declarations: [ ],
  bootstrap: [ ]
})
export class AppModule { }

Когда вы используете CLI для создания своего приложения, корневой модуль приложения app.module.ts помещается в папку src/app.

Шаг 2. Импорт функций анимации в файлы компонентов

Если вы планируете использовать определенные функции анимации в файлах компонентов, импортируйте эти функции из @angular/animations.

import { Component, HostBinding } from '@angular/core';
import {
  trigger,
  state,
  style,
  animate,
  transition,
  // ...
} from '@angular/animations';

Шаг 3. Добавление свойства метаданных анимации

В файле компонента добавьте свойство метаданных под названием анимация: в декораторе @Component(). Вы помещаете триггер, определяющий анимацию, в свойство метаданных анимации.

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.css'],
  animations: [
    // animation triggers go here
  ]
})

Анимация перехода

Давайте анимируем переход, который изменяет один элемент HTML из одного состояния в другое. Например, вы можете указать, чтобы кнопка отображала либо «Открыто», либо «Закрыто» в зависимости от последнего действия пользователя. Когда кнопка находится в открытом состоянии, она видна и имеет желтый цвет. В закрытом состоянии он полупрозрачный и синий. В HTML эти атрибуты задаются с помощью обычных стилей CSS, таких как цвет и непрозрачность. В Angular используйте функцию style(), чтобы указать набор стилей CSS для использования с анимацией. Соберите набор стилей в состоянии анимации и присвойте состоянию имя, например открытое или закрытое. Давайте создадим новый компонент open-close для анимации с помощью простых переходов. Запустите следующую команду в терминале, чтобы сгенерировать компонент:

ng g component open-close

Это создаст компонент в src/app/open-close.component.ts.

Состояние и стили анимации

Используйте функцию Angular state(), чтобы определить различные состояния для вызова в конце каждого перехода. Эта функция принимает два аргумента: уникальное имя, например открытое или закрытое, и функцию style(). Используйте функцию style(), чтобы определить набор стилей, которые будут связаны с данным именем состояния. Вы должны использовать camelCase для атрибутов стиля, которые содержат дефисы, такие как backgroundColor, или заключать их в кавычки, такие как «фоновый цвет». Давайте посмотрим, как функция Angular state() работает с функцией style⁣(⁠) для установки атрибутов стиля CSS. В этом фрагменте кода для состояния одновременно задаются несколько атрибутов стиля. В открытом состоянии кнопка имеет высоту 200 пикселей, непрозрачность 1 и желтый цвет фона.

// ...
state('open', style({
  height: '200px',
  opacity: 1,
  backgroundColor: 'yellow'
})),

В следующем закрытом состоянии кнопка имеет высоту 100 пикселей, непрозрачность 0,8 и синий цвет фона.

state('closed', style({
  height: '100px',
  opacity: 0.8,
  backgroundColor: 'blue'
})),

Переходы и время

В Angular вы можете установить несколько стилей без анимации. Однако без дальнейшей доработки кнопка мгновенно трансформируется без выцветания, сжатия или других видимых признаков того, что происходит изменение. Чтобы сделать изменение менее резким, необходимо определить анимацию перехода, чтобы указать изменения, которые происходят между одним состоянием и другим в течение определенного периода времени. Функция transition() принимает два аргумента: первый аргумент принимает выражение, определяющее направление между двумя переходными состояниями, а второй аргумент принимает один или несколько шагов animate(). Используйте функцию animate(), чтобы определить длину, задержку и плавность перехода, а также назначить функцию стиля для определения стилей во время перехода. Используйте функцию animate(), чтобы определить функцию keyframes() для многошаговой анимации. Эти определения помещаются во второй аргумент функции animate().

Метаданные анимации: продолжительность, задержка и замедление

Функция animate() (второй аргумент функции перехода) принимает входные параметры таймингов и стилей. Параметр таймингов принимает либо число, либо строку, состоящую из трех частей.

animate (duration)

or

animate ('duration delay easing')

Первая часть, продолжительность, обязательна. Продолжительность может быть выражена в миллисекундах в виде числа без кавычек или в секундах с кавычками и спецификатором времени. Например, продолжительность одной десятой секунды может быть выражена следующим образом: В виде простого числа в миллисекундах: 100. В виде строки в миллисекундах: «100 мс». В строке в секундах: «0,1 с». Второй аргумент, задержка, имеет тот же синтаксис, что и продолжительность. Например: подождите 100 мс, а затем запустите 200 мс: «0,2 с 100 мс». Третий аргумент, easing, управляет тем, как анимация ускоряется и замедляется во время выполнения. Например, при ускорении анимация начинается медленно, а скорость увеличивается по мере ее выполнения. Подождите 100 мс, запустите на 200 мс. Используйте кривую замедления для быстрого старта и медленного замедления до точки покоя: «0,2 с 100 мс замедление». Запустите на 200 мс, без задержки. Используйте стандартную кривую, чтобы начать медленно, ускориться в середине, а затем медленно замедлиться в конце: «0,2 с плавного входа-выхода». Начните немедленно, запустите на 200 мс. Используйте кривую ускорения, чтобы начать медленно и закончить на полной скорости: «0,2 с замедления». Этот пример обеспечивает переход состояния из открытого в закрытое с 1-секундным переходом между состояниями.

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

В предыдущем фрагменте кода оператор =› указывает на однонаправленные переходы, а ‹=› — на двунаправленный. Внутри перехода animate() указывает, сколько времени занимает переход. В этом случае изменение состояния с открытого на закрытое занимает 1 секунду, выраженную здесь как 1 с. В этом примере добавляется переход состояния из закрытого состояния в открытое состояние с 0,5-секундной дугой анимации перехода.

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

Некоторые дополнительные примечания по использованию стилей в функциях состояния и перехода. Используйте state() для определения стилей, которые применяются в конце каждого перехода, они сохраняются после завершения анимации. Используйте transition() для определения промежуточных стилей, которые создают иллюзию движения во время анимации. Когда анимация отключена, стили transition() можно пропустить, а стили state() — нет. Включите несколько пар состояний в один и тот же аргумент transition():

transition( 'on => off, off => void' )

Запуск анимации

Для анимации требуется триггер, чтобы она знала, когда начинать. Функция trigger() собирает состояния и переходы и присваивает анимации имя, чтобы вы могли прикрепить ее к инициирующему элементу в шаблоне HTML. Функция trigger() описывает имя свойства для отслеживания изменений. Когда происходит изменение, триггер инициирует действия, включенные в его определение. Этими действиями могут быть переходы или другие функции, как мы увидим позже. В этом примере мы назовем триггер openClose и прикрепим его к элементу button. Триггер описывает открытое и закрытое состояния, а также время для двух переходов. В каждом вызове функции trigger() элемент может находиться только в одном состоянии в любой момент времени. Однако одновременно могут быть активны несколько триггеров.

Определение анимации и ее присоединение к шаблону HTML

Анимации определяются в метаданных компонента, который управляет анимируемым элементом HTML. Поместите код, определяющий вашу анимацию, в свойство animations: в декораторе @Component().

@Component({
  selector: 'app-open-close',
  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')
      ]),
    ]),
  ],
  templateUrl: 'open-close.component.html',
  styleUrls: ['open-close.component.css']
})
export class OpenCloseComponent {
  isOpen = true;

  toggle() {
    this.isOpen = !this.isOpen;
  }

}

Когда вы определили триггер анимации для компонента, прикрепите его к элементу в шаблоне этого компонента, заключив имя триггера в квадратные скобки и поставив перед ним символ @. Затем вы можете привязать триггер к выражению шаблона, используя стандартный синтаксис привязки свойств Angular, как показано ниже, где triggerName — это имя триггера, а выражение оценивается как определенное состояние анимации.

<div [@triggerName]="expression">…</div>;

Анимация выполняется или запускается, когда значение выражения переходит в новое состояние. Следующий фрагмент кода связывает триггер со значением свойства isOpen.

<nav>
  <button type="button" (click)="toggle()">Toggle Open/Close</button>
</nav>

<div [@openClose]="isOpen ? 'open' : 'closed'" class="open-close-container">
  <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
</div>

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

Анимационные переходы и триггеры

В этом руководстве подробно рассматриваются специальные переходные состояния, такие как подстановочный знак * и пустота. Он показывает, как эти специальные состояния используются для элементов, входящих в представление и покидающих его.

Предопределенные состояния и сопоставление с подстановочными знаками

В Angular переходные состояния могут быть определены явно с помощью функции state() или с использованием предопределенных подстановочных знаков * и void состояний.

Подстановочное состояние

Звездочка * или подстановочный знак соответствует любому состоянию анимации. Это полезно для определения переходов, которые применяются независимо от начального или конечного состояния элемента HTML. Например, переход open => * применяется, когда состояние элемента изменяется с открытого на любое другое. Ниже приведен еще один пример кода, использующий состояние подстановочного знака вместе с предыдущим примером, использующим состояния open и closed. Вместо определения каждой пары переходов между состояниями любой переход к closed занимает 1 секунду, а любой переход к open занимает 0,5 секунды.

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')
    ]),
  ]),
],

Используйте синтаксис двойной стрелки, чтобы указать переходы между состояниями в обоих направлениях.

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

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

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

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, а затем используйте * => * в качестве запасного варианта для пар состояний, которые иначе не вызываются. Для этого перечислите более конкретные переходы до * => *.

Используйте подстановочные знаки со стилями

Используйте подстановочный знак * со стилем, чтобы указать анимации использовать любое значение текущего стиля, и анимируйте его. Подстановочный знак — это резервное значение, которое используется, если анимируемое состояние не объявлено в триггере.

Недействительное состояние

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

Объединить подстановочные знаки и недействительные состояния

Объедините состояния подстановки и пустоты в переходе, чтобы активировать анимацию входа и выхода со страницы: переход * => void применяется, когда элемент покидает представление, независимо от того, в каком состоянии он был до выхода. Переход void => * применяется, когда элемент входит в представление, независимо от того, какое состояние он принимает при входе. Состояние подстановочного знака * соответствует любому состоянию, включая void.

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

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

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 не присоединен к представлению.

Псевдонимы :enter и :leave

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

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

Труднее настроить таргетинг на элемент, который входит в представление, потому что он еще не находится в DOM. Используйте псевдонимы :enter и :leave для нацеливания на элементы HTML, которые вставляются или удаляются из представления.

Используйте *ngIf и *ngFor с :enter и :leave

Переход :enter запускается, когда на страницу помещаются любые представления *ngIf или *ngFor, а :leave запускается, когда эти представления удаляются со страницы. Поведение при входе/выходе иногда может сбивать с толку. Как правило, считается, что любой элемент, добавляемый в DOM с помощью Angular, проходит через переход :enter. Только элементы, которые Angular напрямую удаляет из DOM, проходят через переход :leave. Например, представление элемента удаляется из DOM, потому что его родитель удаляется из DOM. В этом примере есть специальный триггер для анимации входа и выхода под названием myInsertRemoveTrigger. Шаблон HTML содержит следующий код.

<div @myInsertRemoveTrigger *ngIf="isShown" class="insert-remove-container">
  <p>The box is inserted</p>
</div>

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

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

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

Переход: увеличение и уменьшение

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

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 })),
      ]),
    ])
  ]),
]),

Логические значения в переходах

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

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

В приведенном выше фрагменте кода шаблон HTML привязывает элемент ‹div› к триггеру с именем openClose с выражением состояния isOpen и возможными значениями true и false. Этот шаблон является альтернативой практике создания двух именованных состояний, таких как открытое и закрытое. Внутри метаданных @Component под свойством animations:, когда состояние оценивается как true, высота связанного HTML-элемента является стилем подстановочного знака или значением по умолчанию. В этом случае анимация использует ту высоту, которую элемент уже имел до начала анимации. Когда элемент закрыт, элемент анимируется до высоты 0, что делает его невидимым.

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

Несколько триггеров анимации

Вы можете определить более одного триггера анимации для компонента. Прикрепляйте триггеры анимации к различным элементам, а отношения родитель-потомок между элементами влияют на то, как и когда запускается анимация. Каждый раз, когда анимация запускается в Angular, родительская анимация всегда получает приоритет, а дочерние анимации блокируются. Для запуска дочерней анимации родительская анимация должна запрашивать каждый из элементов, содержащих дочерние анимации. Затем он позволяет запускать анимацию с помощью функции animateChild(). Специальную привязку управления анимацией, называемую @.disabled, можно поместить в элемент HTML, чтобы отключить анимацию для этого элемента, а также для любых вложенных элементов. При значении true привязка @.disabled предотвращает рендеринг всех анимаций. Когда для элемента в шаблоне HTML отключена анимация с помощью привязки хоста @.disabled, анимация также отключается для всех внутренних элементов. Вы не можете выборочно отключить несколько анимаций для одного элемента. Выбранные дочерние анимации все еще могут быть запущены для отключенного родителя одним из следующих способов: Родительская анимация может использовать функцию query() для сбора внутренних элементов, расположенных в отключенных областях шаблона HTML. Эти элементы все еще могут анимироваться. Родитель может запросить дочернюю анимацию, а затем анимировать ее с помощью функции animateChild(). Чтобы отключить все анимации для приложения Angular, поместите привязку хоста @.disabled к самому верхнему компоненту Angular.

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

Обратные вызовы анимации

Функция триггера анимации генерирует обратные вызовы при запуске и завершении. В следующем примере показан компонент, содержащий триггер openClose.

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

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

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

Потенциальное использование обратных вызовов анимации может состоять в том, чтобы прикрыть медленный вызов API, например поиск в базе данных. Например, для кнопки InProgress можно настроить собственную циклическую анимацию, пока завершается работа серверной системы. Другая анимация может быть вызвана, когда закончится текущая анимация. Например, кнопка переходит из состояния inProgress в закрытое состояние после завершения вызова API. Анимация может заставить конечного пользователя воспринимать операцию как более быструю, даже если это не так. Обратные вызовы могут служить инструментом отладки, например, в сочетании с console.warn() для просмотра хода выполнения приложения в консоли разработчика JavaScript браузера. Следующий фрагмент кода создает вывод журнала консоли для исходного примера, кнопки с двумя состояниями: открыто и закрыто.

export class OpenCloseComponent {
  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}`);
  }
}

Ключевые кадры

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

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

Компенсировать

Ключевые кадры включают смещение, определяющее точку анимации, в которой происходит каждое изменение стиля. Смещения — это относительные меры от нуля до единицы, отмечающие начало и конец анимации. Их следует применять к каждому из шагов ключевого кадра, если они использовались хотя бы один раз. Определение смещения для ключевых кадров не является обязательным. Если их не указать, автоматически назначаются равномерные смещения. Например, три ключевых кадра без предопределенных смещений получают смещения 0, 0,5 и 1. Указание смещения 0,8 для среднего перехода в предыдущем примере может выглядеть следующим образом. Код с указанными смещениями будет следующим.

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})
  ]))
]),

Вы можете комбинировать ключевые кадры с длительностью, задержкой и замедлением в рамках одной анимации.

Ключевые кадры с пульсацией

Используйте ключевые кадры для создания эффекта пульсации в анимации, определяя стили с определенным смещением по всей анимации. Вот пример использования ключевых кадров для создания эффекта пульсации: исходные открытые и закрытые состояния с исходными изменениями высоты, цвета и непрозрачности, происходящими в течение 1 секунды. Последовательность ключевых кадров, вставленная посередине, из-за которой кнопка кажется неравномерно пульсирующей в течение того же 1-секундного периода времени. Фрагмент кода для этой анимации может выглядеть так.

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 })
    ]))
  ])
])

Анимируемые свойства и единицы

Анимации Angular поддерживают сборки поверх веб-анимаций, поэтому вы можете анимировать любое свойство, которое браузер считает анимируемым. Это включает в себя позиции, размеры, преобразования, цвета, границы и многое другое. W3C поддерживает список анимируемых свойств на своей странице переходов CSS. Для свойств с числовым значением определите единицу измерения, предоставив значение в виде строки в кавычках с соответствующим суффиксом: 50 пикселей: «50px». Относительный размер шрифта: «3em». Процент: «100%». Вы также можете указать значение в виде числа. В таких случаях Angular принимает единицу измерения по умолчанию в пикселях или px. Выражение 50 пикселей как 50 — это то же самое, что сказать «50px». Вместо этого строка «50» не будет считаться действительной).

Автоматический расчет свойств с подстановочными знаками

Иногда значение свойства размерного стиля неизвестно до времени выполнения. Например, ширина и высота элементов часто зависят от их содержимого или размера экрана. Эти свойства часто сложно анимировать с помощью CSS. В этих случаях вы можете использовать специальное значение свойства подстановочного знака * в style(). Значение этого конкретного свойства стиля вычисляется во время выполнения, а затем подключается к анимации. В следующем примере есть триггер с именем «shrinkOut», который используется, когда элемент HTML покидает страницу. Анимация принимает любую высоту, которую имеет элемент перед тем, как покинуть его, и анимирует от этой высоты до нуля.

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

Сложные анимационные последовательности

До сих пор мы изучили простую анимацию отдельных элементов HTML. Angular также позволяет анимировать скоординированные последовательности, такие как вся сетка или список элементов, когда они входят и покидают страницу. Вы можете запускать несколько анимаций параллельно или запускать отдельные анимации последовательно, одна за другой. Функции, которые управляют сложными анимационными последовательностями:

Функция запроса()

Большинство сложных анимаций используют функцию query() для поиска дочерних элементов и применения к ним анимации, основными примерами которой являются: query(), за которой следует animate(): используется для запроса простых HTML-элементов и непосредственного применения к ним анимации. query(), за которой следует animateChild(): используется для запроса дочерних элементов, к которым сами применяются метаданные анимации, и запускает такую ​​анимацию (которая в противном случае была бы заблокирована анимацией текущего/родительского элемента). Первый аргумент query() — это строка селектора css, которая также может содержать следующие специфичные для Angular токены:

TOKENS=› ПОДРОБНОСТИ
:enter, :leave=› Для входа/выхода из элементов.
:animating=› Для элементов, которые в данный момент анимируются.
@*, @triggerName=› Для элементов с любой — или определенный — триггер.
:self Сам анимируемый элемент.

ВХОД И ВЫХОД ИЗ ЭЛЕМЕНТОВ

Не все дочерние элементы на самом деле считаются входящими/исходящими; иногда это может быть нелогичным и сбивающим с толку.

Анимируйте несколько элементов с помощью функций query() и stagger().

После запроса дочерних элементов с помощью query() функция stagger() позволяет определить временной интервал между каждым анимируемым запрошенным элементом и, таким образом, анимирует элементы с задержкой между ними. В следующем примере показано, как использовать функции query() и stagger() для анимации списка (героев), добавляя их последовательно, с небольшой задержкой, сверху вниз. Используйте query() для поиска элемента, входящего на страницу, который соответствует определенным критериям. Для каждого из этих элементов используйте style(), чтобы установить один и тот же начальный стиль для элемента. Сделайте его прозрачным и используйте преобразование, чтобы сдвинуть его с места, чтобы он мог встать на место. Используйте stagger() для задержки каждой анимации на 30 миллисекунд. Анимируйте каждый элемент на экране в течение 0,5 секунды, используя пользовательскую кривую замедления, одновременно добавляя его и не трансформируя.

animations: [
  trigger('pageAnimations', [
    transition(':enter', [
      query('.hero', [
        style({opacity: 0, transform: 'translateY(-100px)'}),
        stagger(30, [
          animate('500ms cubic-bezier(0.35, 0, 0.25, 1)',
          style({ opacity: 1, transform: 'none' }))
        ])
      ])
    ])
  ]),

Параллельная анимация с использованием функции group()

Вы видели, как добавить задержку между каждой последующей анимацией. Но вы также можете настроить анимацию, которая происходит параллельно. Например, вы можете захотеть анимировать два свойства CSS одного и того же элемента, но использовать разные функции плавности для каждого из них. Для этого вы можете использовать функцию анимации group(). Функция group() используется для группировки шагов анимации, а не анимированных элементов. В следующем примере group() используются как для :enter, так и для :leave для двух разных конфигураций синхронизации, таким образом применяя две независимые анимации к одному и тому же элементу параллельно.

animations: [
  trigger('flyInOut', [
    state('in', style({
      width: '*',
      transform: 'translateX(0)', opacity: 1
    })),
    transition(':enter', [
      style({ width: 10, transform: 'translateX(50px)', opacity: 0 }),
      group([
        animate('0.3s 0.1s ease', style({
          transform: 'translateX(0)',
          width: '*'
        })),
        animate('0.3s ease', style({
          opacity: 1
        }))
      ])
    ]),
    transition(':leave', [
      group([
        animate('0.3s ease', style({
          transform: 'translateX(50px)',
          width: 10
        })),
        animate('0.3s 0.2s ease', style({
          opacity: 0
        }))
      ])
    ])
  ])
]

Последовательная и параллельная анимация

Сложные анимации могут иметь много вещей, происходящих одновременно. Но что, если вы хотите создать анимацию, состоящую из нескольких анимаций, происходящих одна за другой? Ранее вы использовали group() для запуска нескольких анимаций одновременно, параллельно. Вторая функция с именем sequence() позволяет запускать одни и те же анимации одну за другой. В sequence() этапы анимации состоят из вызовов функций style() или animate(). Используйте style() для немедленного применения предоставленных данных стиля. Используйте animate() для применения данных стиля за заданный интервал времени.

Пример анимации фильтра

Взгляните на другую анимацию на странице живого примера. На вкладке «Фильтр/Шаг» введите текст в текстовое поле «Поиск героев», например «Магнит» или «Торнадо». Фильтр работает в режиме реального времени, когда вы печатаете. Элементы покидают страницу по мере того, как вы вводите каждую новую букву, и фильтр становится все более строгим. Список героев постепенно возвращается на страницу по мере удаления каждой буквы в поле фильтра. Шаблон HTML содержит триггер под названием filterAnimation.

<label for="search">Search heroes: </label>
<input type="text" id="search" #criteria
       (input)="updateCriteria(criteria.value)"
       placeholder="Search heroes">

<ul class="heroes" [@filterAnimation]="heroesTotal">
  <li *ngFor="let hero of heroes" class="hero">
    <div class="inner">
      <span class="badge">{{ hero.id }}</span>
      <span class="name">{{ hero.name }}</span>
    </div>
  </li>
</ul>

filterAnimation в декораторе компонента содержит три перехода.

@Component({
  animations: [
    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 })),
          ]),
        ])
      ]),
    ]),
  ]
})
export class HeroListPageComponent implements OnInit {
  heroesTotal = -1;

  get heroes() { return this._heroes; }
  private _heroes: Hero[] = [];

  ngOnInit() {
    this._heroes = HEROES;
  }

  updateCriteria(criteria: string) {
    criteria = criteria ? criteria.trim() : '';

    this._heroes = HEROES.filter(hero => hero.name.toLowerCase().includes(criteria.toLowerCase()));
    const newTotal = this.heroes.length;

    if (this.heroesTotal !== newTotal) {
      this.heroesTotal = newTotal;
    } else if (!criteria) {
      this.heroesTotal = -1;
    }
  }
}

Код в этом примере выполняет следующие задачи: Пропускает анимацию, когда пользователь впервые открывает или переходит на эту страницу (анимация фильтра сужает то, что уже есть, поэтому она работает только с элементами, которые уже существуют в DOM). Фильтрует героев на основе значения ввода поиска. Для каждого изменения: скрывает элемент, выходящий из DOM, устанавливая его непрозрачность и ширину на 0. Анимирует элемент, входящий в DOM, в течение 300 миллисекунд. Во время анимации элемент принимает ширину и прозрачность по умолчанию. Если есть несколько элементов, входящих или выходящих из DOM, каждая анимация начинается в верхней части страницы с задержкой 50 миллисекунд между каждым элементом.

Анимация элементов списка переупорядочения

Хотя Angular корректно анимирует элементы списка *ngFor из коробки, он не сможет этого сделать, если их порядок изменится. Это связано с тем, что он потеряет представление о том, какой элемент является каким, что приведет к нарушению анимации. Единственный способ помочь Angular отслеживать такие элементы — назначить TrackByFunction директиве NgForOf. Это гарантирует, что Angular всегда знает, какой элемент есть какой, что позволяет ему постоянно применять правильные анимации к правильным элементам.