CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-zrender

A lightweight 2D graphics library providing canvas and SVG rendering for Apache ECharts

Overview
Eval results
Files

animation.mddocs/

Animation System

ZRender provides a comprehensive animation framework that supports property interpolation, easing functions, timeline control, and advanced features like path morphing. The animation system is frame-based and optimized for smooth 60fps performance.

Core Animation Classes

Animator

The primary interface for controlling animations on elements:

interface Animator {
  // Timeline control
  when(time: number, props: any): Animator;
  during(callback: (percent: number) => void): Animator;
  delay(time: number): Animator;
  
  // Lifecycle callbacks
  done(callback: () => void): Animator;
  aborted(callback: () => void): Animator;
  
  // Playback control
  start(easing?: string | Function): Animator;
  stop(): void;
  pause(): void;
  resume(): void;
  
  // Configuration
  getLoop(): boolean;
  setLoop(loop: boolean): Animator;
}

Element Animation Methods

All graphics elements provide animation methods:

interface Element {
  animate(props?: string): Animator;
  animateStyle(): Animator;
  animateShape(): Animator;
  stopAnimation(forwardToLast?: boolean): this;
  
  // Animation state queries
  isAnimationFinished(): boolean;
  getAnimationDelay(): number;
}

Built-in Easing Functions

ZRender includes a comprehensive set of easing functions:

Basic Easing

function linear(t: number): number;
function easeInQuad(t: number): number;
function easeOutQuad(t: number): number;
function easeInOutQuad(t: number): number;
function easeInCubic(t: number): number;
function easeOutCubic(t: number): number;
function easeInOutCubic(t: number): number;
function easeInQuart(t: number): number;
function easeOutQuart(t: number): number;
function easeInOutQuart(t: number): number;
function easeInQuint(t: number): number;
function easeOutQuint(t: number): number;
function easeInOutQuint(t: number): number;

Advanced Easing

function easeInSine(t: number): number;
function easeOutSine(t: number): number;
function easeInOutSine(t: number): number;
function easeInExpo(t: number): number;
function easeOutExpo(t: number): number;
function easeInOutExpo(t: number): number;
function easeInCirc(t: number): number;
function easeOutCirc(t: number): number;
function easeInOutCirc(t: number): number;

Elastic and Bounce

function easeInElastic(t: number): number;
function easeOutElastic(t: number): number;
function easeInOutElastic(t: number): number;
function easeInBack(t: number): number;
function easeOutBack(t: number): number;
function easeInOutBack(t: number): number;
function easeInBounce(t: number): number;
function easeOutBounce(t: number): number;
function easeInOutBounce(t: number): number;

Convenience Aliases

const easeIn: typeof easeInQuad;
const easeOut: typeof easeOutQuad;
const easeInOut: typeof easeInOutQuad;
const ease: typeof easeInOut;

Animation Configuration

Clip Interface

interface Clip {
  life: number;          // Duration in milliseconds
  delay: number;         // Delay before starting
  loop: boolean;         // Whether to loop
  gap: number;          // Gap between loops
  easing: string | Function; // Easing function
  onframe: (percent: number) => void; // Frame callback
  ondestroy: () => void;  // Cleanup callback
  onrestart: () => void;  // Restart callback
}

Animation Options

interface AnimationOptions {
  duration?: number;     // Animation duration
  easing?: string | Function; // Easing function
  delay?: number;        // Start delay
  loop?: boolean;        // Loop animation
  gap?: number;          // Gap between loops
  during?: (percent: number) => void; // Progress callback
  done?: () => void;     // Completion callback
  aborted?: () => void;  // Abortion callback
}

Path Morphing

Advanced morphing capabilities for transforming between different paths:

namespace morph {
  function morphPath(from: string, to: string, animationOpts?: AnimationOptions): string;
  function combineMorphing(morphList: any[]): any;
  function isCombineMorphing(obj: any): boolean;
}

Usage Examples

Basic Property Animation

import { Circle } from "zrender";

const circle = new Circle({
  shape: { cx: 100, cy: 100, r: 30 },
  style: { fill: '#74b9ff' },
  position: [0, 0]
});

// Animate position
circle.animate('position')
  .when(1000, [200, 150])  // Move to (200, 150) over 1 second
  .when(2000, [100, 100])  // Return to (100, 100) over next second
  .start('easeInOut');

// Animate shape properties
circle.animate('shape')
  .when(1500, { r: 60 })   // Grow radius to 60
  .start('easeOutElastic');

// Animate style properties  
circle.animate('style')
  .when(800, { fill: '#e17055', opacity: 0.7 })
  .start('easeInOutQuad');

zr.add(circle);

Complex Animation Sequences

import { Rect } from "zrender";

const rect = new Rect({
  shape: { x: 50, y: 50, width: 80, height: 60 },
  style: { fill: '#00b894' }
});

// Chain multiple animations
rect.animate('position')
  .when(1000, [200, 50])
  .when(2000, [200, 200])
  .when(3000, [50, 200])
  .when(4000, [50, 50])
  .start('easeInOutCubic');

// Parallel animation on different properties
rect.animate('shape')
  .delay(500)  // Start after 500ms delay
  .when(1000, { width: 120, height: 40 })
  .when(2000, { width: 40, height: 120 })
  .when(3000, { width: 80, height: 60 })
  .start('easeOutBounce');

// Color animation with callbacks
rect.animate('style')
  .when(1000, { fill: '#fdcb6e' })
  .when(2000, { fill: '#e84393' })
  .when(3000, { fill: '#74b9ff' })
  .when(4000, { fill: '#00b894' })
  .during((percent) => {
    // Custom logic during animation
    if (percent > 0.5) {
      rect.style.shadowBlur = (percent - 0.5) * 20;
    }
  })
  .done(() => {
    console.log('Animation completed!');
  })
  .start();

zr.add(rect);

Easing Function Demonstrations

import { Circle } from "zrender";

// Create circles to demonstrate different easing functions
const easingFunctions = [
  'linear', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad',
  'easeInCubic', 'easeOutBounce', 'easeOutElastic', 'easeInBack'
];

easingFunctions.forEach((easing, index) => {
  const circle = new Circle({
    shape: { cx: 50, cy: 50 + index * 60, r: 20 },
    style: { fill: '#74b9ff' }
  });
  
  // Animate position with different easing
  circle.animate('position')
    .when(2000, [400, 50 + index * 60])
    .setLoop(true)  // Loop the animation
    .start(easing);
  
  // Add label
  const label = new Text({
    style: {
      text: easing,
      fontSize: 12,
      fill: '#2d3436'
    },
    position: [460, 55 + index * 60]
  });
  
  zr.add(circle);
  zr.add(label);
});

Interactive Animations

import { Star } from "zrender";

const star = new Star({
  shape: { cx: 200, cy: 200, n: 5, r0: 30, r: 60 },
  style: { fill: '#fdcb6e', stroke: '#e17055', lineWidth: 2 }
});

// Hover animations
star.on('mouseover', () => {
  star.stopAnimation();  // Stop any running animations
  
  star.animate('shape')
    .when(300, { r: 80, r0: 40 })
    .start('easeOutElastic');
    
  star.animate('style')
    .when(200, { fill: '#fff5b4', shadowBlur: 15, shadowColor: '#fdcb6e' })
    .start();
});

star.on('mouseout', () => {
  star.stopAnimation();
  
  star.animate('shape')
    .when(300, { r: 60, r0: 30 })
    .start('easeOutQuad');
    
  star.animate('style')
    .when(200, { fill: '#fdcb6e', shadowBlur: 0 })
    .start();
});

// Click animation with rotation
star.on('click', () => {
  star.animate('rotation')
    .when(1000, Math.PI * 2)  // Full rotation
    .done(() => {
      star.rotation = 0;  // Reset rotation
    })
    .start('easeInOutCubic');
});

zr.add(star);

Timeline Animations

import { Group, Circle, Rect, Text } from "zrender";

// Create a group of elements for coordinated animation
const animationGroup = new Group();

const elements = [];
for (let i = 0; i < 5; i++) {
  const circle = new Circle({
    shape: { cx: 100 + i * 80, cy: 200, r: 25 },
    style: { fill: `hsl(${i * 60}, 70%, 60%)` }
  });
  elements.push(circle);
  animationGroup.add(circle);
}

// Staggered animation sequence
elements.forEach((element, index) => {
  const delay = index * 200;  // 200ms stagger
  
  // Bounce animation
  element.animate('position')
    .delay(delay)
    .when(600, [0, -100])  // Move up (relative to group)
    .when(1200, [0, 0])    // Return to original position
    .start('easeOutBounce');
  
  // Scale animation
  element.animate('scale')
    .delay(delay + 300)
    .when(400, [1.5, 1.5])
    .when(800, [1, 1])
    .start('easeInOutQuad');
});

// Group-level animation
animationGroup.animate('position')
  .delay(1500)
  .when(1000, [0, 100])
  .start('easeInOutCubic');

zr.add(animationGroup);

Custom Easing Functions

import { Rect } from "zrender";

// Define custom easing function
const customEasing = (t: number): number => {
  // Elastic overshoot effect
  return t === 0 ? 0 : t === 1 ? 1 : 
    -Math.pow(2, 10 * (t - 1)) * Math.sin((t - 1.1) * 5 * Math.PI);
};

// Apply custom easing
const rect = new Rect({
  shape: { x: 50, y: 150, width: 60, height: 40 },
  style: { fill: '#e84393' }
});

rect.animate('position')
  .when(2000, [400, 150])
  .start(customEasing);

// Bezier curve easing (cubic-bezier equivalent)
const bezierEasing = (t: number): number => {
  // Equivalent to cubic-bezier(0.25, 0.46, 0.45, 0.94)
  const p0 = 0, p1 = 0.25, p2 = 0.45, p3 = 1;
  const u = 1 - t;
  return 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3;
};

rect.animate('shape')
  .when(2000, { width: 120, height: 80 })
  .start(bezierEasing);

zr.add(rect);

Animation Control and Management

import { Circle } from "zrender";

const controlledCircle = new Circle({
  shape: { cx: 200, cy: 200, r: 40 },
  style: { fill: '#74b9ff' }
});

// Create controllable animation
let currentAnimation: Animator;

const startAnimation = () => {
  currentAnimation = controlledCircle.animate('position')
    .when(3000, [400, 200])
    .when(6000, [200, 200])
    .setLoop(true)
    .start('easeInOutQuad');
};

const pauseAnimation = () => {
  if (currentAnimation) {
    currentAnimation.pause();
  }
};

const resumeAnimation = () => {
  if (currentAnimation) {
    currentAnimation.resume();
  }
};

const stopAnimation = () => {
  if (currentAnimation) {
    currentAnimation.stop();
  }
};

// Control via keyboard events
document.addEventListener('keydown', (e) => {
  switch(e.key) {
    case 's': startAnimation(); break;
    case 'p': pauseAnimation(); break;
    case 'r': resumeAnimation(); break;
    case 'x': stopAnimation(); break;
  }
});

// Click to toggle animation
controlledCircle.on('click', () => {
  if (controlledCircle.isAnimationFinished()) {
    startAnimation();
  } else {
    stopAnimation();
  }
});

zr.add(controlledCircle);

Performance Optimization

import { Group, Circle } from "zrender";

// Animate many elements efficiently
const createOptimizedAnimation = () => {
  const group = new Group();
  const elements: Circle[] = [];
  
  // Create many elements
  for (let i = 0; i < 100; i++) {
    const circle = new Circle({
      shape: { 
        cx: Math.random() * 800, 
        cy: Math.random() * 600, 
        r: 5 + Math.random() * 10 
      },
      style: { 
        fill: `hsl(${Math.random() * 360}, 70%, 60%)`,
        opacity: 0.8
      }
    });
    elements.push(circle);
    group.add(circle);
  }
  
  // Use single group animation instead of individual animations
  // This is more performant for coordinated movements
  group.animate('rotation')
    .when(10000, Math.PI * 2)
    .setLoop(true)
    .start('linear');
  
  // Stagger individual element animations efficiently
  elements.forEach((element, index) => {
    if (index % 10 === 0) {  // Animate every 10th element
      element.animate('scale')
        .delay(Math.random() * 2000)
        .when(1000, [1.5, 1.5])
        .when(2000, [1, 1])
        .setLoop(true)
        .start('easeInOutSine');
    }
  });
  
  return group;
};

const optimizedGroup = createOptimizedAnimation();
zr.add(optimizedGroup);

Install with Tessl CLI

npx tessl i tessl/npm-zrender

docs

animation.md

core-zrender.md

events.md

graphics-primitives.md

index.md

shapes.md

styling.md

text-images.md

utilities.md

tile.json