Tween动画链接补间及其播放停止暂停继续
当我们只有一个 tween 动画,若需要动画永恒持续运行,我们除了设计一个repeat(Infinity)机制,还可以使用链式补间方法来加以实现,tween.chain(tween),这是 tween 动画自己链接自己,这将在 tween 动画结束时重新启动 tween 动画。依此原理,假如我们有两个或更多的动画,我们可以按次序依次相互链接补间,最后一个动画则链接到第一个动画,这就形成了闭环式的按次序执行的永动动画链。例如:
// 假如我们有三个动画 tweenA、tweenB、tweenC tweenA.start(); // 需要启动第一个动画,其余的不要start() tweenA.chain(tweenB); // 动画A链接动画B tweenB.chain(tweenC); // 动画B链接动画C tweenC.chain(tweenA); // 动画C链接动画A
永动不意味着动画不可控。我们可能已经知道,tween.start()是启动一个动画,tween.stop()则停止动画。动画启动后可以暂停,tween.pause(),暂停之后可以继续,tween.resume()。停止(stop)和暂停(pause)以及开始(start)和继续(resume)是有区别的:停止后再启动动画,动画将以全新的方式开始,暂停后继续动画,动画从暂停的地方继续;开始总是以新的动画方式启动动画,继续则从上一个动画时间线上的点继续动画。另外,停止针对正在进行中的动画,继续则仅仅针对暂停有效。知道这些,我们就可以给多个动画加入控制机制,我们要做的就是遍历动画,检查它们处于什么状态,从而有针对性地、精准地控制其运动状态。
以下示例我们设计三个球形div元素,它们将依次运行从左到右再返回的平移运动,通过链式(chain)补间实现动画接力及不间断运行。动画可以通过点击相应按钮进行交互控制——
<style> #papa {margin: 30px auto; width: 900px; height: 360px; border: 1px solid gray; position: relative; } #b1, #b2, #b3 { position: absolute; left: 10px; top: 20px; width: 60px; height: 60px; border-radius: 50%; background: olive; } #b2 { top: 120px; } #b3 { top: 220px; } .btnsWrap { position: absolute; bottom: 10px; width: 100%; text-align: center; } .btnsWrap button { min-width: 70px; margin: 5px; padding: 4px; } </style> <div id="papa"> <div id="b1"></div> <div id="b2"></div> <div id="b3"></div> <div class="btnsWrap"> <button type="button" value="start">开始</button> <button type="button" value="stop">停止</button> <button type="button" value="pause">暂停</button> <button type="button" value="resume">继续</button> </div> </div> <script type="module"> import TWEEN from 'https://638183.freep.cn/638183/3dev/examples/jsm/libs/tween.module.js'; const btns = papa.querySelectorAll('button'); // 获取按钮 const balls = [b1, b2, b3]; // 球数组 const poses = [{ x: 10 }, { x: 10 }, { x: 10 }]; // 初始位置数组 const endPoses = [{ x: [820, 10] }, { x: [820, 10] }, { x: [820, 10] }]; // 目标位置数组 const tweens = []; // 动画数组 // 为每一个球创建tween动画 balls.forEach( (b, k) => { const tween = new TWEEN.Tween(poses[k]) .to(endPoses[k], 4000) .onUpdate( () => b.style.transform = `translate(${poses[k].x}px)` ); if (k === 0) tween.start(); // 第一个球自动运行动画(不需要自动运行时注释掉或删掉) tweens.push(tween); }); // 动画链式相互调用 tweens.forEach( (t, k) => t.chain(tweens[(k + 1) % balls.length]) ); // 循环运行TWEEN动画 const animate = () => { TWEEN.update(); requestAnimationFrame(animate); }; // Tween运行、暂停状态 const tweenState = () => { let play = 0, pause = 0; // 播放与暂停的tween动画数量 tweens.forEach(tw => { play += tw.isPlaying() ? 1 : 0; pause += tw.isPaused() ? 1 : 0; }); return {play: play > 0, pause: pause > 0 }; // 返回结果 }; // 设置按钮状态 const btnState = () => { const state = tweenState(); // 获取当前动画运行状态 // 播放状态为真 if (state.play) { btns[0].disabled = true; btns[1].disabled = false; btns[2].disabled = state.pause ? true : false; btns[3].disabled = state.pause ? false : true; // 暂停状态为真 } else btns.forEach( (b,k) => b.disabled = k === 0 ? false : true); }; // 按钮点击事件 btns.forEach(btn => { btn.onclick = () => { const state = tweenState(); // 获取当前动画运行状态 switch (btn.value) { case 'start': // 开始 if (!state.play) tweens[0].start(); break; case 'stop': // 停止 if (state.play) tweens.forEach(tw => tw.stop()); break; case 'pause': // 暂停 if (!state.pause) tweens.forEach(tw => tw.pause()); break; case 'resume': // 继续 if (!state.pause) return; tweens.forEach(tw => { if (tw.isPaused()) tw.resume(); }); break; } btnState(); // 设置按钮状态 } }); animate(); // 启动动画 btnState(); // 初始化按钮状态 </script>
代码量偏多,但不是Tween动画层面的实现问题,而是在处理按钮状态、按钮交互逻辑层面。其实按钮状态可以忽略,只是为了提升交互的友好性,我们花了一些精力对按钮的交互功能做了一些逻辑和状态方面的处理机制。
最后提一下:chain 链式补间可以一对多,例如:tweenA.chain(tweenB, tweenC, tweenD);。
前一篇: ThreeJS:纹理流动的直管道
下一篇: ThreeJS:罗列并运行GLTF模型所有动画
评论列表 [1条]
#1 | 飞飞 于 2025-7-2 16:04 发布: 先运行代码看效果。。开始对停止,暂停对继续,也可以第二个球暂停之时,让第一个球体重新开始。。多种运动形式组合。。