元素能量之英雄主题
<style>#papa { margin: 30px 0; left: calc(50% - 81px); transform: translateX(-50%);width: clamp(600px, 90vw, 1400px); height: auto; aspect-ratio: 16/9; background: #000; box-shadow: 2px 2px 8px #000; display: grid; place-items: center; z-index: 1; position: relative; }
#btnFs { bottom: 30px; color: cyan; border-color: cyan !important; }
#player { position: absolute; width: 16vw; height: 16vw; border-radius: 50%; transform: translate(10px, -20px); cursor: pointer; z-index: 91; }
#vid {position: absolute; width: 100%; height: 100%; object-fit: cover; mask: radial-gradient(transparent 20%, red); -webkit-mask: radial-gradient(transparent 20%, red); opacity: .6; pointer-events: none; z-index: 90; }
</style>
<div id="papa">
<audio id="aud" src="https://music.163.com/song/media/outer/url?id=1984625" autoplay loop></audio>
<video id="vid" src="https://bpic.588ku.com/video_listen/588ku_video/25/04/08/17/14/59/video67f4e91361a39.mp4" autoplay loop muted></video>
<div id="player" title="播放/暂停"></div>
</div>
<script type="module">
import { THREE, scene, camera, renderer, clock, basic3 } from 'https://638183.freep.cn/638183/3dev/3/3basic.js?v=1.0';
import TWEEN from 'https://638183.freep.cn/638183/3dev/examples/jsm/libs/tween.module.js';
import { FS } from 'https://638183.freep.cn/638183/web/ku/FS.js';
basic3(papa, aud.paused);
const map = new THREE.TextureLoader().load('https://638183.freep.cn/638183/t24/w4/lzlx.webp');
map.colorSpace = THREE.SRGBColorSpace;
const planeGeometry = new THREE.PlaneGeometry(8, 4.5);
const planeMaterial = new THREE.MeshBasicMaterial({ map: map });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
const geometry1 = new THREE.TorusGeometry(0.4, 0.2, 40, 40); // 几何体1
geometry1.translate(-3, 0, 0); // 位置调整
const geometry2 = new THREE.TorusGeometry(0.4, 0.2, 40, 40); // 几何体2(仅取其点数据)
geometry2.translate(3, 0, 0); // 位置调整
const pointsMaterial = new THREE.PointsMaterial({
size: 0.05, // 尺寸
color: 'cyan', // 颜色
transparent: true, // 开启透明度
opacity: 0.3 // 透明设置
});
const points = new THREE.Points(geometry1, pointsMaterial); // 用几何体1构图
scene.add(plane, points);
const startPositions = geometry1.attributes.position; // tween动画起始位置数组
const endPositions = geometry2.attributes.position; // tween动画终点位置数组
const total = startPositions.count; // 点云的点数量
// tween动画设计 : 点位置交换规则 →
for(let i = 0; i < startPositions.count; i++) {
if (i % 10 === 0) continue; // 跳过被10整除的点
const tween = new TWEEN.Tween(startPositions.array)
.to({
: endPositions.array[(total - i - 1) * 3],
: endPositions.array[(total - i - 1) * 3 + 1],
: endPositions.array[(total - i - 1) * 3 + 2]
}, 5000 * Math.random() + 5000)
.delay(2000)
.easing(TWEEN.Easing.Exponential.Out)
.onUpdate(() => startPositions.needsUpdate = true)
.repeat(Infinity)
.repeatDelay(1500)
.yoyo(3000)
.start();
}
const animate = () => {
TWEEN.update();
const delta = clock.getDelta();
plane.rotation.z += delta / 5;
renderer.render(scene, camera);
requestAnimationFrame(animate);
};
const tweens = TWEEN.getAll();
aud.onplaying = aud.onpause = () => {
tweens.forEach(tween => aud.paused ? tween.pause() : tween.resume());
aud.paused ? clock.stop() : clock.start();
};
animate();
FS(papa, player);
</script> 帖子代码
<style>
#papa { margin: 30px 0; left: calc(50% - 81px); transform: translateX(-50%);width: clamp(600px, 90vw, 1400px); height: auto; aspect-ratio: 16/9; background: #000; box-shadow: 2px 2px 8px #000; display: grid; place-items: center; z-index: 1; position: relative; }
#btnFs { bottom: 30px; color: cyan; border-color: cyan !important; }
#player { position: absolute; width: 16vw; height: 16vw; border-radius: 50%; transform: translate(10px, -20px); cursor: pointer; z-index: 91; }
#vid {position: absolute; width: 100%; height: 100%; object-fit: cover; mask: radial-gradient(transparent 20%, red); -webkit-mask: radial-gradient(transparent 20%, red); opacity: .6; pointer-events: none; z-index: 90; }
</style>
<div id="papa">
<audio id="aud" src="https://music.163.com/song/media/outer/url?id=1984625" autoplay loop></audio>
<video id="vid" src="https://bpic.588ku.com/video_listen/588ku_video/25/04/08/17/14/59/video67f4e91361a39.mp4" autoplay loop muted></video>
<div id="player" title="播放/暂停"></div>
</div>
<script type="module">
import { THREE, scene, camera, renderer, clock, basic3 } from 'https://638183.freep.cn/638183/3dev/3/3basic.js?v=1.0';
import TWEEN from 'https://638183.freep.cn/638183/3dev/examples/jsm/libs/tween.module.js';
import { FS } from 'https://638183.freep.cn/638183/web/ku/FS.js';
basic3(papa, aud.paused);
const map = new THREE.TextureLoader().load('https://638183.freep.cn/638183/t24/w4/lzlx.webp');
map.colorSpace = THREE.SRGBColorSpace;
const planeGeometry = new THREE.PlaneGeometry(8, 4.5);
const planeMaterial = new THREE.MeshBasicMaterial({ map: map });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
const geometry1 = new THREE.TorusGeometry(0.4, 0.2, 40, 40); // 几何体1
geometry1.translate(-3, 0, 0); // 位置调整
const geometry2 = new THREE.TorusGeometry(0.4, 0.2, 40, 40); // 几何体2(仅取其点数据)
geometry2.translate(3, 0, 0); // 位置调整
const pointsMaterial = new THREE.PointsMaterial({
size: 0.05, // 尺寸
color: 'cyan', // 颜色
transparent: true, // 开启透明度
opacity: 0.3 // 透明设置
});
const points = new THREE.Points(geometry1, pointsMaterial); // 用几何体1构图
scene.add(plane, points);
const startPositions = geometry1.attributes.position; // tween动画起始位置数组
const endPositions = geometry2.attributes.position; // tween动画终点位置数组
const total = startPositions.count; // 点云的点数量
// tween动画设计 : 点位置交换规则 →
for(let i = 0; i < startPositions.count; i++) {
if (i % 10 === 0) continue; // 跳过被10整除的点
const tween = new TWEEN.Tween(startPositions.array)
.to({
: endPositions.array[(total - i - 1) * 3],
: endPositions.array[(total - i - 1) * 3 + 1],
: endPositions.array[(total - i - 1) * 3 + 2]
}, 5000 * Math.random() + 5000)
.delay(2000)
.easing(TWEEN.Easing.Exponential.Out)
.onUpdate(() => startPositions.needsUpdate = true)
.repeat(Infinity)
.repeatDelay(1500)
.yoyo(3000)
.start();
}
const animate = () => {
TWEEN.update();
const delta = clock.getDelta();
plane.rotation.z += delta / 5;
renderer.render(scene, camera);
requestAnimationFrame(animate);
};
const tweens = TWEEN.getAll();
aud.onplaying = aud.onpause = () => {
tweens.forEach(tween => aud.paused ? tween.pause() : tween.resume());
aud.paused ? clock.stop() : clock.start();
};
animate();
FS(papa, player);
</script>
帖子使用两个圆环几何体(TorusGeometry),第一个跟点云材质(PointsMaterial)构图,第二个仅取其点云数据。两个圆环几何体做对称偏移,目的是拉开距离,为点云的动画布置路径长度。
tween动画是点对点的运动,点云中的点按 → 这样的次序交叉换位。
旋转的背景也是 ThreeJS 动画,这是一个平面几何体 PlaneGeometry,在 Z 轴上旋转。 这个太奇妙了,刚开始还是圆环1的点朝圆环2跑,然后反过来跑,跑两次后达到均衡,就变成两边彼此跑了。这个看着太奇妙了{:4_199:} 圆环2被设置成了终点位置,交换点的方式还是 → 错位的,空间交错特别好看。
{:4_187:} 好像时间长点不去看它,再 看的时候又开始初始的点的分配了。 红影 发表于 2025-6-30 19:31
圆环2被设置成了终点位置,交换点的方式还是 → 错位的,空间交错特别好看。
最初的设计是走弧线。点太多,感觉把控不完美,放弃,换用现在的交叉换位方式。 红影 发表于 2025-6-30 19:37
好像时间长点不去看它,再 看的时候又开始初始的点的分配了。
这个有可能:tween有它的管理机制,3basic.js 模块考虑到关键帧动画的节流问题,但和tween的接洽没有实现机制,因为之前没有接触过tween 红影 发表于 2025-6-30 19:24
这个太奇妙了,刚开始还是圆环1的点朝圆环2跑,然后反过来跑,跑两次后达到均衡,就变成两边彼此跑了。这个 ...
一切是随机tween动画的结果:tween动画使用 for 循环为每一个参与动画的粒子设计,每一个粒子运行的时间是 5000 * Math.random() + 5000,最少5秒一个运行周期,最长10秒(几乎没有),以这样的时间差计算,一般会走两个来回才会均衡。 独特的景观,奇妙的效果,谢谢马老师精彩分享{:4_176:} 马黑黑 发表于 2025-6-30 19:42
最初的设计是走弧线。点太多,感觉把控不完美,放弃,换用现在的交叉换位方式。
天啊,那些点还能走弧线呢,这也太神奇了。 马黑黑 发表于 2025-6-30 19:44
这个有可能:tween有它的管理机制,3basic.js 模块考虑到关键帧动画的节流问题,但和tween的接洽没有实现 ...
是的,就是黑黑说的节流问题吧,估计这个也有的。{:4_187:} 马黑黑 发表于 2025-6-30 19:47
一切是随机tween动画的结果:tween动画使用 for 循环为每一个参与动画的粒子设计,每一个粒子运行的时间 ...
嗯嗯,正像黑黑说的,走了两个来回后才均衡的呢{:4_187:} 红影 发表于 2025-6-30 21:14
嗯嗯,正像黑黑说的,走了两个来回后才均衡的呢
也可以一开始就均衡的,一切看怎么设计 红影 发表于 2025-6-30 21:13
是的,就是黑黑说的节流问题吧,估计这个也有的。
对,tween是讲究节流的 红影 发表于 2025-6-30 21:12
天啊,那些点还能走弧线呢,这也太神奇了。
tween动画走弧线难度较大,因为它是线性的 杨帆 发表于 2025-6-30 19:57
独特的景观,奇妙的效果,谢谢马老师精彩分享
{:4_190:} 马黑黑 发表于 2025-6-30 21:33
也可以一开始就均衡的,一切看怎么设计
还是现在这样样子好,很神奇的感觉。 红影 发表于 2025-6-30 21:41
还是现在这样样子好,很神奇的感觉。
还好 马黑黑 发表于 2025-6-30 21:33
对,tween是讲究节流的
所以不去看的时候它就不演示了。