CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-google--model-viewer

Web component for easily displaying interactive 3D models with AR support across browsers and devices

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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 });

docs

accessibility.md

animation.md

annotation.md

ar.md

controls.md

environment.md

index.md

loading.md

scene-graph.md

tile.json