发布时间:2025-04-15

设计模式:工厂模式 + 状态模式。
定义 set + remove 方法用于搜集和释放外部传入的动画。标记requestAnimationFrame执行状态,在插入需要执行的动画时,创建requestAnimationFrame。当所有动画结束后,清除requestAnimationFrame。
requestAnimationId: number | null;
animations: Map<string, () => void>;
reRender?: () => void;requestAnimationId:requestAnimationFrame执行时生成id,用于动画执行完毕的清除,以及作为状态标记防止重复创建;
animations:动画集合,类型为Map,用于搜集外部传入的动画,动画帧执行时循环调用其中的动画;
reRender:重画canvas等渲染的函数,由外部在初始化页面时传入;
对象在构造时候需要初始requestAnimationId和animations,在外部调用构造函数后,页面需要主动传入reRender进行渲染函数的初始化。
constructor() {
this.requestAnimationId = null;
this.animations = new Map();
}
init = (reRender: () => void) => {
this.reRender = reRender;
}定义start方法,就是通常requestAnimationFrame的递归调用方法,当然我们要做到requestAnimationFrame的清理需要在每次执行时检测
animations集合。
start = () => {
const render = () => {
// 调用hasAnyAnimation函数判断是否继续执行
if (!this.hasAnyAnimation()) return;
this.animations.forEach(anime => anime());
if (this.reRender) this.reRender();
this.requestAnimationId = requestAnimationFrame(render);
};
render();
}
hasAnyAnimation = () => {
// 当animations集合中没有需要执行的动画时,就清除requestAnimationFrame
if (this.animations.size === 0) {
if (this.requestAnimationId) {
cancelAnimationFrame(this.requestAnimationId);
this.requestAnimationId = null;
};
return false;
}
return true
}定义set方法,外部调用set方法添加一个动画执行函数,为该函数生成一个uuid。
定义remove方法,一般情况下动画函数执行完毕后会有onComplete回调,所以在外部的回调中执行remove方法,通过uuid移除存在于集合中的对应函数。
setAnimation = (anime: () => void) => {
// 生成uuid的
封装方法,通过uuid库实现
const uuid = generateRandomNumberString(12);
this.animations.set(uuid, anime);
// 当没有requestAnimationFrame被创建时,调用start方法,触发render的执行
if (!this.requestAnimationId) {
this.start();
}
// 返回uuid用于外部的移除调用
return uuid;
}
removeAnimation = (uuid: string) => {
if (this.animations.has(uuid)) {
this.animations.delete(uuid);
}
}react + tweenjs + threejs 中使用
const animationRef = useRef<GisAnimation>(null)
const initAnimation = () => {
const gisAnimation = new GisAnimation();
gisAnimation.init(reRender);
animationRef.current = gisAnimation;
}
useEffect(() => {
initAnimation();
}, [])2、添加动画和移除动画
这里同时实现了动画的异步调用以控制连续动画执行顺序,如模型1先执行leave动画再执行模型2的enter动画。
声明addAnimation,以tween为例:
const addAnimation = async (tween: TWEEN.Tween | undefined, onComplete?: () => void) => {
if (!tween) return;
return new Promise((rs) => {
const gisAnimation = animationRef.current;
if (gisAnimation) {
const uuid = gisAnimation.setAnimation(() => tween.update())
tween.onComplete(() => {
gisAnimation.removeAnimation(uuid);
if (onComplete) {
onComplete();
rs(true);
}
})
tween.start();
}
})
}
const doAnimation = async () => {
// earthEnter为地球模型的入场动画,返回一个tween实例
await addAnimation(earthEnter());
// earthLeave为地球模型的离场动画,返回一个包含tween和onComplete方法的对象
const earthLeaveBack = earthLeave();
await addAnimation(
earthLeaveBack.tween,
earthLeaveBack.onComplete,
)
}