Перейти к содержанию

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

📅 28.02.2022

Необходимые условия

Базовое понимание следующих концепций:

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

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

Функции, управляющие сложными анимационными последовательностями, следующие:

Функции Подробности
query() Находит один или несколько внутренних элементов HTML.
stagger() Применяет каскадную задержку к анимации для нескольких элементов.
group() Запускает несколько шагов анимации параллельно.
sequence() Запускает шаги анимации один за другим.

Функция query()

Большинство сложных анимаций полагаются на функцию query() для поиска дочерних элементов и применения к ним анимации, основными примерами которых являются:

Примеры Детали
query() с последующим animate() Используется для запроса простых элементов HTML и прямого применения к ним анимации.
query() с последующим animateChild() Используется для запроса дочерних элементов, которые сами имеют метаданные анимации, примененные к ним, и запускают такую анимацию (которая в противном случае была бы заблокирована анимацией текущего/родительского элемента).

Первым аргументом query() является строка css selector, которая также может содержать следующие специфические для Angular лексемы:

Токены Детали
:enter :leave Для ввода/вывода элементов.
:animating Для элементов, которые в данный момент анимируются.
@* @triggerName Для элементов с любым или определенным триггером.
:self Сам анимируемый элемент.

Вход и выход из элементов

Не все дочерние элементы на самом деле считаются входящими/выходящими; иногда это может быть нелогичным и запутанным. Пожалуйста, смотрите query api docs для получения дополнительной информации.

Вы также можете увидеть иллюстрацию этого в живом примере анимации (представленном в разделе анимации введение) на вкладке Querying.

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

После запроса дочерних элементов с помощью функции query(), функция stagger() позволяет определить временной промежуток между каждым анимируемым элементом и, таким образом, анимировать элементы с задержкой между ними.

Следующий пример демонстрирует, как использовать функции query() и stagger() для анимации списка (героев\, добавляя каждый из них последовательно, с небольшой задержкой, сверху вниз.

  • Используйте функцию query() для поиска элемента на странице, который соответствует определенным критериям.

  • Для каждого из этих элементов используйте style(), чтобы установить одинаковый начальный стиль для элемента.

    Сделайте его прозрачным и с помощью transform переместите его из позиции в позицию, чтобы он мог встать на место.

  • Используйте stagger() для задержки каждой анимации на 30 миллисекунд.

  • Анимируйте каждый элемент на экране в течение 0,5 секунды, используя заданную кривую смягчения, одновременно затухая и разворачиваясь.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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-свойства одного и того же элемента, но использовать для каждого из них свою функцию сглаживания.

Для этого можно использовать функцию animation group().

Функция group() используется для группировки шагов анимации, а не анимированных элементов.

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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() для применения данных о стиле в течение заданного интервала времени.

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

Посмотрите на другую анимацию на странице живого примера. На вкладке Filter/Stagger введите текст в поле Search Heroes, например Magnet или tornado.

Фильтр работает в реальном времени по мере ввода текста. Элементы покидают страницу по мере ввода каждой новой буквы, а фильтр становится все более строгим.

Список героев постепенно возвращается на страницу по мере удаления каждой буквы в поле фильтра.

HTML-шаблон содержит триггер под названием filterAnimation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<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 в декораторе компонента содержит три перехода.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
@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 отслеживать такие элементы — присвоить директиве NgForOf функцию TrackByFunction.

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

Важно

Если вам нужно анимировать элементы списка *ngFor и есть вероятность, что порядок этих элементов изменится во время выполнения, всегда используйте TrackByFunction.

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

Анимации Angular основаны на структуре DOM компонентов и не учитывают View Encapsulation, это означает, что компоненты, использующие ViewEncapsulation.Emulated, ведут себя точно так же, как если бы они использовали ViewEncapsulation.None (ViewEncapsulation.ShadowDom ведет себя по-другому, о чем мы поговорим в ближайшее время).

Например, если бы функция query() (о которой мы расскажем в остальной части руководства по анимации) была применена в верхней части дерева компонентов, использующих инкапсуляцию эмулированного представления, такой запрос смог бы идентифицировать (и, таким образом, анимировать) элементы DOM на любой глубине дерева.

С другой стороны, ViewEncapsulation.ShadowDom изменяет структуру DOM компонента, "пряча" элементы DOM внутри элементов ShadowRoot. Такие манипуляции с DOM препятствуют корректной работе некоторых реализаций анимации, поскольку они полагаются на простые структуры DOM и не учитывают элементы ShadowRoot. Поэтому рекомендуется избегать применения анимации к представлениям, включающим компоненты, использующие инкапсуляцию представления ShadowDom.

Краткое описание последовательности анимации

Функции Angular для анимации нескольких элементов начинаются с query() для поиска внутренних элементов; например, сбор всех изображений внутри div. Остальные функции, stagger(), group() и sequence(), применяют каскады или позволяют контролировать применение нескольких шагов анимации.

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

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

Комментарии