Frontend Development 15 min read

Implementing a Simple Tween/Easing Library in TypeScript

This article introduces a lightweight TypeScript tween library, explains its core classes HTweenLite and HTween, details the implementation of various easing functions, and demonstrates how to use the library to perform data-driven animations without relying on any specific UI framework.

New Oriental Technology
New Oriental Technology
New Oriental Technology
Implementing a Simple Tween/Easing Library in TypeScript

Tweening (easing) is widely used for animation, and many libraries exist for different languages and engines. Most articles focus on how to apply these libraries, but few explain how the easing functions are actually implemented.

This post presents a simple tween library written in TypeScript to illustrate the underlying principles, not to reinvent the wheel.

The source code for the library is shown below.

import {HEasing, HIEaseFunction} from "./HEasing";

export class HTweenLite {
  private static list: HTween[] = [];
  /**
   * 缓动方法
   * @param tar   缓动目标,一个目标只能同时指定一个缓动
   * @param dur   缓动持续时间
   * @param delay   延时启动缓动
   * @param to  缓动目标值
   * @param ease  缓动类型
   * @param update 缓动更新,缓动只是实现了数据的缓动,对象的状态变换还需要自己在中实现
   */
  public static to(tar: Object, dur: number, delay: number, to: Object, ease: HIEaseFunction, update: HIUpdate): void {
    const tween: HTween = new HTween(tar, dur, delay, HTweenLite.getObj(tar, to), to, ease, update);
    this.list.push(tween);
  }

  /**
   * 缓动方法
   * @param tar   缓动目标,一个目标只能同时指定一个缓动
   * @param dur   缓动持续时间
   * @param delay   延时启动缓动
   * @param from  缓动初始值
   * @param ease  缓动类型
   * @param update 缓动更新,缓动只是实现了数据的缓动,对象的状态变换还需要自己在中实现
   */
  public static from(tar: Object, dur: number, delay: number, from: Object, ease: HIEaseFunction, update: HIUpdate): void {
    const tween: HTween = new HTween(tar, dur, delay, from, HTweenLite.getObj(tar, from), ease, update);
    this.list.push(tween);
  }

  /**
   * 取消指定对象的缓动
   * @param tar
   * @param complete
   */
  public static kill(tar: object, complete: boolean): void {
    for(let i: number = HTweenLite.list.length - 1;i >= 0; i--){
      const tween: HTween = HTweenLite.list[i];
      if(tween.target == tar){
        tween.kill(complete);
      }
    }
  }

  /**
   * 取消所有缓动
   * @param complete
   */
  public static killAll(complete: boolean): void {
    for(let i: number = HTweenLite.list.length - 1;i >= 0; i--){
      HTweenLite.list[i].kill(complete);
    }
  }
  /**
   * 移除tween
   * @param htween
   */
  public static removeTween(htween: HTween): void {
    const ind: number = HTweenLite.list.indexOf(htween);
    if (ind != -1) {
      HTweenLite.list.splice(ind, 1);
    }
  }

  private static getObj(tar: Object, fromOrTo: Object): Object {
    const result: Object = {};
    for (let key in fromOrTo) {
      if (fromOrTo.hasOwnProperty(key)) {
        result[key] = tar[key];
      }
    }
    return result;
  }
}

type HIUpdate = (complete: boolean) => void;

class HTween{
  public target: Object;
  private duration: number;
  private from: Object;
  private to: Object;
  private t: number = 0;
  private update: HIUpdate;
  private timer: number;
  private ease: HIEaseFunction;
  private TIME_STEP: number = 0.03;

  constructor(tar: object, duration: number, delay: number, from: Object, to: Object, ease: HIEaseFunction, update: HIUpdate){
    this.target=tar;
    this.duration=duration;
    this.from=from;
    this.to=to;
    this.update=update;
    this.ease=ease || HEasing.None.easeIn;
    this.TIME_STEP = duration / Math.ceil(duration / 0.03);
    if (delay > 0) {
      this.timer = setTimeout(this.tt, delay) as Object as number;
    } else {
      this.tt();
    }
  }

  public kill(complete: boolean): void {
    if(complete){
      for (let key in this.to) {
        if(this.to.hasOwnProperty(key)) {
          this.target[key] = this.to[key];
        }
      }
      if (this.update) {
        this.update(true);
      }
    }
    clearInterval(this.timer);
    HTweenLite.removeTween(this);
  }

  private tt = () => {
    this.t += this.TIME_STEP;
    if(this.t >= this.duration){
      this.kill(true);
    }
    else{
      for (let key in this.from) {
        if(this.from.hasOwnProperty(key)) {
          //计算
          this.target[key] = this.ease(this.t, this.from[key], this.to[key] - this.from[key], this.duration);
        }
      }
      if(this.update){
        this.update(false);
      }
      this.timer = setTimeout(this.tt, this.TIME_STEP * 1000) as Object as number;
    }
  }

}

The accompanying HEasing.ts file defines the HIEaseFunction type and provides a collection of common easing formulas (linear, quadratic, cubic, quartic, quintic, sine, strong, exponential, circular, elastic, back, bounce).

/**
 * 参数解释
 * @param t  当前时间,即缓动经过的时间,0-d
 * @param b  用于缓动的属性的初始值
 * @param c  用于缓动的属性的改变总量
 * @param d  缓动总持续时间
 */
export type HIEaseFunction = (t: number, b: number, c: number, d: number, ...args: number[]) => number;

export const HEasing = {
  None: {
    easeIn: (t: number, b: number, c: number, d: number) => {
      return b+t*c/d;
    }
  },
  Quad : {
    easeIn: (t: number, b: number, c: number, d: number) => {
      return c*(t/=d)*t+b;
    },
    easeOut: (t: number, b: number, c: number, d: number) => {
      return -c *(t/=d)*(t-2) + b;
    },
    easeInOut: (t: number, b: number, c: number, d: number) => {
      if ((t/=d/2) < 1) return c/2*t*t + b;
      return -c/2 * ((--t)*(t-2) - 1) + b;
    }
  },
  // ... other easing definitions omitted for brevity ...
}

HTweenLite is the public API exposing methods such as to , from , kill , and killAll . Each call creates a new HTween instance, stores it in an internal list, and runs a timer that updates the target values based on the selected easing function.

HTween receives the animation parameters (initial values, target values, duration, easing function, optional delay, and an update callback). It advances time in fixed steps, computes the interpolated value using the easing formula, and invokes the callback so the caller can apply the new values to any object.

The article also clarifies a common misconception: tween libraries are not tied to visual objects or specific frameworks; they are pure data‑driven calculators that can be used anywhere, even without a UI.

Finally, the piece encourages readers to explore the source, experiment with custom easing functions, and adapt the library to their own needs.

FrontendanimationTypeScriptlibraryeasingTween
New Oriental Technology
Written by

New Oriental Technology

Practical internet development experience, tech sharing, knowledge consolidation, and forward-thinking insights.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.