tween.js 初探
<style>.artBox { font-size: 18px; }
.artBox > p { margin: 10px 0; line-height: 30px; }
.artBox mark { padding: 4px 6px; background: lightblue; }
#prevBox { position: fixed; top: 0; right: 0; bottom: 0; left: 0; background: #eee; display: none; padding: 0; overflow: hidden; z-index: 1000; margin: 0; }
#prevBox::after { position: absolute; content: '关闭预览'; bottom: 10px; left: calc(50% - 40px); padding: 0 4px; width: 80px; height: 30px; line-height: 30px; text-align: center; border: 1px solid #efe; border-radius: 6px; background: #eee; font-size: 14px; box-shadow: 2px 2px 6px rgba(0,0,0,.25); cursor: pointer; }
iframe { position: relative; width: 100%; height: 100%; border: none; outline: none; box-sizing: border-box; margin: 0; }
</style>
<div id="prevBox"></div>
<div class="artBox">
<p>英文单词 tween 的一个意思是“在……之间”,Tween.js 实现的就是对象属性值从 a 到 b 的变化。简言之,Tween.js 是一个模块化的JS补间动画库,轻量级(30来KB)且开源免费,用于创建指定属性值的变化、并利用变化的属性值为对象创建平滑的动画提供简洁明快的实现机制。</p>
<p>要使用 tween 库,首先需要在 type="module" 的 script 标签中使用 import 导入 tween 库模块文档:</p>
<div class="hEdiv"><pre class="hEpre">
<script type="module">
import TWEEN from 'https://638183.freep.cn/638183/3dev/examples/jsm/libs/tween.module.js';
//... 其它代码
</script>
</pre></div>
<p>上述资源库是 ThreeJS 作为扩展库而收录的tween模块文档,版本号为 23.1.1,并非最新。需要了解或获取最新版本的可访问 <a href="https://tweenjs.github.io/tween.js/README_zh-CN.html" target="_blank">tween.js官网</a>。注意上面我们是从 tween 库资源导入了<mark>TWEEN</mark>对象,该对象是我们后续使用的依据,比如创建一个后续要改变的对象单个或多个属性值,我们可以使用它的 Tween 方法,<mark>TWEEN.Tween(参数)</mark>:</p>
<div class="hEdiv"><pre class="hEpre">
const vals = { x: 0, y: 0 }; // 需要改变的属性值初始值 :使用JS对象的键值对表示
const tween = new TWEEN.Tween(vals); // 实例化一个 TEEEN 对象,命名为 tween,该对象针对 vals 开展工作
</pre></div>
<p>tween 就是一个实例化的 TWEEN 对象,通过它、对事先声明的变量 vals 的处理可以改变 vals.x 和 vals.y 的值,并可设置和控制 vals 作用对象的动画。首先,我们要让 vals 里面的数值产生变化,用 to 方法:</p>
<div class="hEdiv"><pre class="hEpre">
// 在2000毫秒的时间内令 vals 的值变为
tween.to({ x: 600, y: 460 }, 2000);
</pre></div>
<p>然后将 tween 改变的值作用于具体的对象,例如一个 id="box" 的绝对定位的 div 元素,我们通过 vals 记录的 x、y 值去驱动 div 的 left 和 top 即可,紧跟着是启动动画:</p>
<div class="hEdiv"><pre class="hEpre">
// 在 onUpdate 方法中驱动盒子CSS的left和top值
tween.onUpdate( () => {
box.style.left = vals.x + 'px';
box.style.top = vals.y + 'px';
};
// 用 start() 方法开启动画
tween.start();
</pre></div>
<p>以上,完成了动画机制,算是准备就绪,但要真正让 box 动起来,还需要一个全局的<mark>TWEEN.update();</mark>指令,指令放在一个函数中,函数则通过请求关键帧动画 requestAnimationFrame() 反复调用:</p>
<div class="hEdiv"><pre class="hEpre">
// 持续运行动画函数
const animate = () => {
TWEEN.update(); // 全局更新 tween 数据指令
requestAnimationFrame(animate); // 通过请求关键帧动画的API递归调用函数
};
animate(); // 运行动画
</pre></div>
<p>以下,我们将上述代码整合起来,激动人心的时刻立马到来:</p>
<div class="hEdiv"><pre class="hEpre" id="pre1">
<div id="box" style="position: absolute; width: 100px; height: 100px; background: tan;"></div>
<script type="module">
import TWEEN from 'https://638183.freep.cn/638183/3dev/examples/jsm/libs/tween.module.js';
const vals = { x: 0, y: 0 }; // 需要改变的属性值初始值
const tween = new TWEEN.Tween(vals); // 实例化一个 TEEEN 对象,该对象针对 vals 开展工作
tween.to({ x: 600, y: 460 }, 2000); // 在2000毫秒的时间内持续更新 vals 值到花括号里的设定
// 在 onUpdate 方法中驱动盒子CSS的left和top值
tween.onUpdate( () => {
box.style.left = vals.x + 'px';
box.style.top = vals.y + 'px';
});
tween.start(); // 开启动画
// 持续运行动画函数
const animate = () => {
TWEEN.update(); // 全局更新 tween 数据指令
requestAnimationFrame(animate); // 请求关键帧动画递归调用函数
};
animate(); // 运行动画
</script>
</pre></div>
<blockquote><button id="btnPrev1">运行代码</button></blockquote>
<p>可以看出,tween 先是改变一个JS对象(vals)里面的值(x和y),例如上例的<mark>vals = {x: 0, y: 0};</mark>中的 x 和 y,接着通过 onUpdate() 方法将改变中的 x 和 y 的值持续作用于指定的HTML元素即 id="box" 的 div,再辅以其它指令最终实现了 div 的位置移动动画。tween 能做到的和能做到的程度不止这些,以下例子我们将让 div 从左上角移动到右下角然后返回,到右下角时div宽高尺寸变为最大,动画反复运行并可暂停、继续:</p>
<div class="hEdiv"><pre class="hEpre" id="pre2">
<div style="position: absolute; width: 100%; margin: 10px; text-align: center;">点击页面可控制动画</div>
<div id="box" style="position: absolute; width: 100px; height: 100px; background: tan;"></div>
<script type="module">
import TWEEN from 'https://638183.freep.cn/638183/3dev/examples/jsm/libs/tween.module.js';
const vals = { x: 10, y: 10, w: 40, h: 40 }; // 初始化要改变的css属性值
const target = {x: window.innerWidth - 120, y: window.innerHeight - 120, w: 100, h: 100}; // css属性目标值
// 下面以链式写法创建tween动画机制
const tween = new TWEEN.Tween(vals)
.to(target, 3000)
.onUpdate( () => {
box.style.cssText += `
transform: translate(${vals.x}px, ${vals.y}px);
width: ${vals.w}px;
height: ${vals.h}px;
`
})
.easing(TWEEN.Easing.Quadratic.Out) // 缓动方式
.repeat(Infinity) // Infinity表示无限重复(支持正整数)
.repeatDelay(1000) // 重复间隔时间(毫秒)
.yoyo(3000) // 反向运行(时间毫秒,设置了repeat才会生效)
.start();
// 持续运行动画函数
const animate = () => {
TWEEN.update();
requestAnimationFrame(animate);
};
// 页面点击事件 :开始(启动)和暂停动画
document.onclick = () => {
if (!tween.isPlaying()) return tween.start(); // 如果动画尚未开始则开启动画
tween.isPaused() ? tween.resume() : tween.pause(); // 暂停或继续动画
};
animate();
</script>
</pre></div>
<blockquote><button id="btnPrev2">运行代码</button></blockquote>
<p>上面的代码实现了一个相对复杂的可控动画,如你所见,只要愿意,还可以做出更为精彩美妙的动画,前提是得稍微研究一下 tween.js 到底提供了多少功能,可以查阅中文版 <a href="https://tweenjs.github.io/tween.js/docs/user_guide_zh-CN.html" target="_blank">tween.js 用户指南</a> 以获取更多的tween相关知识。作为初探,我们点到为止,但请相信,ThreeJS 能将其作为扩展库收入囊中,tween.js 绝非等闲之辈。</p>
</div>
<script type="module">
import hlight from 'https://638183.freep.cn/638183/web/helight/helight1.js';
const pres = document.querySelectorAll('.hEpre');
const divs = document.querySelectorAll('.hEdiv');
divs.forEach( (div, key) => hlight.hl(div, pres));
const preView = (htmlCode, targetBox) => {
if (targetBox.innerHTML) return;
const iframe = document.createElement('iframe');
htmlCode = htmlCode + '<style>body {margin: 0; }</style>';
iframe.srcdoc = htmlCode;
targetBox.appendChild(iframe);
targetBox.style.display = 'block';
targetBox.onclick = () => {
targetBox.innerHTML = '';
targetBox.style.display = 'none';
}
};
const btns = ;
const idPrevs = ;
btns.forEach( (btn, key) => {
btn.onclick = () => preView(idPrevs.textContent, prevBox);
});
</script> 点赞老师才情{:4_187:} 菲儿 发表于 2025-6-27 20:47
点赞老师才情
谢点 辛苦了!谢谢马老师经典讲授{:4_191:} 补间动画,也就是能补充第一个点到后一个点的过程信息。{:4_187:}
那个链式写法可以让书写变简洁了。 哈哈,后面这个实例点击页面就能暂停了,我还追着那小方块去点击{:4_173:}
这个不但改变位置了,连大小也变化的。 红影 发表于 2025-6-27 22:34
哈哈,后面这个实例点击页面就能暂停了,我还追着那小方块去点击
这个不但改变位置了,连大小也 ...
说明里有的,是点击页面。另外,代码里是 document.onclick,页面点击事件 红影 发表于 2025-6-27 22:32
补间动画,也就是能补充第一个点到后一个点的过程信息。
那个链式写法可以让书写变简洁了。
链式写法优雅而漂亮 杨帆 发表于 2025-6-27 21:58
辛苦了!谢谢马老师经典讲授
{:4_191:} 马黑黑 发表于 2025-6-27 22:40
说明里有的,是点击页面。另外,代码里是 document.onclick,页面点击事件
是啊,我没注意,结果追着小方块跑了半天的{:4_170:} 红影 发表于 2025-6-27 22:44
是啊,我没注意,结果追着小方块跑了半天的
吃鱼少了{:4_170:} 马黑黑 发表于 2025-6-27 22:40
链式写法优雅而漂亮
看来黑黑很推崇这链式写法呢{:4_173:} 红影 发表于 2025-6-27 22:45
看来黑黑很推崇这链式写法呢
我那个SVG就支持链式的 马黑黑 发表于 2025-6-27 22:45
吃鱼少了
鱼都没小方块跑得快{:4_170:} 马黑黑 发表于 2025-6-27 22:46
我那个SVG就支持链式的
所以黑黑早就知道链式的优雅和高级了{:4_187:} 红影 发表于 2025-6-27 22:54
所以黑黑早就知道链式的优雅和高级了
链式也不应滥用,会很绕 马黑黑 发表于 2025-6-27 22:55
链式也不应滥用,会很绕
嗯,不容易理解。虽然很好看。 红影 发表于 2025-6-27 23:10
嗯,不容易理解。虽然很好看。
恰当使用吧。像 tween 酱紫,逻辑简单的,统统可以放在链式里,复杂的子项,可以拿出来另外处理。 马黑黑 发表于 2025-6-28 08:10
恰当使用吧。像 tween 酱紫,逻辑简单的,统统可以放在链式里,复杂的子项,可以拿出来另外处理。
嗯嗯,链式的看着很清爽,用着也方便。 红影 发表于 2025-6-28 15:01
嗯嗯,链式的看着很清爽,用着也方便。
反正挺不错的