Animation (Comprehensive Guide)
A comprehensive developer guide for implementing animations in Aurelia applications, covering CSS animations, Web Animations API, lifecycle hooks, third-party libraries, and advanced animation pattern
Master animation techniques in Aurelia applications, from simple CSS transitions to complex coordinated animations using lifecycle hooks, modern Web APIs, and third-party libraries.
Overview
Aurelia provides multiple approaches for implementing animations, each suited to different use cases:
CSS Transitions
Simple state changes, hover effects
Low
Excellent
CSS Animations
Loading spinners, attention-grabbers
Low
Excellent
Web Animations API
Programmatic control, complex sequences
Medium
Excellent
Lifecycle Hooks
Component mount/unmount, route transitions
Medium
Excellent
Third-party Libraries
Advanced effects, timelines, physics
High
Good
When to Use What
Choose CSS Transitions When:
Animating between two states (expanded/collapsed, visible/hidden)
Creating hover effects or focus states
You need the best performance with minimal code
Choose CSS Keyframe Animations When:
Creating looping animations (spinners, pulsing effects)
Needing multi-step animations that don't respond to state
Building simple attention-grabbing effects
Choose Web Animations API When:
You need JavaScript control over CSS-quality animations
Building interactive animations that respond to user input
Coordinating multiple animations programmatically
Animations need to be paused, reversed, or dynamically adjusted
Choose Lifecycle Hooks When:
Animating component entrance/exit
Coordinating animations with component lifecycle
Creating route transition effects
Animations depend on DOM measurements
Choose Third-party Libraries When:
You need advanced easing functions or physics
Building complex animation timelines
Implementing scroll-triggered animations
Need SVG morphing or path animations
CSS-Based Animations
Simple State Transitions
CSS transitions are perfect for smooth state changes:
export class ToggleCard {
private isExpanded = false;
toggle() {
this.isExpanded = !this.isExpanded;
}
}.card {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
max-height: 100px;
overflow: hidden;
background: #f5f5f5;
padding: 20px;
border-radius: 8px;
cursor: pointer;
}
.card.expanded {
max-height: 500px;
background: #e3f2fd;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.card-content {
transition: opacity 0.2s ease-in-out;
opacity: 0;
}
.card.expanded .card-content {
opacity: 1;
transition-delay: 0.1s;
}<div class="card" class.bind="isExpanded ? 'expanded' : ''" click.trigger="toggle()">
<h3>Click to ${isExpanded ? 'collapse' : 'expand'}</h3>
<div class="card-content">
<p>This content animates in when the card expands.</p>
</div>
</div>Multi-Step Keyframe Animations
For more complex animations, use @keyframes:
export class AttentionButton {
private isAnimating = false;
animateButton() {
this.isAnimating = true;
setTimeout(() => {
this.isAnimating = false;
}, 2000);
}
}@keyframes wiggle {
0%, 7% { transform: rotateZ(0); }
15% { transform: rotateZ(-15deg); }
20% { transform: rotateZ(10deg); }
25% { transform: rotateZ(-10deg); }
30% { transform: rotateZ(6deg); }
35% { transform: rotateZ(-4deg); }
40%, 100% { transform: rotateZ(0); }
}
.wiggle {
animation: wiggle 2s linear 1;
}<button
type="button"
class.bind="isAnimating ? 'wiggle' : ''"
disabled.bind="isAnimating"
click.trigger="animateButton()">
${isAnimating ? 'Wiggling...' : 'Click to Wiggle!'}
</button>Staggered List Animations
Animate list items with a stagger effect using CSS custom properties:
export class StaggeredList {
private items = [
'First item',
'Second item',
'Third item',
'Fourth item',
'Fifth item'
];
}<ul class="staggered-list">
<li repeat.for="item of items" class="list-item" css="--index: ${$index}">
${item}
</li>
</ul>.list-item {
opacity: 0;
animation: fadeInSlide 0.5s ease-out forwards;
animation-delay: calc(var(--index) * 0.1s);
}
@keyframes fadeInSlide {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}Web Animations API
The Web Animations API provides programmatic control over animations with excellent performance.
Basic Web Animations
export class WebAnimationExample {
private element: HTMLElement;
created(controller) {
this.element = controller.host;
}
async animateElement(): Promise<void> {
const animation = this.element.animate([
{ transform: 'scale(1) rotate(0deg)', opacity: 1 },
{ transform: 'scale(1.2) rotate(180deg)', opacity: 0.7 },
{ transform: 'scale(1) rotate(360deg)', opacity: 1 }
], {
duration: 1000,
easing: 'ease-in-out'
});
await animation.finished;
console.log('Animation completed!');
}
}Interactive Animations
Create animations that respond to user input:
export class InteractiveAnimation {
private x = 0;
private y = 0;
mouseMove(event: MouseEvent) {
this.x = event.clientX;
this.y = event.clientY;
}
}<div
mousemove.trigger="mouseMove($event)"
style="background-color: hsl(${x / 3}, 60%, 45%)"
class="interactive-area">
<h3>Interactive Color Animation</h3>
<p>Move your mouse to change the background color!</p>
<p>X: ${x}, Y: ${y}</p>
</div>.interactive-area {
padding: 40px;
transition: 0.3s background-color ease-in-out;
border-radius: 8px;
cursor: crosshair;
min-height: 200px;
color: white;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
}Controllable Animations
Create animations you can pause, play, or reverse:
export class ControllableAnimation {
private element: HTMLElement;
private animation: Animation | null = null;
created(controller) {
this.element = controller.host;
}
private createAnimation(): Animation {
return this.element.animate([
{ transform: 'translateX(0)' },
{ transform: 'translateX(300px)' }
], {
duration: 2000,
easing: 'ease-in-out',
fill: 'both'
});
}
play() {
if (!this.animation) {
this.animation = this.createAnimation();
}
this.animation.play();
}
pause() {
this.animation?.pause();
}
reverse() {
if (this.animation) {
this.animation.reverse();
}
}
reset() {
this.animation?.cancel();
this.animation = null;
}
}<div ref="element" class="animated-box">Animated Box</div>
<div class="controls">
<button click.trigger="play()">Play</button>
<button click.trigger="pause()">Pause</button>
<button click.trigger="reverse()">Reverse</button>
<button click.trigger="reset()">Reset</button>
</div>Coordinated Animations
Run multiple animations simultaneously:
export class CoordinatedAnimations {
private element: HTMLElement;
created(controller) {
this.element = controller.host;
}
async animateWithCoordination(): Promise<void> {
const background = this.element.animate([
{ backgroundColor: '#2196F3' },
{ backgroundColor: '#4CAF50' }
], { duration: 1000, fill: 'forwards' });
const scale = this.element.animate([
{ transform: 'scale(1)' },
{ transform: 'scale(1.2)' },
{ transform: 'scale(1)' }
], { duration: 1000 });
// Wait for both to complete
await Promise.all([background.finished, scale.finished]);
}
}Component Lifecycle Animations
Animate components during their attachment and detachment using lifecycle hooks.
Fade In/Out Animation
export class FadeCard {
private element: HTMLElement;
created(controller) {
this.element = controller.host;
}
attaching(): Promise<void> {
this.element.style.opacity = '0';
const animation = this.element.animate([
{ opacity: 0, transform: 'translateY(20px)' },
{ opacity: 1, transform: 'translateY(0)' }
], {
duration: 400,
easing: 'ease-out',
fill: 'forwards'
});
return animation.finished;
}
detaching(): Promise<void> {
const animation = this.element.animate([
{ opacity: 1, transform: 'translateY(0)' },
{ opacity: 0, transform: 'translateY(-20px)' }
], {
duration: 300,
easing: 'ease-in',
fill: 'forwards'
});
return animation.finished;
}
}Reusable Animation Hooks
Create reusable animation controllers with @lifecycleHooks():
import { lifecycleHooks } from '@aurelia/runtime-html';
@lifecycleHooks()
export class FadeAnimationHooks {
private element: HTMLElement;
created(vm, controller): void {
this.element = controller.host;
}
attaching(vm): Promise<void> {
return this.element.animate([
{ opacity: 0, transform: 'scale(0.8)' },
{ opacity: 1, transform: 'scale(1)' }
], {
duration: 300,
easing: 'ease-out',
fill: 'forwards'
}).finished;
}
detaching(vm): Promise<void> {
return this.element.animate([
{ opacity: 1, transform: 'scale(1)' },
{ opacity: 0, transform: 'scale(0.8)' }
], {
duration: 200,
easing: 'ease-in',
fill: 'forwards'
}).finished;
}
}Register globally in your app configuration:
import Aurelia from 'aurelia';
import { FadeAnimationHooks } from './fade-animation-hooks';
Aurelia
.register(FadeAnimationHooks)
.app(MyApp)
.start();Or use per-component:
export class MyComponent {
static dependencies = [FadeAnimationHooks];
}Animating Conditional Content
Animate elements controlled by if.bind:
@lifecycleHooks()
export class ConditionalAnimationHooks {
private element: HTMLElement;
created(vm, controller): void {
this.element = controller.host;
}
attaching(vm): Promise<void> {
this.element.style.opacity = '0';
return this.element.animate([
{ opacity: 0, transform: 'translateY(-10px)' },
{ opacity: 1, transform: 'translateY(0)' }
], {
duration: 250,
easing: 'ease-out',
fill: 'forwards'
}).finished;
}
detaching(vm): Promise<void> {
return this.element.animate([
{ opacity: 1, transform: 'translateY(0)' },
{ opacity: 0, transform: 'translateY(-10px)' }
], {
duration: 200,
easing: 'ease-in',
fill: 'forwards'
}).finished;
}
}<div if.bind="showContent">Content that animates in/out</div>Animating List Items
Animate individual items in a repeat.for:
export class ListItem {
private element: HTMLElement;
created(controller) {
this.element = controller.host;
}
attaching(): Promise<void> {
return this.element.animate([
{ opacity: 0, transform: 'translateX(-20px) scale(0.8)' },
{ opacity: 1, transform: 'translateX(0) scale(1)' }
], {
duration: 300,
easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
fill: 'forwards'
}).finished;
}
detaching(): Promise<void> {
return this.element.animate([
{ opacity: 1, transform: 'translateX(0) scale(1)' },
{ opacity: 0, transform: 'translateX(20px) scale(0.8)' }
], {
duration: 250,
easing: 'ease-in',
fill: 'forwards'
}).finished;
}
}<list-item repeat.for="item of items" model.bind="item"></list-item>Router Transition Animations
Animate page transitions using router lifecycle hooks. These techniques work with both @aurelia/router (recommended) and router-direct. See the dedicated Router Animation Guide for comprehensive coverage.
Quick Router Animation Example
import { lifecycleHooks } from '@aurelia/runtime-html';
@lifecycleHooks()
export class SlideAnimationHooks {
private element: HTMLElement;
private backwards = false;
created(vm, controller): void {
this.element = controller.host;
}
loading(vm, _params, _instruction, navigation) {
this.backwards = navigation.navigation.back;
}
unloading(vm, _instruction, navigation) {
this.backwards = navigation.navigation.back;
}
attaching(vm): Promise<void> {
return this.slideIn(this.element, this.backwards ? 'left' : 'right');
}
detaching(vm): Promise<void> {
return this.slideOut(this.element, this.backwards ? 'right' : 'left');
}
private slideIn(element: HTMLElement, from: 'left' | 'right'): Promise<void> {
const animation = element.animate([
{ transform: `translateX(${from === 'left' ? '-' : ''}100%)` },
{ transform: 'translateX(0)' }
], { duration: 300, easing: 'ease-out', fill: 'forwards' });
return animation.finished;
}
private slideOut(element: HTMLElement, to: 'left' | 'right'): Promise<void> {
const animation = element.animate([
{ transform: 'translateX(0)' },
{ transform: `translateX(${to === 'left' ? '-' : ''}100%)` }
], { duration: 300, easing: 'ease-in', fill: 'forwards' });
return animation.finished;
}
}View Transitions API
The modern View Transitions API provides smooth, cross-fade transitions between DOM states with minimal code.
Basic View Transitions
export class ViewTransitionExample {
private items = ['Item 1', 'Item 2', 'Item 3'];
async updateWithTransition() {
// Check for browser support
if (!document.startViewTransition) {
// Fallback for browsers without support
this.items.push(`Item ${this.items.length + 1}`);
return;
}
// Start a view transition
const transition = document.startViewTransition(() => {
this.items.push(`Item ${this.items.length + 1}`);
});
await transition.finished;
}
}Customizing View Transitions
Control the transition with CSS:
/* Customize the transition animation */
::view-transition-old(root) {
animation: fade-out 0.3s ease-out;
}
::view-transition-new(root) {
animation: fade-in 0.3s ease-in;
}
@keyframes fade-out {
to { opacity: 0; }
}
@keyframes fade-in {
from { opacity: 0; }
}Named View Transitions
Transition specific elements independently:
export class NamedTransitionExample {
private selectedId: number | null = null;
async selectItem(id: number) {
if (!document.startViewTransition) {
this.selectedId = id;
return;
}
await document.startViewTransition(() => {
this.selectedId = id;
}).finished;
}
}<div repeat.for="item of items" click.trigger="selectItem(item.id)">
<div css="view-transition-name: item-${item.id}">
${item.name}
</div>
</div>
<div if.bind="selectedId" css="view-transition-name: item-${selectedId}">
<h2>Selected Item Details</h2>
</div>/* Animate the selected item's transition */
::view-transition-old(item-*),
::view-transition-new(item-*) {
animation-duration: 0.5s;
}Third-Party Animation Libraries
Aurelia works seamlessly with popular animation libraries.
Anime.js Integration
Anime.js provides powerful, lightweight animations:
import anime from 'animejs';
export class AnimeExample {
private currentValue = 0;
private displayValue = 0;
private valueWrapper: HTMLElement;
animateToValue(newValue: number) {
this.currentValue = newValue;
anime({
targets: this,
displayValue: newValue,
easing: 'easeInOutQuart',
round: true,
duration: 1200,
update: () => {
this.valueWrapper.textContent = this.displayValue.toLocaleString();
}
});
}
increment() {
this.animateToValue(this.currentValue + Math.floor(Math.random() * 10000));
}
reset() {
this.animateToValue(0);
}
}<div class="value-animator">
<h3>Animated Counter</h3>
<div ref="valueWrapper" class="display-value">${displayValue & oneTime}</div>
<div class="controls">
<button click.trigger="increment()">Add Random Amount</button>
<button click.trigger="reset()">Reset</button>
</div>
</div>GSAP Integration
GSAP provides professional-grade animation capabilities:
import { gsap } from 'gsap';
export class GSAPExample {
private timeline: GSAPTimeline;
attached() {
this.timeline = gsap.timeline({ paused: true });
this.timeline
.from('.card', { y: 50, opacity: 0, duration: 0.5 })
.from('.card h3', { x: -30, opacity: 0, duration: 0.3 }, '-=0.2')
.from('.card p', { y: 20, opacity: 0, stagger: 0.1, duration: 0.3 }, '-=0.1');
}
playAnimation() {
this.timeline.play();
}
reverseAnimation() {
this.timeline.reverse();
}
disposing() {
this.timeline?.kill();
}
}Stagger Animations with GSAP
Create beautiful staggered animations:
import { gsap } from 'gsap';
export class StaggerExample {
private items = Array.from({ length: 10 }, (_, i) => `Item ${i + 1}`);
attached() {
gsap.from('.list-item', {
opacity: 0,
y: 30,
stagger: 0.1,
duration: 0.5,
ease: 'power2.out'
});
}
}<ul>
<li repeat.for="item of items" class="list-item">${item}</li>
</ul>Advanced Patterns
Reusable Animation Composer
Create a library of reusable animations:
export class AnimationComposer {
static fadeIn(element: Element, duration = 300): Promise<void> {
return element.animate([
{ opacity: 0 },
{ opacity: 1 }
], { duration, easing: 'ease-out', fill: 'forwards' }).finished;
}
static fadeOut(element: Element, duration = 300): Promise<void> {
return element.animate([
{ opacity: 1 },
{ opacity: 0 }
], { duration, easing: 'ease-in', fill: 'forwards' }).finished;
}
static slideUp(element: Element, duration = 300): Promise<void> {
return element.animate([
{ transform: 'translateY(20px)' },
{ transform: 'translateY(0)' }
], { duration, easing: 'ease-out', fill: 'forwards' }).finished;
}
static slideDown(element: Element, duration = 300): Promise<void> {
return element.animate([
{ transform: 'translateY(0)' },
{ transform: 'translateY(20px)' }
], { duration, easing: 'ease-in', fill: 'forwards' }).finished;
}
static async fadeInAndSlideUp(element: Element): Promise<void> {
await Promise.all([
this.fadeIn(element),
this.slideUp(element)
]);
}
static async fadeOutAndSlideDown(element: Element): Promise<void> {
await Promise.all([
this.fadeOut(element),
this.slideDown(element)
]);
}
}Usage in components:
import { AnimationComposer } from './animation-composer';
export class MyComponent {
private element: HTMLElement;
created(controller) {
this.element = controller.host;
}
attaching(): Promise<void> {
return AnimationComposer.fadeInAndSlideUp(this.element);
}
detaching(): Promise<void> {
return AnimationComposer.fadeOutAndSlideDown(this.element);
}
}Sequential Animations
Chain animations together in sequence:
export class SequenceAnimation {
async playSequence(elements: Element[]): Promise<void> {
for (const [index, element] of elements.entries()) {
await element.animate([
{ opacity: 0, transform: 'translateX(-20px)' },
{ opacity: 1, transform: 'translateX(0)' }
], {
duration: 200,
delay: index * 100,
easing: 'ease-out',
fill: 'forwards'
}).finished;
}
}
}Parallel Animation Groups
Run groups of animations simultaneously:
export class ParallelAnimations {
async animateGroup(elements: Element[]): Promise<void> {
const animations = elements.map(element =>
element.animate([
{ opacity: 0, transform: 'scale(0.8)' },
{ opacity: 1, transform: 'scale(1)' }
], {
duration: 300,
easing: 'ease-out',
fill: 'forwards'
}).finished
);
await Promise.all(animations);
}
}Scroll-Triggered Animations
Animate elements as they enter the viewport:
export class ScrollAnimation {
private observer: IntersectionObserver;
private elements: Element[] = [];
attached() {
this.observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.animateElement(entry.target);
this.observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1 }
);
this.elements.forEach(el => this.observer.observe(el));
}
private animateElement(element: Element): void {
element.animate([
{ opacity: 0, transform: 'translateY(50px)' },
{ opacity: 1, transform: 'translateY(0)' }
], {
duration: 600,
easing: 'ease-out',
fill: 'forwards'
});
}
detaching() {
this.observer?.disconnect();
}
}Performance Optimization
Use GPU-Accelerated Properties
Always prefer properties that can be animated on the GPU:
/* Good - GPU accelerated */
.animated {
transform: translateX(100px) scale(1.2);
opacity: 0.5;
}
/* Avoid - triggers layout recalculation */
.animated {
left: 100px;
width: 200px;
margin-top: 50px;
}Hint the Browser with will-change
will-changeFor complex animations, hint what will change:
.complex-animation {
will-change: transform, opacity;
}
/* Remove will-change after animation completes */
.complex-animation.done {
will-change: auto;
}Clean Up Animations
Always cancel animations when components are destroyed:
export class AnimatedComponent {
private activeAnimations: Animation[] = [];
startAnimation() {
const animation = this.element.animate(/* ... */);
this.activeAnimations.push(animation);
}
disposing() {
this.activeAnimations.forEach(animation => animation.cancel());
this.activeAnimations = [];
}
}Batch DOM Operations
Minimize layout thrashing by batching DOM reads and writes:
export class BatchedAnimation {
async animateElements(elements: Element[]): Promise<void> {
// Read phase - batch all measurements
const positions = elements.map(el => ({
element: el,
rect: el.getBoundingClientRect()
}));
// Write phase - batch all animations
const animations = positions.map(({ element, rect }) =>
element.animate([
{ transform: `translateY(${rect.height}px)` },
{ transform: 'translateY(0)' }
], { duration: 300 }).finished
);
await Promise.all(animations);
}
}Accessibility Considerations
Respect prefers-reduced-motion
prefers-reduced-motionAlways respect user preferences for reduced motion:
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}In JavaScript:
export class AccessibleAnimation {
private shouldAnimate(): boolean {
return !window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}
animateIfAllowed(element: Element): Promise<void> | void {
if (!this.shouldAnimate()) {
return Promise.resolve();
}
const animation = element.animate([
{ opacity: 0 },
{ opacity: 1 }
], { duration: 300 });
return animation.finished;
}
}Maintain Focus Management
Ensure focus remains intuitive during animations:
export class FocusAwareAnimation {
async animateAndFocus(element: HTMLElement): Promise<void> {
await element.animate([
{ opacity: 0, transform: 'scale(0.8)' },
{ opacity: 1, transform: 'scale(1)' }
], { duration: 200 }).finished;
// Focus after animation completes
if (element.tabIndex >= 0 || element.matches('button, a, input, select, textarea')) {
element.focus();
}
}
}Provide Skip Options
For long or complex animations, provide a way to skip:
export class SkippableAnimation {
private skipRequested = false;
async playAnimation(): Promise<void> {
const animation = this.element.animate(/* ... */, { duration: 2000 });
// Allow skipping
const skipHandler = () => {
this.skipRequested = true;
animation.finish();
};
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') skipHandler();
}, { once: true });
await animation.finished;
}
}Lifecycle Hook Reference
Understanding lifecycle hooks is crucial for timing animations correctly:
created
After construction
Element reference setup
No
binding
Before data binding
Pre-animation state setup
No
bound
After data binding
Data-dependent setup
No
attaching
Before DOM insertion
Enter animations
Yes
attached
After DOM insertion
Animations needing measurements
No
detaching
Before DOM removal
Exit animations
Yes
unbinding
Before unbinding
Cleanup
No
disposing
Before disposal
Cancel active animations
No
Best Practices for Lifecycle Animations
Always return promises from
attachinganddetaching:attaching(): Promise<void> { return this.element.animate(/* ... */).finished; }Handle interruptions - Cancel animations if detached early:
export class AnimatedComponent { private currentAnimation: Animation | null = null; attaching(): Promise<void> { this.currentAnimation = this.element.animate(/* ... */); return this.currentAnimation.finished; } detaching(): Promise<void> { this.currentAnimation?.cancel(); this.currentAnimation = this.element.animate(/* ... */); return this.currentAnimation.finished; } }Coordinate complex animations:
attaching(): Promise<void> { return Promise.all([ this.animateBackground(), this.animateContent(), this.animateControls() ]).then(() => void 0); }
Real-World Examples
Notification Toast
export class ToastNotification {
private element: HTMLElement;
private message = '';
private visible = false;
created(controller) {
this.element = controller.host;
}
async show(message: string, duration = 3000): Promise<void> {
this.message = message;
this.visible = true;
// Slide in
await this.element.animate([
{ transform: 'translateY(-100%)', opacity: 0 },
{ transform: 'translateY(0)', opacity: 1 }
], { duration: 300, easing: 'ease-out', fill: 'forwards' }).finished;
// Wait
await new Promise(resolve => setTimeout(resolve, duration));
// Slide out
await this.element.animate([
{ transform: 'translateY(0)', opacity: 1 },
{ transform: 'translateY(-100%)', opacity: 0 }
], { duration: 200, easing: 'ease-in', fill: 'forwards' }).finished;
this.visible = false;
}
}Modal Dialog
export class ModalDialog {
private backdrop: HTMLElement;
private dialog: HTMLElement;
private isOpen = false;
async open(): Promise<void> {
this.isOpen = true;
// Animate backdrop and dialog in parallel
await Promise.all([
this.backdrop.animate([
{ opacity: 0 },
{ opacity: 1 }
], { duration: 200, fill: 'forwards' }).finished,
this.dialog.animate([
{ opacity: 0, transform: 'scale(0.7) translateY(-50px)' },
{ opacity: 1, transform: 'scale(1) translateY(0)' }
], { duration: 300, easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)', fill: 'forwards' }).finished
]);
}
async close(): Promise<void> {
await Promise.all([
this.backdrop.animate([
{ opacity: 1 },
{ opacity: 0 }
], { duration: 200, fill: 'forwards' }).finished,
this.dialog.animate([
{ opacity: 1, transform: 'scale(1) translateY(0)' },
{ opacity: 0, transform: 'scale(0.7) translateY(-50px)' }
], { duration: 200, easing: 'ease-in', fill: 'forwards' }).finished
]);
this.isOpen = false;
}
}Image Gallery Transition
export class ImageGallery {
private images = ['img1.jpg', 'img2.jpg', 'img3.jpg'];
private currentIndex = 0;
private imageElement: HTMLElement;
async nextImage(): Promise<void> {
const nextIndex = (this.currentIndex + 1) % this.images.length;
// Fade out current
await this.imageElement.animate([
{ opacity: 1 },
{ opacity: 0 }
], { duration: 200, easing: 'ease-in', fill: 'forwards' }).finished;
// Change image
this.currentIndex = nextIndex;
// Fade in next
await this.imageElement.animate([
{ opacity: 0 },
{ opacity: 1 }
], { duration: 200, easing: 'ease-out', fill: 'forwards' }).finished;
}
}Summary
Aurelia provides a flexible animation system that works with:
CSS for simple, performant animations
Web Animations API for programmatic control
Lifecycle hooks for component and route transitions
Third-party libraries for advanced effects
Choose the right tool for your needs, prioritize performance, and always consider accessibility. For router-specific animations (works with both @aurelia/router and router-direct), see the Router Animation Guide.
Last updated
Was this helpful?