O pacote @angular/animations está descontinuado a partir da v20.2, que também introduziu os novos recursos animate.enter e animate.leave para adicionar animações à sua aplicação. Usando esses novos recursos, você pode substituir todas as animações baseadas em @angular/animations por CSS puro ou bibliotecas de animação JS. Remover @angular/animations da sua aplicação pode reduzir significativamente o tamanho do seu bundle JavaScript. Animações CSS nativas geralmente oferecem performance superior, pois podem se beneficiar de aceleração por hardware. Este guia percorre o processo de refatoração do seu código de @angular/animations para animações CSS nativas.
Como escrever animações em CSS nativo
Se você nunca escreveu animações CSS nativas, existem vários guias excelentes para começar. Aqui estão alguns deles: Guia de Animações CSS do MDN Guia de Animações CSS3 do W3Schools Tutorial Completo de Animações CSS Animação CSS para Iniciantes
e alguns vídeos: Aprenda Animação CSS em 9 Minutos Playlist de Tutorial de Animação CSS do Net Ninja
Confira alguns destes vários guias e tutoriais, e depois volte a este guia.
Criando Animações Reutilizáveis
Assim como com o pacote de animations, você pode criar animações reutilizáveis que podem ser compartilhadas em toda a sua aplicação. A versão do pacote de animations tinha você usando a função animation() em um arquivo typescript compartilhado. A versão CSS nativa disso é similar, mas fica em um arquivo CSS compartilhado.
Com Pacote de Animations
src/app/animations.ts
import {animation, style, animate, trigger, transition, useAnimation} from '@angular/animations';export const transitionAnimation = animation([ style({ height: '{{ height }}', opacity: '{{ opacity }}', backgroundColor: '{{ backgroundColor }}', }), animate('{{ time }}'),]);export const sharedAnimation = animation([ style({ height: 0, opacity: 1, backgroundColor: 'red', }), animate('1s'),]);export const triggerAnimation = trigger('openClose', [ transition('open => closed', [ useAnimation(transitionAnimation, { params: { height: 0, opacity: 1, backgroundColor: 'red', time: '1s', }, }), ]),]);
Com CSS Nativo
src/app/animations.css
@keyframes sharedAnimation { to { height: 0; opacity: 1; background-color: 'red'; }}.animated-class { animation: sharedAnimation 1s;}.open { height: '200px'; opacity: 1; background-color: 'yellow'; transition: all 1s;}.closed { height: '100px'; opacity: 0.8; background-color: 'blue'; transition: all 1s;}.example-element { animation-duration: 1s; animation-delay: 500ms; animation-timing-function: ease-in-out;}.example-shorthand { animation: exampleAnimation 1s ease-in-out 500ms;}.example-element { transition-duration: 1s; transition-delay: 500ms; transition-timing-function: ease-in-out; transition-property: margin-right;}.example-shorthand { transition: margin-right 1s ease-in-out 500ms;}
Adicionar a classe animated-class a um elemento acionaria a animação nesse elemento.
Animando uma Transição
Animando Estado e Estilos
O pacote de animations permitia que você definisse vários estados usando a função state() dentro de um component. Exemplos podem ser um estado open ou closed contendo os estilos para cada respectivo estado dentro da definição. Por exemplo:
Com Pacote de Animations
src/app/open-close.component.ts
import {Component, input} from '@angular/core';import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';@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')]), transition('* => closed', [animate('1s')]), transition('* => open', [animate('0.5s')]), transition('open <=> closed', [animate('0.5s')]), transition('* => open', [animate('1s', style({opacity: '*'}))]), transition('* => *', [animate('1s')]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { logging = input(false); isOpen = true; toggle() { this.isOpen = !this.isOpen; } onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } // 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}`); }}
Esse mesmo comportamento pode ser realizado nativamente usando classes CSS, seja usando uma animação de keyframe ou estilização de transition.
Com CSS Nativo
src/app/animations.css
@keyframes sharedAnimation { to { height: 0; opacity: 1; background-color: 'red'; }}.animated-class { animation: sharedAnimation 1s;}.open { height: '200px'; opacity: 1; background-color: 'yellow'; transition: all 1s;}.closed { height: '100px'; opacity: 0.8; background-color: 'blue'; transition: all 1s;}.example-element { animation-duration: 1s; animation-delay: 500ms; animation-timing-function: ease-in-out;}.example-shorthand { animation: exampleAnimation 1s ease-in-out 500ms;}.example-element { transition-duration: 1s; transition-delay: 500ms; transition-timing-function: ease-in-out; transition-property: margin-right;}.example-shorthand { transition: margin-right 1s ease-in-out 500ms;}
Acionar o estado open ou closed é feito alternando classes no elemento em seu component. Você pode encontrar exemplos de como fazer isso em nosso guia de template.
Você pode ver exemplos semelhantes no guia de template para animar estilos diretamente.
Transitions, Timing e Easing
A função animate() do pacote de animations permite fornecer timing, como duração, delays e easing. Isso pode ser feito nativamente com CSS usando várias propriedades CSS ou propriedades de atalho.
Especifique animation-duration, animation-delay e animation-timing-function para uma animação de keyframe em CSS, ou alternativamente use a propriedade de atalho animation.
src/app/animations.css
@keyframes sharedAnimation { to { height: 0; opacity: 1; background-color: 'red'; }}.animated-class { animation: sharedAnimation 1s;}.open { height: '200px'; opacity: 1; background-color: 'yellow'; transition: all 1s;}.closed { height: '100px'; opacity: 0.8; background-color: 'blue'; transition: all 1s;}.example-element { animation-duration: 1s; animation-delay: 500ms; animation-timing-function: ease-in-out;}.example-shorthand { animation: exampleAnimation 1s ease-in-out 500ms;}.example-element { transition-duration: 1s; transition-delay: 500ms; transition-timing-function: ease-in-out; transition-property: margin-right;}.example-shorthand { transition: margin-right 1s ease-in-out 500ms;}
Da mesma forma, você pode usar transition-duration, transition-delay e transition-timing-function e o atalho transition para animações que não estão usando @keyframes.
src/app/animations.css
@keyframes sharedAnimation { to { height: 0; opacity: 1; background-color: 'red'; }}.animated-class { animation: sharedAnimation 1s;}.open { height: '200px'; opacity: 1; background-color: 'yellow'; transition: all 1s;}.closed { height: '100px'; opacity: 0.8; background-color: 'blue'; transition: all 1s;}.example-element { animation-duration: 1s; animation-delay: 500ms; animation-timing-function: ease-in-out;}.example-shorthand { animation: exampleAnimation 1s ease-in-out 500ms;}.example-element { transition-duration: 1s; transition-delay: 500ms; transition-timing-function: ease-in-out; transition-property: margin-right;}.example-shorthand { transition: margin-right 1s ease-in-out 500ms;}
Acionando uma Animação
O pacote de animations requeria especificar triggers usando a função trigger() e aninhar todos os seus estados dentro dela. Com CSS nativo, isso é desnecessário. Animações podem ser acionadas alternando estilos ou classes CSS. Uma vez que uma classe está presente em um elemento, a animação ocorrerá. Remover a classe reverterá o elemento de volta para qualquer CSS que esteja definido para esse elemento. Isso resulta em significativamente menos código para fazer a mesma animação. Aqui está um exemplo:
Com Pacote de Animations
src/app/open-close.component.ts
import {Component, signal} from '@angular/core';import {trigger, transition, state, animate, style, keyframes} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ state( 'open', style({ height: '200px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', 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}), ]), ), ]), ]), ], templateUrl: 'open-close.component.html', styleUrl: 'open-close.component.css',})export class OpenCloseComponent { isOpen = signal(false); toggle() { this.isOpen.update((isOpen) => !isOpen); }}
src/app/open-close.component.html
<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>
src/app/open-close.component.css
:host { display: block; margin-top: 1rem;}.open-close-container { border: 1px solid #dddddd; margin-top: 1em; padding: 20px 20px 0px 20px; color: #000000; font-weight: bold; font-size: 20px;}
Com CSS Nativo
src/app/open-close.component.ts
import {Component, signal} from '@angular/core';@Component({ selector: 'app-open-close', templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { isOpen = signal(true); toggle() { this.isOpen.update((isOpen) => !isOpen); }}
src/app/open-close.component.html
<h2>Open / Close Example</h2><button type="button" (click)="toggle()">Toggle Open/Close</button><div class="open-close-container" [class.open]="isOpen()"> <p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p></div>
src/app/open-close.component.css
:host { display: block; margin-top: 1rem;}.open-close-container { border: 1px solid #dddddd; margin-top: 1em; padding: 20px 20px 0px 20px; font-weight: bold; font-size: 20px; height: 100px; opacity: 0.8; background-color: blue; color: #ebebeb; transition-property: height, opacity, background-color, color; transition-duration: 1s;}.open { transition-duration: 0.5s; height: 200px; opacity: 1; background-color: yellow; color: #000000;}
Transition e Triggers
Estado Predefinido e correspondência com wildcard
O pacote de animations oferece a capacidade de corresponder seus estados definidos a uma transition via strings. Por exemplo, animar de open para closed seria open => closed. Você pode usar wildcards para corresponder qualquer estado a um estado alvo, como * => closed e a palavra-chave void pode ser usada para estados de entrada e saída. Por exemplo: * => void para quando um elemento sai de uma view ou void => * para quando o elemento entra em uma view.
Esses padrões de correspondência de estado não são necessários de forma alguma ao animar com CSS diretamente. Você pode gerenciar quais transitions e animações @keyframes aplicam com base em quaisquer classes que você define e/ou estilos que você define nos elementos. Você também pode adicionar @starting-style para controlar como o elemento fica ao entrar imediatamente no DOM.
Cálculo Automático de Propriedade com Wildcards
O pacote de animations oferece a capacidade de animar coisas que foram historicamente difíceis de animar, como animar uma altura definida para height: auto. Você agora pode fazer isso com CSS puro também.
Com Pacote de Animations
src/app/auto-height.component.ts
import {Component, signal} from '@angular/core';import {trigger, transition, state, animate, style} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ state('true', style({height: '*'})), state('false', style({height: '0px'})), transition('false <=> true', animate(1000)), ]), ], templateUrl: 'auto-height.component.html', styleUrl: 'auto-height.component.css',})export class AutoHeightComponent { isOpen = signal(false); toggle() { this.isOpen.update((isOpen) => !isOpen); }}
src/app/auto-height.component.html
<h2>Auto Height Example</h2><button type="button" (click)="toggle()">Toggle Open/Close</button><div class="container" [@openClose]="isOpen() ? true : false"> <div class="content"> <p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p> </div></div>
src/app/auto-height.component.css
.container { display: block; overflow: hidden;}.container .content { padding: 20px; margin-top: 1em; font-weight: bold; font-size: 20px; background-color: blue; color: #ebebeb;}
Você pode usar css-grid para animar para altura automática.
Com CSS Nativo
src/app/auto-height.component.ts
import {Component, signal} from '@angular/core';@Component({ selector: 'app-auto-height', templateUrl: 'auto-height.component.html', styleUrls: ['auto-height.component.css'],})export class AutoHeightComponent { isOpen = signal(true); toggle() { this.isOpen.update((isOpen) => !isOpen); }}
src/app/auto-height.component.html
<h2>Auto Height Example</h2><button type="button" (click)="toggle()">Toggle Open/Close</button><div class="container" [class.open]="isOpen()"> <div class="content"> <p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p> </div></div>
src/app/auto-height.component.css
.container { display: grid; grid-template-rows: 0fr; overflow: hidden; transition: grid-template-rows 1s;}.container.open { grid-template-rows: 1fr;}.container .content { min-height: 0; transition: visibility 1s; padding: 0 20px; visibility: hidden; margin-top: 1em; font-weight: bold; font-size: 20px; background-color: blue; color: #ebebeb; overflow: hidden;}.container.open .content { visibility: visible;}
Se você não precisa se preocupar em suportar todos os browsers, você também pode conferir calc-size(), que é a verdadeira solução para animar altura automática. Veja a documentação do MDN e este tutorial para mais informações.
Animar entrada e saída de uma view
O pacote de animations oferecia o padrão de correspondência mencionado anteriormente para entrada e saída, mas também incluía os aliases de atalho de :enter e :leave.
Com Pacote de Animations
src/app/insert-remove.component.ts
import {Component} from '@angular/core';import {trigger, transition, animate, style} from '@angular/animations';@Component({ selector: 'app-insert-remove', animations: [ trigger('myInsertRemoveTrigger', [ transition(':enter', [style({opacity: 0}), animate('200ms', style({opacity: 1}))]), transition(':leave', [animate('200ms', style({opacity: 0}))]), ]), ], templateUrl: 'insert-remove.component.html', styleUrls: ['insert-remove.component.css'],})export class InsertRemoveComponent { isShown = false; toggle() { this.isShown = !this.isShown; }}
src/app/insert-remove.component.html
<h2>Insert/Remove</h2><nav> <button type="button" (click)="toggle()">Toggle Insert/Remove</button></nav>@if (isShown) { <div @myInsertRemoveTrigger class="insert-remove-container"> <p>The box is inserted</p> </div>}
src/app/insert-remove.component.css
:host { display: block;}.insert-remove-container { border: 1px solid #dddddd; margin-top: 1em; padding: 20px 20px 0px 20px; color: #000000; font-weight: bold; font-size: 20px;}
Aqui está como a mesma coisa pode ser realizada sem o pacote de animations usando animate.enter.
Com CSS Nativo
src/app/insert.component.ts
import {Component, signal} from '@angular/core';@Component({ selector: 'app-insert', templateUrl: 'insert.component.html', styleUrls: ['insert.component.css'],})export class InsertComponent { isShown = signal(false); toggle() { this.isShown.update((isShown) => !isShown); }}
src/app/insert.component.html
<h2>Insert Element Example</h2><nav> <button type="button" (click)="toggle()">Toggle Element</button></nav>@if (isShown()) { <div class="insert-container" animate.enter="enter-animation"> <p>The box is inserted</p> </div>}
src/app/insert.component.css
:host { display: block;}.insert-container { border: 1px solid #dddddd; margin-top: 1em; padding: 20px 20px 0px 20px; font-weight: bold; font-size: 20px;}.enter-animation { animation: slide-fade 1s;}@keyframes slide-fade { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); }}
Use animate.leave para animar elementos à medida que eles saem da view, o que aplicará as classes CSS especificadas ao elemento à medida que ele sai da view.
Com CSS Nativo
src/app/remove.component.ts
import {Component, signal} from '@angular/core';@Component({ selector: 'app-remove', templateUrl: 'remove.component.html', styleUrls: ['remove.component.css'],})export class RemoveComponent { isShown = signal(false); toggle() { this.isShown.update((isShown) => !isShown); }}
src/app/remove.component.html
<h2>Remove Element Example</h2><nav> <button type="button" (click)="toggle()">Toggle Element</button></nav>@if (isShown()) { <div class="insert-container" animate.leave="deleting"> <p>The box is inserted</p> </div>}
src/app/remove.component.css
:host { display: block;}.insert-container { border: 1px solid #dddddd; margin-top: 1em; padding: 20px 20px 0px 20px; font-weight: bold; font-size: 20px; opacity: 1; transition: opacity 200ms ease-in; @starting-style { opacity: 0; }}.deleting { opacity: 0; transform: translateY(20px); transition: opacity 500ms ease-out, transform 500ms ease-out;}
Para mais informações sobre animate.enter e animate.leave, veja o guia de animações Enter e Leave.
Animando incremento e decremento
Juntamente com os mencionados :enter e :leave, também há :increment e :decrement. Você pode animar esses também adicionando e removendo classes. Ao contrário dos aliases integrados do pacote de animation, não há aplicação automática de classes quando os valores sobem ou descem. Você pode aplicar as classes apropriadas programaticamente. Aqui está um exemplo:
Com Pacote de Animations
src/app/increment-decrement.component.ts
import {Component, signal} from '@angular/core';import {trigger, transition, animate, style, query, stagger} from '@angular/animations';@Component({ selector: 'app-increment-decrement', templateUrl: 'increment-decrement.component.html', styleUrls: ['increment-decrement.component.css'], animations: [ trigger('incrementAnimation', [ transition(':increment', [ animate('300ms ease-out', style({color: 'green', transform: 'scale(1.3, 1.2)'})), ]), transition(':decrement', [ animate('300ms ease-out', style({color: 'red', transform: 'scale(0.8, 0.9)'})), ]), ]), ],})export class IncrementDecrementComponent { num = signal(0); modify(n: number) { this.num.update((v) => (v += n)); }}
src/app/increment-decrement.component.html
<h3>Increment and Decrement Example</h3><section> <p [@incrementAnimation]="num()">Number {{ num() }}</p> <div class="controls"> <button type="button" (click)="modify(1)">+</button> <button type="button" (click)="modify(-1)">-</button> </div></section>
src/app/increment-decrement.component.css
:host { display: block; font-size: 32px; margin: 20px; text-align: center;}section { border: 1px solid lightgray; border-radius: 50px;}p { display: inline-block; margin: 2rem 0; text-transform: uppercase;}.controls { padding-bottom: 2rem;}button { font: inherit; border: 0; background: lightgray; width: 50px; border-radius: 10px;}button + button { margin-left: 10px;}
Com CSS Nativo
src/app/increment-decrement.component.ts
import {Component, ElementRef, OnInit, signal, viewChild} from '@angular/core';@Component({ selector: 'app-increment-decrement', templateUrl: 'increment-decrement.component.html', styleUrls: ['increment-decrement.component.css'],})export class IncrementDecrementComponent implements OnInit { num = signal(0); el = viewChild<ElementRef<HTMLParagraphElement>>('el'); ngOnInit() { this.el()?.nativeElement.addEventListener('animationend', (ev) => { if (ev.animationName.endsWith('decrement') || ev.animationName.endsWith('increment')) { this.animationFinished(); } }); } modify(n: number) { const targetClass = n > 0 ? 'increment' : 'decrement'; this.num.update((v) => (v += n)); this.el()?.nativeElement.classList.add(targetClass); } animationFinished() { this.el()?.nativeElement.classList.remove('increment', 'decrement'); } ngOnDestroy() { this.el()?.nativeElement.removeEventListener('animationend', this.animationFinished); }}
src/app/increment-decrement.component.html
<h3>Increment and Decrement Example</h3><section> <p #el>Number {{ num() }}</p> <div class="controls"> <button type="button" (click)="modify(1)">+</button> <button type="button" (click)="modify(-1)">-</button> </div></section>
src/app/increment-decrement.component.css
:host { display: block; font-size: 32px; margin: 20px; text-align: center;}section { border: 1px solid lightgray; border-radius: 50px;}p { display: inline-block; margin: 2rem 0; text-transform: uppercase;}.increment { animation: increment 300ms;}.decrement { animation: decrement 300ms;}.controls { padding-bottom: 2rem;}button { font: inherit; border: 0; background: lightgray; width: 50px; border-radius: 10px;}button + button { margin-left: 10px;}@keyframes increment { 33% { color: green; transform: scale(1.3, 1.2); } 66% { color: green; transform: scale(1.2, 1.2); } 100% { transform: scale(1, 1); }}@keyframes decrement { 33% { color: red; transform: scale(0.8, 0.9); } 66% { color: red; transform: scale(0.9, 0.9); } 100% { transform: scale(1, 1); }}
Animações Pai / Filho
Ao contrário do pacote de animations, quando múltiplas animações são especificadas dentro de um determinado component, nenhuma animação tem prioridade sobre outra e nada bloqueia qualquer animação de disparar. Qualquer sequenciamento de animações teria que ser tratado pela sua definição da sua animação CSS, usando animation / transition delay, e/ou usando animationend ou transitionend para tratar a adição do próximo CSS a ser animado.
Desabilitando uma animação ou todas as animações
Com animações CSS nativas, se você quiser desabilitar as animações que especificou, você tem múltiplas opções.
- Crie uma classe personalizada que force animation e transition para
none.
.no-animation { animation: none !important; transition: none !important;}
Aplicar esta classe a um elemento impede que qualquer animação seja disparada nesse elemento. Você poderia alternativamente delimitar isso ao seu DOM inteiro ou seção do seu DOM para aplicar esse comportamento. No entanto, isso impede que eventos de animação sejam disparados. Se você está aguardando eventos de animação para remoção de elemento, essa solução não funcionará. Uma solução alternativa é definir as durações para 1 milissegundo.
Use a media query
prefers-reduced-motionpara garantir que nenhuma animação seja reproduzida para usuários que preferem menos animação.Previna adicionar classes de animação programaticamente
Callbacks de Animação
O pacote de animations expunha callbacks para você usar no caso de querer fazer algo quando a animação terminou. Animações CSS nativas também têm esses callbacks.
OnAnimationStart
OnAnimationEnd
OnAnimationIteration
OnAnimationCancel
OnTransitionStart
OnTransitionRun
OnTransitionEnd
OnTransitionCancel
A Web Animations API tem muita funcionalidade adicional. Dê uma olhada na documentação para ver todas as APIs de animação disponíveis.
NOTA: Esteja ciente de problemas de bubbling com esses callbacks. Se você está animando filhos e pais, os eventos sobem dos filhos para os pais. Considere parar a propagação ou olhar mais detalhes dentro do evento para determinar se você está respondendo ao evento alvo desejado em vez de um evento subindo de um nó filho. Você pode examinar a propriedade animationname ou as propriedades sendo transicionadas para verificar se você tem os nós certos.
Sequências Complexas
O pacote de animations tem funcionalidade integrada para criar sequências complexas. Essas sequências são todas inteiramente possíveis sem o pacote de animations.
Direcionando elementos específicos
No pacote de animations, você poderia direcionar elementos específicos usando a função query() para encontrar elementos específicos por um nome de classe CSS, similar a document.querySelector(). Isso é desnecessário em um mundo de animação CSS nativa. Em vez disso, você pode usar seus seletores CSS para direcionar subclasses e aplicar qualquer transform ou animation desejada.
Para alternar classes para nós filhos dentro de um template, você pode usar bindings de classe e estilo para adicionar as animações nos pontos certos.
Stagger()
A função stagger() permitia que você atrasasse a animação de cada item em uma lista de itens por um tempo especificado para criar um efeito cascata. Você pode replicar esse comportamento em CSS nativo utilizando animation-delay ou transition-delay. Aqui está um exemplo de como esse CSS pode parecer.
Com Pacote de Animations
src/app/stagger.component.ts
import {Component, HostBinding, signal} from '@angular/core';import {trigger, transition, animate, style, query, stagger} from '@angular/animations';@Component({ selector: 'app-stagger', templateUrl: 'stagger.component.html', styleUrls: ['stagger.component.css'], animations: [ trigger('pageAnimations', [ transition(':enter', [ query('.item', [ style({opacity: 0, transform: 'translateY(-10px)'}), stagger(200, [animate('500ms ease-in', style({opacity: 1, transform: 'none'}))]), ]), ]), ]), ],})export class StaggerComponent { @HostBinding('@pageAnimations') items = [1, 2, 3];}
src/app/stagger.component.html
<h2>Stagger Example</h2><ul class="items"> @for(item of items; track item) { <li class="item">{{ item }}</li> }</ul>
src/app/stagger.component.css
.items { list-style: none; padding: 0; margin: 0;}
Com CSS Nativo
src/app/stagger.component.ts
import {Component, signal} from '@angular/core';@Component({ selector: 'app-stagger', templateUrl: './stagger.component.html', styleUrls: ['stagger.component.css'],})export class StaggerComponent { show = signal(true); items = [1, 2, 3]; refresh() { this.show.set(false); setTimeout(() => { this.show.set(true); }, 10); }}
src/app/stagger.component.html
<h1>Stagger Example</h1><button type="button" (click)="refresh()">Refresh</button>@if (show()) { <ul class="items"> @for(item of items; track item) { <li class="item" style="--index: {{ item }}">{{item}}</li> } </ul>}
src/app/stagger.component.css
.items { list-style: none; padding: 0; margin: 0;}.items .item { transition-property: opacity, transform; transition-duration: 500ms; transition-delay: calc(200ms * var(--index)); @starting-style { opacity: 0; transform: translateX(-10px); }}
Animações Paralelas
O pacote de animations tem uma função group() para reproduzir múltiplas animações ao mesmo tempo. Em CSS, você tem controle total sobre o timing da animação. Se você tem múltiplas animações definidas, você pode aplicar todas elas de uma vez.
.target-element { animation: rotate 3s, fade-in 2s;}
Neste exemplo, as animações rotate e fade-in disparam ao mesmo tempo.
Animando os itens de uma lista reordenada
Itens reordenando em uma lista funciona imediatamente usando as técnicas descritas anteriormente. Nenhum trabalho especial adicional é necessário. Itens em um loop @for serão removidos e readicionados adequadamente, o que disparará animações usando @starting-styles para animações de entrada. Alternativamente, você pode usar animate.enter para esse mesmo comportamento. Use animate.leave para animar elementos à medida que eles são removidos, como visto no exemplo acima.
Com Pacote de Animations<
src/app/reorder.component.ts
import {Component, signal} from '@angular/core';import {trigger, transition, animate, query, style} from '@angular/animations';@Component({ selector: 'app-reorder', templateUrl: './reorder.component.html', styleUrls: ['reorder.component.css'], animations: [ trigger('itemAnimation', [ transition(':enter', [ style({opacity: 0, transform: 'translateX(-10px)'}), animate('300ms', style({opacity: 1, transform: 'translateX(none)'})), ]), transition(':leave', [ style({opacity: 1, transform: 'translateX(none)'}), animate('300ms', style({opacity: 0, transform: 'translateX(-10px)'})), ]), ]), ],})export class ReorderComponent { show = signal(true); items = ['stuff', 'things', 'cheese', 'paper', 'scissors', 'rock']; randomize() { const randItems = [...this.items]; const newItems = []; for (let i of this.items) { const max: number = this.items.length - newItems.length; const randNum = Math.floor(Math.random() * max); newItems.push(...randItems.splice(randNum, 1)); } this.items = newItems; }}
src/app/reorder.component.html
<h1>Reordering List Example</h1><button type="button" (click)="randomize()">Randomize</button><ul class="items"> @for(item of items; track item) { <li @itemAnimation class="item">{{ item }}</li> }</ul>
src/app/reorder.component.css
.items { list-style: none; padding: 0; margin: 0;}
Com CSS Nativo
src/app/reorder.component.ts
import {Component, signal} from '@angular/core';@Component({ selector: 'app-reorder', templateUrl: './reorder.component.html', styleUrls: ['reorder.component.css'],})export class ReorderComponent { show = signal(true); items = ['stuff', 'things', 'cheese', 'paper', 'scissors', 'rock']; randomize() { const randItems = [...this.items]; const newItems = []; for (let i of this.items) { const max: number = this.items.length - newItems.length; const randNum = Math.floor(Math.random() * max); newItems.push(...randItems.splice(randNum, 1)); } this.items = newItems; }}
src/app/reorder.component.html
<h1>Reordering List Example</h1><button type="button" (click)="randomize()">Randomize</button><ul class="items"> @for(item of items; track item) { <li class="item" animate.leave="fade">{{ item }}</li> }</ul>
src/app/reorder.component.css
.items { list-style: none; padding: 0; margin: 0;}.items .item { transition-property: opacity, transform; transition-duration: 500ms; @starting-style { opacity: 0; transform: translateX(-10px); }}.items .item.fade { animation: fade-out 500ms;}@keyframes fade-out { from { opacity: 1; } to { opacity: 0; }}
Migrando usos de AnimationPlayer
A classe AnimationPlayer permite acesso a uma animação para fazer coisas mais avançadas como pausar, reproduzir, reiniciar e terminar uma animação através de código. Todas essas coisas podem ser tratadas nativamente também.
Você pode recuperar animações de um elemento diretamente usando Element.getAnimations(). Isso retorna um array de cada Animation naquele elemento. Você pode usar a API Animation para fazer muito mais do que poderia com o que o AnimationPlayer do pacote de animations oferecia. A partir daqui você pode cancel(), play(), pause(), reverse() e muito mais. Esta API nativa deve fornecer tudo que você precisa para controlar suas animações.
Transições de Rota
Você pode usar view transitions para animar entre rotas. Veja o Guia de Animações de Transição de Rota para começar.