使用js绘制可中断的序列帧动画

使用js绘制可中断的序列帧动画,第1张

准备:

        一张绘有所有帧的雪碧图,图与图之间间距相同,每张图的宽高相同。

        如: 

        (本文使用的是纵向雪碧图,可自行更改参数实验效果) 

原理:

        将动画的所有帧导出为一张雪碧图,使用js控制background-position,每一帧移动一张图的距离。

坑点:

1. css可以绘制序列帧动画:使用step函数,实现简单,性能好。

如: 

.imgContainer{

        background-position: 0 0;

        transition: all 1s step(24);

}

.imgContainer:hover{

        background-position: 0 720px;

}

        但是使用css实现的帧动画,很难做到中途中断动画并反向播放。播放完毕后反向播放实现简单,但是中断当前动画反向播放较为困难。例如中途移出鼠标后,动画会从当前帧反向播放直到初始状态,但是这个过程中timing-function还是step(24),而中断后反向播放的总帧数并没有24帧,导致动画错乱。而使用js控制timing-function倒不如直接将动画播放全权控制。

2. requestAnimationFrame与react的state更新机制冲突,时间足够短时react会自动批处理state更新,导致requestAnimationFrame中的state更新不能及时反馈到页面上, 动画丢帧严重,而使用Ref保存相关state并不会触发页面更新。

可以考虑降级使用setTimeout处理。但是遵循了react哲学的同时性能肯定不如requestAnimationFrame了。

所以我选择直接使用ref更新dom。

实现: React:

hooks封装:

import { MutableRefObject, useState, useEffect, useRef } from "react";

/**
 * @param ref MutableRefObj 目标元素
 * @param frameImageNumber number 总帧数(序列图总数)
 * @param direction "vertical"|"horizontal" 绘制帧方向 默认"vertical"
 * @param frameNumber number 1s内帧数 默认60帧
 * @returns setDispatch<"in"|"out"> 设置鼠标移入还是移出
 */
interface UseFrameAnimationProps {
  ref: MutableRefObject;
  frameImageNumber: number;
  direction?: "vertical" | "horizontal"
  frameNumber?: number;
}
const useFrameAnimation = (props: UseFrameAnimationProps) => {
  const { ref, frameImageNumber, direction = "vertical", frameNumber = 60 } = props;

  // 一帧跨越的高度 数值
  const [frameHeight, setFrameHeight] = useState(0);
  // 一帧跨越的高度 单位
  const [frameHeightUnit, setFrameHeightUnit] = useState("px");
  useEffect(() => {
    const heightString = (ref?.current && window.getComputedStyle(ref.current).height) || "0";
    const height = parseInt(heightString);
    setFrameHeight(height)
    setFrameHeightUnit(heightString.replace(height.toString(), ""));
  }, []);

  const [type, setType] = useState<"in" | "out" | undefined>();

  // 间隔多少秒后绘制一帧
  const [frameTime, setFrameTime] = useState(1000 / (frameNumber-1));
  useEffect(() => { setFrameTime(1000 / (frameNumber-1)) }, [frameNumber])

  // 处理到第几帧画面
  const frameImage = useRef(0);
  // 上次绘制帧的时间
  const enterTiming = useRef(0);
  // requestAnimationFrame flag
  const requestFlag = useRef(null);

  const animationFunc = (type = "in") => {
    // 获取当前时间 与 enterTiming对比 超过frameTime则绘制下一帧
    const nowTime = Date.now();
    if (nowTime - enterTiming.current >= frameTime) {
      enterTiming.current = nowTime;
      // 通过离散地移动backgroundPosition跳到下一帧
      if (direction === "horizontal") {
        ref.current && (ref.current.style.backgroundPosition = `-${frameImage.current * frameHeight}${frameHeightUnit} 0`);
      } else {
        ref.current && (ref.current.style.backgroundPosition = `0 -${frameImage.current * frameHeight}${frameHeightUnit}`);
      }
      // 判断是否中断绘制,或已绘制到最后一张
      if (type === "in") {
        frameImage.current += 1;
        if (frameImage.current >= frameImageNumber) {
          return;
        }
      } else {
        if (frameImage.current <= 0) {
          return;
        }
        frameImage.current -= 1;
      }
    }
    // 继续绘制
    requestFlag.current = requestAnimationFrame(() => animationFunc(type));
  }

  useEffect(() => () => {
    requestFlag.current && cancelAnimationFrame(requestFlag.current)
  }, [])

  // type变更则开始绘制
  useEffect(() => {
    if (!type) {
      return;
    }
    // 终止上次绘制
    requestFlag.current && cancelAnimationFrame(requestFlag.current);

    frameImage.current === frameImageNumber && (frameImage.current -= 1)

    enterTiming.current = Date.now();
    requestFlag.current = requestAnimationFrame(() => animationFunc(type))
  }, [type])

  return setType;
}

export default useFrameAnimation;

使用: 

#animation-container{
    background-image: url("step-image.png");
    background-repeat: no-repeat;
    background-size: 100% auto;
}

const Index = () => {

  const animationEl = useRef(null);
  const setAnimationType = useFrameAnimation({
    ref: animationEl,
    frameImageNumber: 24
  });

  return (
    {
        backgroundImage: `url(${icon})`,
      }}
    />
  )
}
原生:



  
  
  
  Document

  



  

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/943044.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-18
下一篇 2022-05-18

发表评论

登录后才能评论

评论列表(0条)

保存