or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

animation-composition.mdindex.mdprogrammatic-control.mdreusable-animations.mdstyling-timing.mdtriggers.md
tile.json

reusable-animations.mddocs/

Reusable Animations

System for creating and using reusable animation definitions that can be shared across multiple components and customized with parameters.

Capabilities

Animation Function

Creates a reusable animation definition that can be used with useAnimation().

/**
 * Creates reusable animation for use with useAnimation()
 * @param steps - Animation steps or single animation metadata
 * @param options - Optional default parameters and timing
 * @returns AnimationReferenceMetadata for reuse across components
 */
function animation(
  steps: AnimationMetadata | AnimationMetadata[],
  options?: AnimationOptions | null
): AnimationReferenceMetadata;

interface AnimationReferenceMetadata extends AnimationMetadata {
  animation: AnimationMetadata | AnimationMetadata[];
  options: AnimationOptions | null;
}

Usage Examples:

import { animation, animate, style, keyframes } from '@angular/animations';

// Simple reusable animation
export const fadeInAnimation = animation([
  style({ opacity: 0 }),
  animate('{{ duration }}ms {{ easing }}', style({ opacity: 1 }))
], { params: { duration: 300, easing: 'ease-in' } });

// Complex reusable animation with keyframes
export const bounceInAnimation = animation([
  animate('{{ duration }}ms {{ easing }}', keyframes([
    style({ opacity: 0, transform: 'scale(0.3)', offset: 0 }),
    style({ opacity: 1, transform: 'scale(1.05)', offset: 0.5 }),
    style({ opacity: 1, transform: 'scale(0.9)', offset: 0.7 }),
    style({ opacity: 1, transform: 'scale(1)', offset: 1 })
  ]))
], { params: { duration: 600, easing: 'ease-out' } });

// Multi-step reusable animation
export const slideAndFadeAnimation = animation([
  style({ 
    opacity: 0, 
    transform: 'translateX({{ startX }}px)' 
  }),
  animate('{{ duration }}ms {{ easing }}', style({
    opacity: 1,
    transform: 'translateX(0)'
  }))
], { params: { startX: -100, duration: 400, easing: 'ease-out' } });

UseAnimation Function

Uses a reusable animation created with animation(), optionally overriding parameters.

/**
 * Uses a reusable animation with optional parameter overrides
 * @param animation - AnimationReferenceMetadata from animation() function
 * @param options - Optional parameter overrides and timing
 * @returns AnimationAnimateRefMetadata for using the reusable animation
 */
function useAnimation(
  animation: AnimationReferenceMetadata,
  options?: AnimationOptions | null
): AnimationAnimateRefMetadata;

interface AnimationAnimateRefMetadata extends AnimationMetadata {
  animation: AnimationReferenceMetadata;
  options: AnimationOptions | null;
}

Usage Examples:

import { trigger, transition, useAnimation } from '@angular/animations';
import { fadeInAnimation, bounceInAnimation, slideAndFadeAnimation } from './animations';

// Use animation with default parameters
trigger('fadeIn', [
  transition(':enter', [
    useAnimation(fadeInAnimation)
  ])
])

// Use animation with parameter overrides
trigger('customFade', [
  transition(':enter', [
    useAnimation(fadeInAnimation, {
      params: { duration: 500, easing: 'ease-in-out' }
    })
  ])
])

// Use animation with different parameters per transition
trigger('slideVariations', [
  transition('* => left', [
    useAnimation(slideAndFadeAnimation, {
      params: { startX: -200, duration: 300 }
    })
  ]),
  transition('* => right', [
    useAnimation(slideAndFadeAnimation, {
      params: { startX: 200, duration: 300 }
    })
  ]),
  transition('* => slow', [
    useAnimation(slideAndFadeAnimation, {
      params: { startX: -100, duration: 800, easing: 'ease-in-out' }
    })
  ])
])

AnimateChild Function

Runs child animations when parent animation executes, useful for coordinating nested animations.

/**
 * Runs child animations when parent animation executes
 * @param options - Optional duration and parameter configuration
 * @returns AnimationAnimateChildMetadata for child animation coordination
 */
function animateChild(
  options?: AnimateChildOptions | null
): AnimationAnimateChildMetadata;

interface AnimateChildOptions extends AnimationOptions {
  duration?: number | string;
}

interface AnimationAnimateChildMetadata extends AnimationMetadata {
  options: AnimateChildOptions | null;
}

Usage Examples:

import { trigger, transition, animateChild, group, query } from '@angular/animations';

// Basic child animation coordination
trigger('parentAnimation', [
  transition('* => *', [
    group([
      animate('300ms', style({ opacity: 0.5 })),  // Parent animation
      animateChild()  // Run any child animations simultaneously
    ])
  ])
])

// Child animation with custom duration
trigger('timedChildAnimation', [
  transition(':enter', [
    group([
      animate('500ms', style({ transform: 'scale(1.1)' })),
      animateChild({ duration: '300ms' })  // Child animations run for 300ms
    ])
  ])
])

// Query with animateChild for specific child elements
trigger('coordinatedAnimation', [
  transition('inactive => active', [
    group([
      // Parent element animation
      animate('400ms', style({ backgroundColor: 'lightblue' })),
      
      // Coordinate child animations
      query('.child-element', [
        animateChild({ duration: '400ms' })
      ], { optional: true })
    ])
  ])
])

Creating Animation Libraries

Shared Animation Definitions

// animations.ts - Shared animation library
import { animation, animate, style, keyframes, group, sequence } from '@angular/animations';

// Entrance animations
export const fadeIn = animation([
  style({ opacity: 0 }),
  animate('{{ duration }}ms {{ easing }}', style({ opacity: 1 }))
], { params: { duration: 300, easing: 'ease-in' } });

export const slideInLeft = animation([
  style({ transform: 'translateX(-100%)', opacity: 0 }),
  animate('{{ duration }}ms {{ easing }}', style({ 
    transform: 'translateX(0)', 
    opacity: 1 
  }))
], { params: { duration: 400, easing: 'ease-out' } });

export const bounceIn = animation([
  animate('{{ duration }}ms {{ easing }}', keyframes([
    style({ opacity: 0, transform: 'scale(0.3)', offset: 0 }),
    style({ opacity: 1, transform: 'scale(1.05)', offset: 0.5 }),
    style({ opacity: 1, transform: 'scale(0.9)', offset: 0.7 }),
    style({ opacity: 1, transform: 'scale(1)', offset: 1 })
  ]))
], { params: { duration: 600, easing: 'ease-out' } });

// Exit animations
export const fadeOut = animation([
  animate('{{ duration }}ms {{ easing }}', style({ opacity: 0 }))
], { params: { duration: 300, easing: 'ease-out' } });

export const slideOutRight = animation([
  animate('{{ duration }}ms {{ easing }}', style({ 
    transform: 'translateX(100%)', 
    opacity: 0 
  }))
], { params: { duration: 400, easing: 'ease-in' } });

// Complex animations
export const pulseAndScale = animation([
  sequence([
    animate('200ms', style({ transform: 'scale(1.1)' })),
    animate('200ms', style({ transform: 'scale(1)' })),
    animate('200ms', style({ transform: 'scale(1.1)' })),
    animate('200ms', style({ transform: 'scale(1)' }))
  ])
]);

export const flipCard = animation([
  group([
    sequence([
      animate('{{ duration }}ms ease-in', style({ 
        transform: 'rotateY(90deg)' 
      })),
      animate('{{ duration }}ms ease-out', style({ 
        transform: 'rotateY(0deg)' 
      }))
    ])
  ])
], { params: { duration: 300 } });

Using Animation Library

// component.ts - Using shared animations
import { Component } from '@angular/core';
import { trigger, transition, useAnimation } from '@angular/animations';
import { 
  fadeIn, 
  fadeOut, 
  slideInLeft, 
  slideOutRight, 
  bounceIn,
  pulseAndScale 
} from './animations';

@Component({
  selector: 'app-card',
  template: `
    <div [@cardAnimation]="state" class="card">
      <h3>{{ title }}</h3>
      <p>{{ content }}</p>
    </div>
  `,
  animations: [
    trigger('cardAnimation', [
      // Different entrance animations
      transition('void => fadeIn', [
        useAnimation(fadeIn)
      ]),
      transition('void => slideIn', [
        useAnimation(slideInLeft, {
          params: { duration: 500, easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)' }
        })
      ]),
      transition('void => bounceIn', [
        useAnimation(bounceIn)
      ]),
      
      // Exit animations
      transition('* => void', [
        useAnimation(fadeOut, {
          params: { duration: 200 }
        })
      ]),
      
      // Interaction animations
      transition('idle => pulse', [
        useAnimation(pulseAndScale)
      ])
    ])
  ]
})
export class CardComponent {
  state = 'fadeIn';
  title = 'Card Title';
  content = 'Card content...';
  
  triggerPulse() {
    this.state = 'pulse';
    setTimeout(() => this.state = 'idle', 1000);
  }
}

Advanced Reusable Patterns

Parameterized Animation Factory

// animation-factory.ts
import { animation, animate, style, sequence, group } from '@angular/animations';

export function createSlideAnimation(direction: 'left' | 'right' | 'up' | 'down') {
  const transforms = {
    left: 'translateX(-100%)',
    right: 'translateX(100%)',
    up: 'translateY(-100%)',
    down: 'translateY(100%)'
  };
  
  return animation([
    style({ 
      transform: transforms[direction], 
      opacity: 0 
    }),
    animate('{{ duration }}ms {{ easing }}', style({
      transform: 'translate(0, 0)',
      opacity: 1
    }))
  ], { params: { duration: 400, easing: 'ease-out' } });
}

export function createScaleAnimation(fromScale: number = 0, toScale: number = 1) {
  return animation([
    style({ 
      transform: `scale(${fromScale})`, 
      opacity: 0 
    }),
    animate('{{ duration }}ms {{ easing }}', style({
      transform: `scale(${toScale})`,
      opacity: 1
    }))
  ], { params: { duration: 300, easing: 'ease-out' } });
}

// Usage
const slideLeft = createSlideAnimation('left');
const slideUp = createSlideAnimation('up');
const scaleIn = createScaleAnimation(0.5, 1);
const scaleOut = createScaleAnimation(1, 0.8);

Animation with Conditional Steps

export function createConditionalAnimation(includeRotation: boolean = false) {
  const baseSteps = [
    style({ opacity: 0, transform: 'translateY(20px)' }),
    animate('300ms ease-out', style({ opacity: 1, transform: 'translateY(0)' }))
  ];
  
  const rotationStep = animate('200ms', style({ transform: 'rotate(360deg)' }));
  
  return animation(
    includeRotation ? [...baseSteps, rotationStep] : baseSteps
  );
}

Animation Composition with Child Coordination

export const parentChildAnimation = animation([
  group([
    // Parent animation
    sequence([
      style({ opacity: 0, transform: 'scale(0.8)' }),
      animate('300ms ease-out', style({ opacity: 1, transform: 'scale(1)' }))
    ]),
    
    // Coordinate children
    animateChild({ duration: '500ms' })
  ])
]);

// Usage in component
trigger('coordinated', [
  transition(':enter', [
    useAnimation(parentChildAnimation)
  ])
])

Best Practices

Parameter Naming

// Good: Descriptive parameter names
animation([
  animate('{{ enterDuration }}ms {{ enterEasing }}', 
    style({ opacity: '{{ targetOpacity }}' })
  )
], { 
  params: { 
    enterDuration: 300, 
    enterEasing: 'ease-in',
    targetOpacity: 1
  } 
});

// Avoid: Generic parameter names
animation([
  animate('{{ time }}ms {{ curve }}', style({ opacity: '{{ value }}' }))
], { params: { time: 300, curve: 'ease-in', value: 1 } });

Default Parameters

// Always provide sensible defaults
export const flexibleAnimation = animation([
  style({ 
    opacity: '{{ startOpacity }}',
    transform: 'translateX({{ startX }}px) scale({{ startScale }})'
  }),
  animate('{{ duration }}ms {{ easing }}', style({
    opacity: '{{ endOpacity }}',
    transform: 'translateX({{ endX }}px) scale({{ endScale }})'
  }))
], {
  params: {
    startOpacity: 0,
    endOpacity: 1,
    startX: -50,
    endX: 0,
    startScale: 0.9,
    endScale: 1,
    duration: 400,
    easing: 'ease-out'
  }
});

Animation Organization

// Group related animations together
export const cardAnimations = {
  enter: {
    fade: fadeIn,
    slide: slideInLeft,
    bounce: bounceIn
  },
  exit: {
    fade: fadeOut,
    slide: slideOutRight
  },
  interactions: {
    pulse: pulseAndScale,
    flip: flipCard
  }
};

// Usage
useAnimation(cardAnimations.enter.fade)
useAnimation(cardAnimations.interactions.pulse)