Source: display/AnimatedImage.js

import { TextureInfo } from "../data/texture/TextureInfo";
import { noop } from "../utils/helpers";
import { Image } from "./Image";
import "../geom/RectangleType";

/**
 * Frame
 * @typedef {Object} Frame
 * @extends {Rectangle}
 * @property {number} length - optional frame length
 * @property {number} x
 * @property {number} y
 * @property {number} width
 * @property {number} height
 */

/**
 * Animated Image
 * @extends {Image}
 * @property {number} frameLength
 * @property {boolean} isPlaying
 */
export class AnimatedImage extends Image {
  /**
   * Creates an instance of AnimatedImage.
   * @constructor
   * @param {TextureInfo} texture
   */
  constructor(texture) {
    super(texture);

    this._frame = 0;
    this.frameLength = this._currentFrameLength = 120;

    this._frames = [];
    this._currentRenderTime = Date.now();

    this.stop();
  }

  /**
   * Set/Get frames
   * @type {Array<Frame>}
   */
  get frames() {
    return this._frames;
  }
  set frames(v) {
    this._frames = v;
    const firstFrame = this._frames[0];
    if (firstFrame) {
      this._currentFrameLength = firstFrame.length ?? this.frameLength;
      this._currentRenderTime = Date.now();
    }
  }

  /**
   * Go to a frame and stop
   * @param {number} frame
   */
  gotoAndStop(frame) {
    this.stop();
    this._frame = frame;
    this._useTextureFrame();
  }

  /**
   * Go to a frame and play
   * @param {number} frame
   */
  gotoAndPlay(frame) {
    this._frame = frame;
    this.play();
  }

  /**
   * Stop the animation
   */
  stop() {
    this._updateAnimationFv = noop;
    this.isPlaying = false;
  }

  /**
   * Play the animation
   */
  play() {
    this._updateAnimationFv = this._updateAnimation;
    this.isPlaying = true;
    this._useTextureFrame();
  }

  /**
   * Update the animation
   * @param {number} renderTime
   */
  update(renderTime) {
    super.update();
    this._updateAnimationFv(renderTime);
  }

  /**
   * Destruct class
   */
  destruct() {
    this.stop();
    super.destruct();
  }

  /**
   * @param {number} renderTime
   * @ignore
   */
  _updateAnimation(renderTime) {
    const ellapsedTime = renderTime - this._currentRenderTime;
    if (ellapsedTime > this._currentFrameLength) {
      this._currentRenderTime = renderTime;
      this._frame += ~~(ellapsedTime / this._currentFrameLength);
      if (this._frame >= this._frames.length) this._frame = 0;

      this._useTextureFrame();
    }
  }

  /**
   * @ignore
   */
  _useTextureFrame() {
    const selectedFrame = this._frames[this._frame];
    this.textureCrop.setRect(selectedFrame);
    this._currentFrameLength = selectedFrame.length ?? this.frameLength;
  }
}