canvas实现animate.css动画效果

13,611 阅读2分钟

canvas实现动画的基本步骤

说简单点就是重复的清除绘制每一帧

  • 清空canvas(clearRect)
  • 保存canvas状态(save)
  • 根据动画当前状态要求 平移(translate)、旋转(rotate)、缩放(scale)、错切(skew)绘制目标元素
  • 恢复canvas状态(resotre)

执行动画

通过requestAnimationFrame平缓有效率的来执行动画的重复绘制。

封装一个动画执行的类

export default class CanvasAnimation {
    private _ctx: CanvasRenderingContext2D;
    private _duration: number; // 动画执行时间
    private _startTime: number; // 动画执行开始时间
    private _animationTyep: string; // 动画类型
    private _draw: () => void; // 传入进来的元素绘制方法
    private _clearRect: () => void; // 画布情况方法
    private _progressing: boolean; // 动画是否执行中
    private _width: number; // 执行动画元素的宽度
    private _height: number; // 执行动画元素的高度

    public onEnd: () => void = () => {}; // 动画执行结束回调
    constructor(
        ctx: CanvasRenderingContext2D,
        draw: () => void,
        clearRect: () => void
    ) {
        this._ctx = ctx;
        this._duration = 0;
        this._startTime = 0;
        this._animationTyep = "";
        this._draw = draw;
        this._clearRect = clearRect;
        this._progressing = false;
        this._width = 100;
        this._height = 100;
    }

    // 设置要执行动画的基础配置
    setOptions(options: IAnimationOptions) {
        this._animationTyep = options.type;
        this._duration = options.duration;
        this._width = options.width || 100;
        this._height = options.height || 100;
    }

    // 开始执行动画
    public start() {
        this._progressing = true;
        this._startTime = Date.now();
        this._action();
    }

    // 3d 绕轴旋转!!!!!!!!!!!
    // 待研究
    private _action() {
        if (!this._progressing) return;
        const duration = Date.now() - this._startTime;
        if (duration > this._duration) {
            // 动画执行结束
            this._progressing = false;
            this.onEnd();
            return;
        }
        // 获取当前动画类型对应的canvas状态
        const animationStatus = getAnimationStatus(
            this._animationTyep,
            (duration / this._duration) * 100,
            this._width,
            this._height
        );
        // 清空画布
        this._clearRect();
        // 保存canvas状态
        this._ctx.save();
        // 一些特殊动画状态的特别处理
        if (
            this._animationTyep === "rotateInDownLeft" ||
            this._animationTyep === "rotateInUpLeft" ||
            this._animationTyep === "rotateOutDownLeft" ||
            this._animationTyep === "rotateOutUpLeft" ||
            this._animationTyep === "hinge"
        ) {
            this._ctx.translate(-this._width / 2, this._height / 2);
        } else if (
            this._animationTyep === "rotateInDownRight" ||
            this._animationTyep === "rotateInUpRight" ||
            this._animationTyep === "rotateOutDownRight" ||
            this._animationTyep === "rotateOutUpRight"
        ) {
            this._ctx.translate(this._width / 2, this._height / 2);
        }
        // 缩放状态设置
        this._ctx.scale(...animationStatus.scale);
        // 平移状态设置
        this._ctx.translate(...animationStatus.translate);
        // 透明度状态设置
        this._ctx.globalAlpha = animationStatus.opacity;
        // 旋转状态设置
        this._ctx.rotate((animationStatus.rotate * Math.PI) / 180);
        // 错切状态这设置
        this._ctx.transform(
            1,
            (animationStatus.skew[0] * Math.PI) / 180,
            (animationStatus.skew[1] * Math.PI) / 180,
            1,
            0,
            0
        );
        // 动画元素的绘制
        this._draw();
        // 恢复canvas状态
        this._ctx.restore();
        // 执行下一次动画绘制
        window.requestAnimationFrame(this._action.bind(this));
    }
    // 结束动画
    public stop() {
        this._progressing = false;
    }
}

关于动画类的使用

const animation = new CanvasAnimation(
    context, () => {
        drawAnimationText(context, text, x, y);
    },
    clearRect
);

// ...

// 执行动画
animation.setOptions({
    type,
    duration,
    width,
    height
});
animation.start();

// ...

// 结束动画
animation.stop();

体验地址

体验地址2

开源地址