IMPORTANTE: El paquete @angular/animations ahora está deprecado. El equipo de Angular recomienda usar CSS nativo con animate.enter y animate.leave para animaciones en todo código nuevo. Aprende más en la nueva guía de animaciones de entrada y salida. También consulta Migrando del paquete de Animations de Angular para aprender cómo puedes comenzar a migrar a animaciones CSS puras en tus aplicaciones.
Esta guía profundiza en estados de transición especiales como el comodín * y void. Muestra cómo estos estados especiales se usan para elementos que entran y salen de una vista.
Esta sección también explora múltiples triggers de animación, callbacks de animación y animación basada en secuencias usando keyframes.
Estados predefinidos y coincidencia de comodines
En Angular, los estados de transición se pueden definir explícitamente a través de la función state(), o usando los estados predefinidos * comodín y void.
Estado comodín
Un asterisco * o comodín coincide con cualquier estado de animación.
Esto es útil para definir transiciones que se aplican independientemente del estado inicial o final del elemento HTML.
Por ejemplo, una transición de open => * se aplica cuando el estado del elemento cambia de open a cualquier otra cosa.
El siguiente es otro ejemplo de código usando el estado comodín junto con el ejemplo anterior usando los estados open y closed.
En lugar de definir cada par de transición estado-a-estado, cualquier transición a closed toma 1 segundo, y cualquier transición a open toma 0.5 segundos.
Esto permite la adición de nuevos estados sin tener que incluir transiciones separadas para cada uno.
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}`); }}
Usa una sintaxis de doble flecha para especificar transiciones estado-a-estado en ambas direcciones.
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}`); }}
Usa el estado comodín con múltiples estados de transición
En el ejemplo del botón de dos estados, el comodín no es tan útil porque solo hay dos estados posibles, open y closed.
En general, usa estados comodín cuando un elemento tiene múltiples estados potenciales a los que puede cambiar.
Si el botón puede cambiar de open a closed o algo como inProgress, usar un estado comodín podría reducir la cantidad de código necesario.
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}`); }}
La transición * => * se aplica cuando tiene lugar cualquier cambio entre dos estados.
Las transiciones se emparejan en el orden en que están definidas.
Por lo tanto, puedes aplicar otras transiciones sobre la transición * => *.
Por ejemplo, define cambios de estilo o animaciones que se aplicarían solo a open => closed, luego usa * => * como respaldo para emparejamientos de estado que no estén específicamente llamados.
Para hacer esto, lista las transiciones más específicas antes de * => *.
Usa comodines con estilos
Usa el comodín * con un estilo para decirle a la animación que use cualquier valor de estilo actual y anime con eso.
El comodín es un valor de respaldo que se usa si el estado que se está animando no está declarado dentro del trigger.
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}`); }}
Estado void
Usa el estado void para configurar transiciones para un elemento que está entrando o saliendo de una página.
Consulta Animando entrada y salida de una vista.
Combinar estados comodín y void
Combina estados comodín y void en una transición para disparar animaciones que entran y salen de la página:
- Una transición de
* => voidse aplica cuando el elemento sale de una vista, independientemente del estado en el que estaba antes de salir - Una transición de
void => *se aplica cuando el elemento entra en una vista, independientemente del estado que asuma al entrar - El estado comodín
*coincide con cualquier estado, incluyendovoid
Animar entrada y salida de una vista
Esta sección muestra cómo animar elementos entrando o saliendo de una página.
Agrega un nuevo comportamiento:
- Cuando agregas un héroe a la lista de héroes, parece volar a la página desde la izquierda
- Cuando eliminas un héroe de la lista, parece volar hacia la derecha
src/app/hero-list-enter-leave.component.ts
import {Component, input, output} from '@angular/core';import {trigger, state, style, animate, transition} from '@angular/animations';import {Hero} from './hero';@Component({ selector: 'app-hero-list-enter-leave', template: ` <ul class="heroes"> @for (hero of heroes(); track hero) { <li [@flyInOut]="'in'"> <button class="inner" type="button" (click)="removeHero(hero.id)"> <span class="badge">{{ hero.id }}</span> <span class="name">{{ hero.name }}</span> </button> </li> } </ul> `, styleUrls: ['./hero-list-page.component.css'], 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%)'}))]), ]), ],})export class HeroListEnterLeaveComponent { readonly heroes = input<Hero[]>([]); readonly remove = output<number>(); removeHero(id: number) { this.remove.emit(id); }}
En el código anterior, aplicaste el estado void cuando el elemento HTML no está adjunto a una vista.
Alias :enter y :leave
:enter y :leave son alias para las transiciones void => * y * => void.
Estos alias son usados por varias funciones de animación.
transition ( ':enter', [ … ] ); // alias for void => *transition ( ':leave', [ … ] ); // alias for * => void
Es más difícil dirigirse a un elemento que está entrando en una vista porque aún no está en el DOM.
Usa los alias :enter y :leave para dirigirte a elementos HTML que se insertan o eliminan de una vista.
Usa *ngIf y *ngFor con :enter y :leave
La transición :enter se ejecuta cuando cualquier vista *ngIf o *ngFor se coloca en la página, y :leave se ejecuta cuando esas vistas se eliminan de la página.
IMPORTANTE: Los comportamientos de entrada/salida a veces pueden ser confusos.
Como regla general, considera que cualquier elemento que Angular agregue al DOM pasa por la transición :enter. Solo los elementos que Angular elimine directamente del DOM pasan por la transición :leave. Por ejemplo, la vista de un elemento se elimina del DOM porque su padre se está eliminando del DOM.
Este ejemplo tiene un trigger especial para la animación de entrada y salida llamado myInsertRemoveTrigger.
La plantilla HTML contiene el siguiente código.
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>}
En el archivo del componente, la transición :enter establece una opacidad inicial de 0. Luego la anima para cambiar esa opacidad a 1 a medida que el elemento se inserta en la vista.
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('100ms', style({opacity: 1}))]), transition(':leave', [animate('100ms', style({opacity: 0}))]), ]), ], templateUrl: 'insert-remove.component.html', styleUrls: ['insert-remove.component.css'],})export class InsertRemoveComponent { isShown = false; toggle() { this.isShown = !this.isShown; }}
Nota que este ejemplo no necesita usar state().
Transición :increment y :decrement
La función transition() acepta otros valores de selector, :increment y :decrement.
Úsalos para iniciar una transición cuando un valor numérico ha aumentado o disminuido en valor.
ÚTIL: El siguiente ejemplo usa los métodos query() y stagger().
Para más información sobre estos métodos, consulta la página de secuencias complejas.
src/app/hero-list-page.component.ts
import {Component, HostBinding, OnInit} from '@angular/core';import {trigger, transition, animate, style, query, stagger} from '@angular/animations';import {HEROES} from './mock-heroes';import {Hero} from './hero';@Component({ selector: 'app-hero-list-page', templateUrl: 'hero-list-page.component.html', styleUrls: ['hero-list-page.component.css'], 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'})), ]), ]), ]), ]), 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 { @HostBinding('@pageAnimations') public animatePage = true; 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; } }}
Valores booleanos en transiciones
Si un trigger contiene un valor booleano como valor de enlace, entonces este valor se puede emparejar usando una expresión transition() que compara true y false, o 1 y 0.
src/app/open-close.component.html
<nav> <button type="button" (click)="toggle()">Toggle Boolean/Close</button></nav><div [@openClose]="isOpen ? true : false" class="open-close-container"> <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p></div>
En el fragmento de código anterior, la plantilla HTML enlaza un elemento <div> a un trigger llamado openClose con una expresión de estado de isOpen, y con valores posibles de true y false.
Este patrón es una alternativa a la práctica de crear dos estados nombrados como open y close.
Dentro de los metadatos del @Component bajo la propiedad animations:, cuando el estado se evalúa a true, la altura del elemento HTML asociado es un estilo comodín o predeterminado.
En este caso, la animación usa cualquier altura que el elemento ya tenía antes de que comenzara la animación.
Cuando el elemento está closed, el elemento se anima a una altura de 0, lo que lo hace invisible.
src/app/open-close.component.ts
import {Component} from '@angular/core';import {trigger, transition, state, animate, style} from '@angular/animations';@Component({ selector: 'app-open-close-boolean', animations: [ trigger('openClose', [ state('true', style({height: '*'})), state('false', style({height: '0px'})), transition('false <=> true', animate(500)), ]), ], templateUrl: 'open-close.component.2.html', styleUrls: ['open-close.component.css'],})export class OpenCloseBooleanComponent { isOpen = false; toggle() { this.isOpen = !this.isOpen; }}
Múltiples triggers de animación
Puedes definir más de un trigger de animación para un componente. Adjunta triggers de animación a diferentes elementos, y las relaciones padre-hijo entre los elementos afectan cómo y cuándo se ejecutan las animaciones.
Animaciones padre-hijo
Cada vez que se dispara una animación en Angular, la animación padre siempre tiene prioridad y las animaciones hijas se bloquean.
Para que se ejecute una animación hija, la animación padre debe consultar cada uno de los elementos que contienen animaciones hijas. Luego permite que las animaciones se ejecuten usando la función animateChild().
Deshabilitar una animación en un elemento HTML
Un enlace de control de animación especial llamado @.disabled se puede colocar en un elemento HTML para apagar animaciones en ese elemento, así como en cualquier elemento anidado.
Cuando es true, el enlace @.disabled previene que se rendericen todas las animaciones.
El siguiente ejemplo de código muestra cómo usar esta característica.
src/app/open-close.component.html
<nav> <button type="button" (click)="toggleAnimations()">Toggle Animations</button> <button type="button" (click)="toggle()">Toggle Open/Closed</button></nav><div [@.disabled]="isDisabled"> <div [@childAnimation]="isOpen ? 'open' : 'closed'" class="open-close-container"> <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p> </div></div>
src/app/open-close.component.ts
import {Component} from '@angular/core';import {trigger, transition, state, animate, style} from '@angular/animations';@Component({ selector: 'app-open-close-toggle', templateUrl: 'open-close.component.4.html', styleUrls: ['open-close.component.css'], animations: [ trigger('childAnimation', [ // ... state( 'open', style({ width: '250px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', style({ width: '100px', opacity: 0.8, backgroundColor: 'blue', }), ), transition('* => *', [animate('1s')]), ]), ],})export class OpenCloseChildComponent { isDisabled = false; isOpen = false; toggleAnimations() { this.isDisabled = !this.isDisabled; } toggle() { this.isOpen = !this.isOpen; }}
Cuando el enlace @.disabled es true, el trigger @childAnimation no se activa.
Cuando un elemento dentro de una plantilla HTML tiene animaciones desactivadas usando el enlace host @.disabled, las animaciones se desactivan en todos los elementos internos también.
No puedes desactivar selectivamente múltiples animaciones en un solo elemento.
Una animación hija selectiva aún se puede ejecutar en un padre deshabilitado de una de las siguientes maneras:
- Una animación padre puede usar la función
query()para recopilar elementos internos ubicados en áreas deshabilitadas de la plantilla HTML. Esos elementos aún pueden animar.
- Una animación hija puede ser consultada por un padre y luego animada posteriormente con la función
animateChild()
Deshabilitar todas las animaciones
Para desactivar todas las animaciones para una aplicación Angular, coloca el enlace host @.disabled en el componente Angular superior.
src/app/app.component.ts
import {Component, HostBinding, inject} from '@angular/core';import { trigger, state, style, animate, transition, // ...} from '@angular/animations';import {ChildrenOutletContexts, RouterLink, RouterOutlet} from '@angular/router';import {slideInAnimation} from './animations';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrls: ['app.component.css'], imports: [RouterLink, RouterOutlet], animations: [ slideInAnimation, // animation triggers go here ],})export class AppComponent { @HostBinding('@.disabled') public animationsDisabled = false; private contexts = inject(ChildrenOutletContexts); getRouteAnimationData() { return this.contexts.getContext('primary')?.route?.snapshot?.data?.['animation']; } toggleAnimations() { this.animationsDisabled = !this.animationsDisabled; }}
ÚTIL: Deshabilitar animaciones en toda la aplicación es útil durante las pruebas end-to-end (E2E).
Callbacks de animación
La función trigger() de animación emite callbacks cuando comienza y cuando termina.
El siguiente ejemplo presenta un componente que contiene un trigger openClose.
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}`); }}
En la plantilla HTML, el evento de animación se devuelve a través de $event, como @triggerName.start y @triggerName.done, donde triggerName es el nombre del trigger que se está usando.
En este ejemplo, el trigger openClose aparece de la siguiente manera.
src/app/open-close.component.html
<nav> <button type="button" (click)="toggle()">Toggle Open/Close</button></nav> <div [@openClose]="isOpen ? 'open' : 'closed'" (@openClose.start)="onAnimationEvent($event)" (@openClose.done)="onAnimationEvent($event)" class="open-close-container"> <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p></div>
Un uso potencial para callbacks de animación podría ser cubrir una llamada API lenta, como una búsqueda en base de datos. Por ejemplo, se puede configurar un botón InProgress para tener su propia animación en bucle mientras la operación del sistema backend finaliza.
Se puede llamar otra animación cuando la animación actual termina.
Por ejemplo, el botón pasa del estado inProgress al estado closed cuando la llamada API se completa.
Una animación puede influir en que un usuario final perciba la operación como más rápida, incluso cuando no lo es.
Los callbacks pueden servir como herramienta de depuración, por ejemplo en conjunto con console.warn() para ver el progreso de la aplicación en la Consola de JavaScript del desarrollador del navegador.
El siguiente fragmento de código crea salida de registro de consola para el ejemplo original, un botón con los dos estados de open y closed.
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}`); }}
Keyframes
Para crear una animación con múltiples pasos ejecutados en secuencia, usa keyframes.
La función keyframe() de Angular permite varios cambios de estilo dentro de un solo segmento de tiempo.
Por ejemplo, el botón, en lugar de desvanecerse, podría cambiar de color varias veces durante un solo período de tiempo de 2 segundos.
El código para este cambio de color podría verse así.
src/app/status-slider.component.ts
import {Component} from '@angular/core';import {trigger, transition, state, animate, style, keyframes} from '@angular/animations';@Component({ selector: 'app-status-slider', templateUrl: 'status-slider.component.html', styleUrls: ['status-slider.component.css'], animations: [ trigger('slideStatus', [ state('inactive', style({backgroundColor: 'blue'})), state('active', style({backgroundColor: '#754600'})), 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}), ]), ), ]), transition('* => active', [ animate( '2s', keyframes([ style({backgroundColor: 'blue'}), style({backgroundColor: 'red'}), style({backgroundColor: 'orange'}), ]), ), ]), ]), ],})export class StatusSliderComponent { status: 'active' | 'inactive' = 'inactive'; toggle() { if (this.status === 'active') { this.status = 'inactive'; } else { this.status = 'active'; } }}
Offset
Los keyframes incluyen un offset que define el punto en la animación donde ocurre cada cambio de estilo.
Los offsets son medidas relativas de cero a uno, marcando el inicio y el final de la animación. Deben aplicarse a cada uno de los pasos de keyframe si se usan al menos una vez.
Definir offsets para keyframes es opcional. Si los omites, se asignan automáticamente offsets espaciados uniformemente. Por ejemplo, tres keyframes sin offsets predefinidos reciben offsets de 0, 0.5 y 1. Especificar un offset de 0.8 para la transición media en el ejemplo anterior podría verse así.
El código con offsets especificados sería el siguiente.
src/app/status-slider.component.ts
import {Component} from '@angular/core';import {trigger, transition, state, animate, style, keyframes} from '@angular/animations';@Component({ selector: 'app-status-slider', templateUrl: 'status-slider.component.html', styleUrls: ['status-slider.component.css'], animations: [ trigger('slideStatus', [ state('inactive', style({backgroundColor: 'blue'})), state('active', style({backgroundColor: '#754600'})), 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}), ]), ), ]), transition('* => active', [ animate( '2s', keyframes([ style({backgroundColor: 'blue'}), style({backgroundColor: 'red'}), style({backgroundColor: 'orange'}), ]), ), ]), ]), ],})export class StatusSliderComponent { status: 'active' | 'inactive' = 'inactive'; toggle() { if (this.status === 'active') { this.status = 'inactive'; } else { this.status = 'active'; } }}
Puedes combinar keyframes con duration, delay y easing dentro de una sola animación.
Keyframes con pulsación
Usa keyframes para crear un efecto de pulso en tus animaciones definiendo estilos en offsets específicos a lo largo de la animación.
Aquí hay un ejemplo de uso de keyframes para crear un efecto de pulso:
- Los estados originales
openyclosed, con los cambios originales en altura, color y opacidad, ocurriendo durante un período de tiempo de 1 segundo - Una secuencia de keyframes insertada en el medio que hace que el botón parezca pulsar irregularmente durante el mismo período de tiempo de 1 segundo
El fragmento de código para esta animación podría verse así.
src/app/open-close.component.ts
import {Component, input} from '@angular/core';import { trigger, transition, state, animate, style, keyframes, AnimationEvent,} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ 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}), ]), ), ]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseKeyframeComponent { isOpen = false; toggle() { this.isOpen = !this.isOpen; } logging = input(false); onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } }}
Propiedades animables y unidades
Las animaciones de Angular se construyen sobre animaciones web, por lo que puedes animar cualquier propiedad que el navegador considere animable. Esto incluye posiciones, tamaños, transformaciones, colores, bordes y más. El W3C mantiene una lista de propiedades animables en su página CSS Transitions.
Para propiedades con un valor numérico, define una unidad proporcionando el valor como una cadena, entre comillas, con el sufijo apropiado:
50 píxeles:
'50px'Tamaño de fuente relativo:
'3em'Porcentaje:
'100%'
También puedes proporcionar el valor como un número. En tales casos Angular asume una unidad predeterminada de píxeles, o px.
Expresar 50 píxeles como 50 es lo mismo que decir '50px'.
ÚTIL: La cadena "50" no sería considerada válida.
Cálculo automático de propiedades con comodines
A veces, el valor de una propiedad de estilo dimensional no se conoce hasta el tiempo de ejecución. Por ejemplo, los elementos a menudo tienen anchos y alturas que dependen de su contenido o del tamaño de la pantalla. Estas propiedades a menudo son desafiantes de animar usando CSS.
En estos casos, puedes usar un valor de propiedad comodín * especial bajo style(). El valor de esa propiedad de estilo particular se calcula en tiempo de ejecución y luego se conecta a la animación.
El siguiente ejemplo tiene un trigger llamado shrinkOut, usado cuando un elemento HTML sale de la página.
La animación toma cualquier altura que el elemento tenga antes de salir, y la anima desde esa altura hasta cero.
src/app/hero-list-auto.component.ts
import {Component, Output, EventEmitter, input} from '@angular/core';import {trigger, state, style, animate, transition} from '@angular/animations';import {Hero} from './hero';@Component({ selector: 'app-hero-list-auto', templateUrl: 'hero-list-auto.component.html', styleUrls: ['./hero-list-page.component.css'], animations: [ trigger('shrinkOut', [ state('in', style({height: '*'})), transition('* => void', [style({height: '*'}), animate(250, style({height: 0}))]), ]), ],})export class HeroListAutoComponent { readonly heroes = input<Hero[]>([]); @Output() remove = new EventEmitter<number>(); removeHero(id: number) { this.remove.emit(id); }}
Resumen de keyframes
La función keyframes() en Angular te permite especificar múltiples estilos intermedios dentro de una sola transición. Se puede usar un offset opcional para definir el punto en la animación donde debe ocurrir cada cambio de estilo.
Más sobre animaciones de Angular
También puede que te interese lo siguiente: