Улучшить эту документациюАнимации
AngularJS предоставляет обработчики анимации для таких общих директив, как ngRepeat, ngSwitch и ngView, а также для настраиваемых директив через $animate службу. Эти обработчики анимации устанавливаются для запуска анимации в течение жизненного цикла различных директив и, при запуске, будут пытаться выполнить анимацию CSS Transition, CSS Keyframe Animation или JavaScript callback Animation (в зависимости от того, назначена ли анимация данной директиве). Анимации могут быть размещены с помощью обычного CSS, следуя соглашениям об именовании, установленным AngularJS, или с помощью JavaScript-кода, определённого как фабрика.
Анимации недоступны, если вы не включите ngAnimate модуль в качестве зависимости вашего приложения.
Ниже приведен быстрый пример включения анимаций для ngShow и ngHide.
Установка
См. документацию API для ngAnimate для инструкций по установке модуля.
Вы также можете настроить отдельный CSS-файл для определения CSS-анимаций.
Как это работает
Анимации в AngularJS полностью основаны на CSS-классах. Если у вас есть CSS-класс, присоединённый к элементу HTML в вашем приложении, вы можете применить к нему анимацию. Предположим, например, что у нас есть шаблон HTML с повторением такого вида:
<div ng-repeat="item in items" class="repeated-item">
{{ item.id }}
</div>
Как видите, класс repeated-item присутствует на элементе, который будет повторяться, и этот класс будет использоваться в CSS и/или JavaScript-коде анимации нашего приложения в качестве ссылки, чтобы сообщить AngularJS о выполнении анимации.
Когда ngRepeat выполняет свою работу, каждый раз, когда новый элемент добавляется в список, ngRepeat добавляет класс ng-enter к добавляемому элементу. При удалении он применяет класс ng-leave, а при перемещении — класс ng-move.
Рассмотрев следующий CSS-код, мы можем увидеть код анимации перехода и ключевых кадров, настроенный для каждого из этих событий, которые происходят, когда ngRepeat запускает их:
/*
We are using CSS transitions for when the enter and move events
are triggered for the element that has the `repeated-item` class
*/
.repeated-item.ng-enter, .repeated-item.ng-move {
transition: all 0.5s linear;
opacity: 0;
}
/*
`.ng-enter-active` and `.ng-move-active` are where the transition destination
properties are set so that the animation knows what to animate
*/
.repeated-item.ng-enter.ng-enter-active,
.repeated-item.ng-move.ng-move-active {
opacity: 1;
}
/*
We are using CSS keyframe animations for when the `leave` event
is triggered for the element that has the `repeated-item` class
*/
.repeated-item.ng-leave {
animation: 0.5s my_animation;
}
@keyframes my_animation {
from { opacity: 1; }
to { opacity: 0; }
}
Тот же подход к анимации может быть использован с помощью JavaScript-кода (для простоты мы здесь опираемся на jQuery для выполнения анимаций):
myModule.animation('.repeated-item', function() {
return {
enter: function(element, done) {
// Initialize the element's opacity
element.css('opacity', 0);
// Animate the element's opacity
// (`element.animate()` is provided by jQuery)
element.animate({opacity: 1}, done);
// Optional `onDone`/`onCancel` callback function
// to handle any post-animation cleanup operations
return function(isCancelled) {
if (isCancelled) {
// Abort the animation if cancelled
// (`element.stop()` is provided by jQuery)
element.stop();
}
};
},
leave: function(element, done) {
// Initialize the element's opacity
element.css('opacity', 1);
// Animate the element's opacity
// (`element.animate()` is provided by jQuery)
element.animate({opacity: 0}, done);
// Optional `onDone`/`onCancel` callback function
// to handle any post-animation cleanup operations
return function(isCancelled) {
if (isCancelled) {
// Abort the animation if cancelled
// (`element.stop()` is provided by jQuery)
element.stop();
}
};
},
// We can also capture the following animation events:
move: function(element, done) {},
addClass: function(element, className, done) {},
removeClass: function(element, className, done) {}
}
});
При наличии этих сгенерированных имен CSS-классов на элементе в данный момент AngularJS автоматически определяет, следует ли выполнять анимацию CSS и/или JavaScript. Обратите внимание, что вы не можете иметь как CSS-, так и JavaScript-анимации, основанные на одном и том же CSS-классе. См. здесь для получения более подробной информации.
Обработчики анимации Class и ngClass
AngularJS также обращает внимание на изменения CSS-классов на элементах, вызывая обработчики add и remove. Это означает, что если к элементу добавляется или удаляется CSS-класс, то в промежутке может быть выполнена анимация, до окончательного добавления или удаления CSS-класса. (Помните, что AngularJS сможет захватить изменения класса только в том случае, если на элементе используется интерполированное выражение или директива ng-class.)
Пример ниже показывает, как выполнять анимации во время изменений класса:
Хотя CSS немного отличается от того, что мы видели раньше, идея та же.
Какие директивы поддерживают анимации?
Несколько общих директив AngularJS поддерживают и запускают обработчики анимаций всякий раз, когда происходит какое-либо существенное событие в течение их жизненного цикла. В таблице ниже подробно объясняется, какие события анимации запускаются:
| Директива | Поддерживаемые анимации |
|---|---|
| ngRepeat | вход, выход и перемещение |
| ngIf | вход и выход |
| ngSwitch | вход и выход |
| ngInclude | вход и выход |
| ngView | вход и выход |
| ngMessage / ngMessageExp | вход и выход |
| ngClass / {{class}} | добавление и удаление |
| ngClassEven / ngClassOdd | добавление и удаление |
| ngHide | добавление и удаление (класс ng-hide) |
| ngShow | добавление и удаление (класс ng-hide) |
| ngModel | добавление и удаление (различные классы) |
| form / ngForm | добавление и удаление (различные классы) |
| ngMessages | добавление и удаление (классы ng-active/ng-inactive) |
Для полного разбора шагов, вовлечённых в каждое событие анимации, см. документацию API.
Как использовать анимации в собственных директивах?
Анимации в пользовательских директивах также могут быть установлены путём прямого ввода $animate в вашу директиву и вызова её при необходимости.
myModule.directive('my-directive', ['$animate', function($animate) {
return function(scope, element) {
element.on('click', function() {
if (element.hasClass('clicked')) {
$animate.removeClass(element, 'clicked');
} else {
$animate.addClass(element, 'clicked');
}
});
};
}]);
Анимации при загрузке приложения/страницы
По умолчанию анимации отключены, когда приложение AngularJS инициализируется. Если вы используете директиву ngApp, это происходит в событии DOMContentLoaded, то есть сразу после загрузки страницы. Анимации отключены, чтобы интерфейс пользователя и содержимое были мгновенно видны. В противном случае, с множеством анимаций на странице, процесс загрузки может стать слишком визуально перегружающим, и производительность может пострадать.
Внутри ngAnimate ожидает, пока все загрузки шаблонов, начатые сразу после инициализации, не завершатся. Затем он ожидает завершения текущего $digest и ещё одного после него. Это гарантирует, что всё приложение полностью скомпилировано перед попыткой выполнения анимаций.
Если вы хотите, чтобы ваши анимации воспроизводились при запуске приложения, вы можете включить анимации глобально в функции run своего основного модуля:
myModule.run(function($animate) {
$animate.enabled(true);
});
Как (селективно) включить, отключить и пропустить анимации
Существует несколько способов отключения анимаций, как глобально, так и для отдельных анимаций. Отключение конкретных анимаций может помочь ускорить производительность рендеринга, например, для больших списков ngRepeat, в которых на самом деле нет анимаций. Поскольку ngAnimate проверяет на этапе выполнения, есть ли анимации, производительность пострадает даже в том случае, если элемент не имеет анимации.
Во время конфигурации: $animateProvider.customFilter()
Эта функция может быть вызвана на фазе config приложения. Она принимает в качестве единственного аргумента функцию фильтра, которая затем будет использоваться для «фильтрации» анимаций (на основе анимируемого элемента, типа события и опций анимации). Только когда функция фильтра вернёт true, анимация будет выполнена. Это даёт большую гибкость — вы можете легко создавать сложные правила, например, разрешая только определённые события или разрешая анимации только на определённых поддеревьях DOM и динамически изменять их, например, отключая анимации в определённые моменты времени или в определённых обстоятельствах.
app.config(function($animateProvider) {
$animateProvider.customFilter(function(node, event, options) {
// Example: Only animate `enter` and `leave` operations.
return event === 'enter' || event === 'leave';
});
});
Подход customFilter обычно обеспечивает большой прирост скорости по сравнению с другими стратегиями, потому что сопоставление выполняется до проверки других стратегий отключения анимации.
Во время конфигурации: $animateProvider.classNameFilter()
Эта функция также может быть вызвана на фазе config приложения. Она принимает в качестве единственного аргумента регулярное выражение, которое затем будет сопоставляться с классами любого элемента, который собирается быть анимированным. Регулярное выражение позволяет большую гибкость — вы можете либо разрешить анимации только для определённых классов (полезно при работе с анимациями сторонних разработчиков), либо исключить определённые классы из анимирования.
app.config(function($animateProvider) {
$animateProvider.classNameFilter(/animate-/);
});
/* prefixed with `animate-` */
.animate-fade-add.animate-fade-add-active {
transition: all 1s linear;
opacity: 0;
}
Подход classNameFilter обычно обеспечивает большой прирост скорости по сравнению с другими стратегиями, так как сопоставление выполняется до проверки других стратегий отключения анимации. Однако это также означает, что нельзя переопределить сопоставление по имени класса с помощью двух следующих стратегий. Конечно, по-прежнему можно включать/отключать анимации, изменяя имя класса элемента во время выполнения.
Во время выполнения: $animate.enabled()
Эта функция может быть использована для включения/отключения анимаций двумя различными способами:
С помощью единственного аргумента boolean можно глобально включить/выключить анимации: $animate.enabled(false) выключает все анимации в вашем приложении.
Когда первым аргументом является родной DOM-элемент или элемент jqLite/jQuery, функция включает/выключает анимации на этом элементе и всех его дочерних элементах: $animate.enabled(myElement, false). Вы по-прежнему можете использовать её для повторного включения анимаций для дочернего элемента, даже если вы выключили их на родительском элементе. И по сравнению с classNameFilter, вы можете изменять состояние анимации во время выполнения, а не во время фазы конфигурации.
Однако обратите внимание, что состояние $animate.enabled() для отдельных элементов не перезаписывает правила отключения, установленные в classNameFilter.
Через стили CSS: перезапись стилей в классе ng-animate CSS
Всякий раз, когда запускается анимация, ngAnimate применяет класс ng-animate к элементу на всё время анимации. Применяя стили CSS-переходов/анимаций к этому классу, вы можете пропустить анимацию:
.my-class {
transition: transform 2s;
}
.my-class:hover {
transform: translateX(50px);
}
my-class.ng-animate {
transition: 0s;
}
Установив transition: 0s, ngAnimate проигнорирует существующие стили перехода и не попытается анимировать их (JavaScript-анимации всё же будут выполняться). Это можно использовать для предотвращения проблем с конфликтами существующих анимаций с ngAnimate.
Предотвращение мерцания перед началом анимации
При вложенности элементов со структурными анимациями, такими как ngIf, в элементы с анимациями на основе классов, такие как ngClass, иногда происходит кратковременное мерцание или вспышка содержимого, когда анимированный элемент на мгновение становится видимым.
Чтобы предотвратить это, вы можете применить стили к классу ng-[event]-prepare, который добавляется как только инициализирована анимация, но удаляется перед началом фактической анимации (после ожидания $digest). Этот класс добавляется только для структурных анимаций (enter, move, и leave).
Вот пример, где может наблюдаться мерцание:
<div ng-class="{red: myProp}">
<div ng-class="{blue: myProp}">
<div class="message" ng-if="myProp"></div>
</div>
</div>
Возможно, во время события enter div с классом .message на мгновение будет виден, прежде чем начнётся анимация. В этом случае вы можете добавить стили в CSS, чтобы убедиться, что элемент остаётся скрытым до начала анимации:
.message.ng-enter-prepare {
opacity: 0;
}
/* Other animation styles ... */
Предотвращение конфликтов с существующими анимациями и библиотеками сторонних разработчиков
По умолчанию любые директивы, использующие ngAnimate, предполагают, что стили transition / animation элемента являются частью анимации ngAnimate. Это может привести к проблемам, когда стили фактически предназначены для анимаций, независимых от ngAnimate.
Например, элемент действует как индикатор загрузки. На нём есть бесконечная CSS-анимация, а также директива ngIf, для которой анимации не определены:
.spinner {
animation: rotating 2s linear infinite;
}
@keyframes rotating {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
Теперь, когда выражение ngIf изменяется, ngAnimate увидит анимацию индикатора загрузки и воспользуется ею для анимации события enter/leave, что не работает, потому что анимация бесконечная. Элемент по-прежнему будет добавляться/удаляться после таймаута, но это приведёт к заметной задержке.
Это также может произойти, потому что некоторые сторонние фреймворки устанавливают значения по умолчанию для длительности анимации для многих элементов или селекторов классов, чтобы их код был компактным и многократно используемым.
Вы можете предотвратить это нежелательное поведение, добавив в CSS стили к классу .ng-animate, который добавляется на всё время каждой анимации. Просто перезапишите длительность перехода/анимации. В случае с индикатором загрузки это будет:
.spinner.ng-animate {
animation: 0s none;
transition: 0s none;
}
Если вы определили CSS-переходы/анимации для событий анимации, убедитесь, что они имеют более высокий приоритет, чем любые стили, не связанные с ngAnimate.
Вы также можете использовать один из других способов отключения анимаций.
Включение анимаций для элементов вне DOM-дерева приложения AngularJS: $animate.pin()
Перед анимацией ngAnimate проверяет, находится ли анимированный элемент внутри DOM-дерева приложения. Если нет, анимация не запускается. Обычно это не проблема, так как большинство приложений используют элементы html или body в качестве корня.
Проблемы возникают, когда приложение инициализируется на другом элементе, и анимации пытаются использовать элементы за пределами дерева приложения, например, когда библиотеки добавляют всплывающие окна или модальные элементы в тег body.
Вы можете использовать $animate.pin(element, parentHost), чтобы связать элемент с другим элементом, принадлежащим вашему приложению. Просто вызовите его перед добавлением элемента в DOM/перед началом анимации, указав элемент, который нужно анимировать, и элемент, который должен быть его предполагаемым родителем.
Подробнее об анимациях
Для полного описания каждого метода, доступного в $animate, обратитесь к документации API.
Для просмотра полной демонстрации см. этап анимации в обучающем пособии по phonecat.
© 2010–2018 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://code.angularjs.org/1.6.9/docs/guide/animations