or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

accessibility.mdanimation.mdannotation.mdar.mdcontrols.mdenvironment.mdindex.mdloading.mdscene-graph.md
tile.json

animation.mddocs/

Animation System

Control 3D model animations with advanced playback options including crossfading, blending, and complex animation orchestration.

Capabilities

Animation Playback Control

Basic animation playback and state management.

/**
 * Automatically start playing animation when model loads
 */
autoplay: boolean;

/**
 * Name of the animation to play (from availableAnimations)
 * Set to undefined to stop current animation
 */
animationName: string | undefined;

/**
 * Get list of all animations available in the loaded model
 */
readonly availableAnimations: Array<string>;

/**
 * Whether animation playback is currently paused
 */
readonly paused: boolean;

/**
 * Current playback time position in seconds
 */
currentTime: number;

/**
 * Playback speed multiplier (1.0 = normal speed, 2.0 = double speed)
 */
timeScale: number;

/**
 * Total duration of current animation in seconds
 */
readonly duration: number;

/**
 * List of currently appended/layered animations
 */
readonly appendedAnimations: string[];

Usage:

<!-- Auto-play named animation -->
<model-viewer 
  src="models/robot.glb"
  animation-name="walking"
  autoplay>
</model-viewer>
// Check available animations
console.log('Available animations:', modelViewer.availableAnimations);

// Control playback
modelViewer.animationName = "running";
modelViewer.timeScale = 1.5; // 1.5x speed
modelViewer.currentTime = 2.0; // Start at 2 seconds

Play and Pause Controls

Direct control over animation playback.

/**
 * Start playing current animation with optional parameters
 * @param options - Playback configuration options
 */
play(options?: PlayAnimationOptions): void;

/**
 * Pause current animation playback
 */
pause(): void;

interface PlayAnimationOptions {
  /** Number of times to repeat animation (Infinity for loop) */
  repetitions?: number;
  /** Whether to play animation back and forth (ping-pong) */
  pingpong?: boolean;
}

Usage:

// Play once
modelViewer.play({ repetitions: 1 });

// Loop forever with ping-pong
modelViewer.play({ 
  repetitions: Infinity, 
  pingpong: true 
});

// Pause playback
modelViewer.pause();

Animation Crossfading

Smooth transitions between different animations.

/**
 * Duration in milliseconds for crossfading between animations
 * When switching animations, they will blend smoothly over this period
 */
animationCrossfadeDuration: number;

Usage:

<!-- Smooth 500ms transitions between animations -->
<model-viewer 
  src="models/character.glb"
  animation-crossfade-duration="500">
</model-viewer>
// Switch animations with crossfade
modelViewer.animationCrossfadeDuration = 300;
modelViewer.animationName = "idle";

// Later switch to running with smooth transition
setTimeout(() => {
  modelViewer.animationName = "running";
}, 2000);

Advanced Animation Blending

Layer multiple animations with different weights and timing.

/**
 * Append an animation to the current animation with blending options
 * @param animationName - Name of animation to append
 * @param options - Blending and timing options
 */
appendAnimation(animationName: string, options?: AppendAnimationOptions): void;

/**
 * Remove an appended animation with optional fade out
 * @param animationName - Name of animation to detach
 * @param options - Detachment options
 */
detachAnimation(animationName: string, options?: DetachAnimationOptions): void;

interface AppendAnimationOptions {
  /** Play animation back and forth */
  pingpong?: boolean;
  /** Number of repetitions (null for infinite) */
  repetitions?: number | null;
  /** Blend weight (0-1, higher = more influence) */
  weight?: number;
  /** Playback speed multiplier for this animation */
  timeScale?: number;
  /** Fade in duration (boolean for default, number for custom ms) */
  fade?: boolean | number;
  /** Time warping for synchronization (boolean or custom ms) */
  warp?: boolean | number;
  /** Whether warp timing is relative to other animations */
  relativeWarp?: boolean;
  /** Start time offset in seconds */
  time?: number | null;
}

interface DetachAnimationOptions {
  /** Fade out duration (boolean for default, number for custom ms) */
  fade?: boolean | number;
}

Usage:

// Start base idle animation
modelViewer.animationName = "idle";
modelViewer.play();

// Layer breathing animation at 50% weight
modelViewer.appendAnimation("breathing", {
  weight: 0.5,
  repetitions: null, // infinite
  fade: true
});

// Add blinking with custom timing
modelViewer.appendAnimation("blinking", {
  weight: 0.3,
  timeScale: 2.0, // Blink twice as fast
  fade: 200 // 200ms fade in
});

// Remove breathing animation later
setTimeout(() => {
  modelViewer.detachAnimation("breathing", { fade: 500 });
}, 10000);

Animation Events

Model Viewer dispatches events during animation playback for synchronization and state management.

Loop Events

Fired when an animation completes a loop iteration.

interface AnimationLoopEvent extends Event {
  type: 'loop';
  detail: {
    animationName: string;
    loopCount: number;
  };
}

Finish Events

Fired when an animation completes entirely (not fired for infinite loops).

interface AnimationFinishEvent extends Event {
  type: 'finished';
  detail: {
    animationName: string;
    totalLoops: number;
  };
}

Usage:

// Track animation loops
modelViewer.addEventListener('loop', (event) => {
  console.log(`${event.detail.animationName} completed loop ${event.detail.loopCount}`);
});

// Handle animation completion
modelViewer.addEventListener('finished', (event) => {
  console.log(`Animation ${event.detail.animationName} finished after ${event.detail.totalLoops} loops`);
  
  // Start next animation in sequence  
  if (event.detail.animationName === "intro") {
    modelViewer.animationName = "idle";
    modelViewer.play();
  }
});

Advanced Usage Examples

Animation Sequences

Create complex animation sequences with timing control:

class AnimationSequence {
  constructor(modelViewer) {
    this.modelViewer = modelViewer;
    this.sequence = [];
    this.currentIndex = 0;
  }
  
  add(animationName, duration, options = {}) {
    this.sequence.push({ animationName, duration, options });
    return this;
  }
  
  async play() {
    for (const step of this.sequence) {
      this.modelViewer.animationName = step.animationName;
      this.modelViewer.play(step.options);
      
      await new Promise(resolve => {
        setTimeout(resolve, step.duration);
      });
    }
  }
}

// Usage
const sequence = new AnimationSequence(modelViewer)
  .add("intro", 3000, { repetitions: 1 })
  .add("idle", 5000, { repetitions: 2 })
  .add("wave", 2000, { repetitions: 1 });

sequence.play();

Interactive Animation Triggers

Trigger animations based on user interaction:

// Animation state machine
const animationStates = {
  idle: { 
    animation: "idle",
    options: { repetitions: Infinity }
  },
  excited: {
    animation: "jump",
    options: { repetitions: 3, pingpong: false }
  },
  thinking: {
    animation: "scratch_head", 
    options: { repetitions: 1 }
  }
};

let currentState = "idle";

function setState(newState) {
  if (animationStates[newState]) {
    currentState = newState;
    const state = animationStates[newState];
    modelViewer.animationName = state.animation;
    modelViewer.play(state.options);
  }
}

// Button controls
document.getElementById('excite-btn').addEventListener('click', () => {
  setState('excited');
});

// Return to idle after non-looping animations
modelViewer.addEventListener('finished', (event) => {
  if (currentState !== 'idle') {
    setState('idle');
  }
});

Synchronized Multiple Models

Synchronize animations across multiple model viewers:

class AnimationSync {
  constructor(modelViewers) {
    this.viewers = modelViewers;
    this.syncTime = 0;
    this.isPlaying = false;
  }
  
  play(animationName, options = {}) {
    this.syncTime = performance.now();
    this.isPlaying = true;
    
    this.viewers.forEach(viewer => {
      viewer.animationName = animationName;
      viewer.currentTime = 0;
      viewer.play(options);
    });
    
    // Keep models synchronized
    this.syncLoop();
  }
  
  syncLoop() {
    if (!this.isPlaying) return;
    
    const elapsed = (performance.now() - this.syncTime) / 1000;
    this.viewers.forEach(viewer => {
      if (Math.abs(viewer.currentTime - elapsed) > 0.1) {
        viewer.currentTime = elapsed;
      }
    });
    
    requestAnimationFrame(() => this.syncLoop());
  }
  
  pause() {
    this.isPlaying = false;
    this.viewers.forEach(viewer => viewer.pause());
  }
}

// Usage with multiple models
const models = document.querySelectorAll('model-viewer');
const sync = new AnimationSync(Array.from(models));
sync.play("dance", { repetitions: Infinity });