Between Two Worlds
<style>#papa { margin: 30px 0; left: calc(50% - 81px); transform: translateX(-50%); width: clamp(600px, 90vw, 1400px); height: auto; aspect-ratio: 16/9; background: #333 url('https://638183.freep.cn/638183/t24/6/bt2w.jpg') no-repeat center/cover; box-shadow: 2px 2px 8px #000; display: grid; place-items: center; perspective: 600px; transform-style: preserve-3d; overflow: hidden; z-index: 1; position: relative; --state: running; --hh: 190px; }
.son { position: absolute; width: 30px; height: 30px; border-radius: 50%; background: linear-gradient(35deg, tan, skyblue); opacity: 0; filter: drop-shadow(4px 6px 8px #333;); cursor: pointer; }
#btnFs { bottom: 30px; color: silver; border-color: silver !important; }
#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; }
#player { position: absolute; width: 200px; height: 200px; filter: drop-shadow(0 0 4px gray); display: grid; place-items: center; }
#player::before, #player::after { position: absolute; content: ''; }
#player::before { width: 6px; height: var(--hh); background: linear-gradient(to right, tan, yellow, tan); top: 50%; }
#player::after { width: 100%; height: 100%; background: url('https://638183.freep.cn/638183/small/2025/fl101.png') no-repeat center/cover; transform: rotateX(45deg) rotateY(5deg) rotateZ(0); cursor: pointer; animation: rot3d 6s linear infinite var(--state); }
@keyframes rot3d { to { transform: rotateX(45deg) rotateY(5deg) rotateZ(360deg); } }
</style>
<div id="papa">
<audio id="aud" src="https://music.163.com/song/media/outer/url?id=1920534254" autoplay loop></audio>
<video id="vid" src="https://bpic.588ku.com/video_listen/588ku_video/22/11/03/05/45/26/video6362e4f65d2be.mp4" autoplay loop muted></video>
<div id="player"></div>
</div>
<script type="module">
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';
let sons = [], tweens = [], total = 40, time = 2000;
let target = { x: papa.clientWidth / 4, z: 300, a: 27 };
Array.from({length: total}).forEach(son => {
son = document.createElement('div');
son.className = 'son';
son.title = 'Alt+X';
son.onclick = () => player.click();
son.style.cssText += `
background: linear-gradient(
35deg,
#${Math.random().toString(16).substring(2,8)},
#${Math.random().toString(16).substring(2,8)}
);
`;
sons.push(son);
papa.appendChild(son);
});
sons.forEach( (son, key) => {
const pos = { x: 0, z: 0, a: 0 };
const ax = key % 2 === 0 ? 126 : 0;
const tween = new TWEEN.Tween(pos)
.to(target, time)
.onUpdate( () => {
son.style.setProperty(
'transform',
`rotateZ(${pos.a + ax}deg) translate3d(${pos.x}px, 0, ${pos.z}px)`
);
})
.onStart( () => {
son.style.setProperty('opacity', 0.8);
})
.onComplete ( () => {
setTimeout( () => {
if (!aud.paused) son.style.setProperty('opacity', 0);
}, time * 2);
})
.dynamic(true);
tweens.push(tween);
});
tweens.forEach( (tween, key) => {
tween.chain(tweens[(key + 1) % tweens.length]);
});
const animate = () => {
TWEEN.update();
requestAnimationFrame(animate);
};
aud.onplaying = aud.onpause = () => {
tweens.forEach(tween => aud.paused ? tween.pause() : tween.resume());
};
window.onresize = () => {
target.x = papa.clientWidth / 4;
papa.style.setProperty('--hh', 190 * papa.clientHeight / 788 + 'px');
}
tweens.start();
animate();
FS(papa, player);
papa.style.setProperty('--hh', 190 * papa.clientHeight / 788 + 'px');
</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: #333 url('https://638183.freep.cn/638183/t24/6/bt2w.jpg') no-repeat center/cover; box-shadow: 2px 2px 8px #000; display: grid; place-items: center; perspective: 600px; transform-style: preserve-3d; overflow: hidden; z-index: 1; position: relative; --state: running; --hh: 190px; }
.son { position: absolute; width: 30px; height: 30px; border-radius: 50%; background: linear-gradient(35deg, tan, skyblue); opacity: 0; filter: drop-shadow(4px 6px 8px #333;); cursor: pointer; }
#btnFs { bottom: 30px; color: silver; border-color: silver !important; }
#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; }
#player { position: absolute; width: 200px; height: 200px; filter: drop-shadow(0 0 4px gray); display: grid; place-items: center; }
#player::before, #player::after { position: absolute; content: ''; }
#player::before { width: 6px; height: var(--hh); background: linear-gradient(to right, tan, yellow, tan); top: 50%; }
#player::after { width: 100%; height: 100%; background: url('https://638183.freep.cn/638183/small/2025/fl101.png') no-repeat center/cover; transform: rotateX(45deg) rotateY(5deg) rotateZ(0); cursor: pointer; animation: rot3d 6s linear infinite var(--state); }
@keyframes rot3d { to { transform: rotateX(45deg) rotateY(5deg) rotateZ(360deg); } }
</style>
<div id="papa">
<audio id="aud" src="https://music.163.com/song/media/outer/url?id=1920534254" autoplay loop></audio>
<video id="vid" src="https://bpic.588ku.com/video_listen/588ku_video/22/11/03/05/45/26/video6362e4f65d2be.mp4" autoplay loop muted></video>
<div id="player"></div>
</div>
<script type="module">
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';
let sons = [], tweens = [], total = 40, time = 2000;
let target = { x: papa.clientWidth / 4, z: 300, a: 27 };
Array.from({length: total}).forEach(son => {
son = document.createElement('div');
son.className = 'son';
son.title = 'Alt+X';
son.onclick = () => player.click();
son.style.cssText += `
background: linear-gradient(
35deg,
#${Math.random().toString(16).substring(2,8)},
#${Math.random().toString(16).substring(2,8)}
);
`;
sons.push(son);
papa.appendChild(son);
});
sons.forEach( (son, key) => {
const pos = { x: 0, z: 0, a: 0 };
const ax = key % 2 === 0 ? 126 : 0;
const tween = new TWEEN.Tween(pos)
.to(target, time)
.onUpdate( () => {
son.style.setProperty(
'transform',
`rotateZ(${pos.a + ax}deg) translate3d(${pos.x}px, 0, ${pos.z}px)`
);
})
.onStart( () => {
son.style.setProperty('opacity', 0.8);
})
.onComplete ( () => {
setTimeout( () => {
if (!aud.paused) son.style.setProperty('opacity', 0);
}, time * 2);
})
.dynamic(true);
tweens.push(tween);
});
tweens.forEach( (tween, key) => {
tween.chain(tweens[(key + 1) % tweens.length]);
});
const animate = () => {
TWEEN.update();
requestAnimationFrame(animate);
};
aud.onplaying = aud.onpause = () => {
tweens.forEach(tween => aud.paused ? tween.pause() : tween.resume());
};
window.onresize = () => {
target.x = papa.clientWidth / 4;
papa.style.setProperty('--hh', 190 * papa.clientHeight / 788 + 'px');
}
tweens.start();
animate();
FS(papa, player);
papa.style.setProperty('--hh', 190 * papa.clientHeight / 788 + 'px');
</script>
本帖最后由 马黑黑 于 2025-7-7 18:07 编辑
本帖:主要演示HTML元素的3d转换
关于3d,请参考 理解CSS 3d transform转换 - 马黑黑教程专版 - 花潮论坛 - Powered by Discuz!
帖子容器设置三维透视视图(perspective)、转换模式(transform-style),转换原点(transform-origin)则使用缺省默认值(center)。小播和小球均采用3d转换进行运动:小播伪元素旋转、小球通过 transform 属性的 rotateZ、translate3d(x,y,z) 相关数据的持续变更(由tween.js驱动)实现曲线运动。
tween.js 的作用在本帖是驱动小球。小球有若干个,四、五个以上就行,多一点也无所谓,同一时间里活跃的球最多三个。小球接受点击操作,功能和小播一样。tween代码中的 61 行,dynamic(true),用于支持动态设置它所维护的数据,这主要源于窗口尺寸的改变(如最大化、全屏等),需要适时更改运动路线相关数据以运动能自适应窗口的尺寸变更。
代码中还有两句相同的语句,
papa.style.setProperty('--hh', 190 * papa.clientHeight / 788 + 'px');
它也是出于自适应窗口尺寸变化的需求,确保小播的杆杆总在相应的位置。在电脑端表现良好,移动端仅做了虚拟设备的测试,也没问题。
先看到面面效果,蓝天白云,高耸的立杆,酷炫的3D旋转小播,超有感染力的画面,加上视频动态效果,整个贴子清澈透亮。。。大爱大赞
小球分别向两个方向做曲线运动,一边向下抛物线,一边向上抛物线,细看还是有些差别的。 小播杆杆的高度设置依据:在1920*1080分别率下,将其设置为190正好令其根部紧贴围栏,此时,papa的高度为788px,故以 190*原始高度/788 计算杆杆的自适应窗口高度尺寸。
现在再来理解这句话,原来高度的设定跟分辨率有关系,
艾玛,进到贴子里后看得晕乎乎。。。{:4_173:} const ax = key % 2 === 0 ? 126 : 0;这句设定有一部分粒子分跑到另一边,{:4_173:}
看了好久没找出来。。 这三位的小播好像在转动过程中甩出的粒子小球,看着很有意思{:4_187:} 红影 发表于 2025-7-7 19:21
这三位的小播好像在转动过程中甩出的粒子小球,看着很有意思
感谢意思意思 花飞飞 发表于 2025-7-7 19:12
const ax = key % 2 === 0 ? 126 : 0;这句设定有一部分粒子分跑到另一边,
看了好久没找出来。。
幼儿园大班的数学课里有取余数的知识扩展介绍的 粒子小球还是分别往两边跑的,而且小播和小球都能被点击而暂停呢{:4_187:} 这个帖子黑黑把3D玩到了极致,厉害{:4_187:} 花飞飞 发表于 2025-7-7 18:41
小播杆杆的高度设置依据:在1920*1080分别率下,将其设置为190正好令其根部紧贴围栏,此时,papa的高度为78 ...
之所以有关系是因为帖子容器采用自适应宽高的元素,它能根据显示设备的尺寸决定自己的尺寸。 花飞飞 发表于 2025-7-7 18:19
先看到面面效果,蓝天白云,高耸的立杆,酷炫的3D旋转小播,超有感染力的画面,加上视频动态效果,整个贴子 ...
谢赞 红影 发表于 2025-7-7 19:23
这个帖子黑黑把3D玩到了极致,厉害
额,一切在那个理解文章里说了。当然,3d转换内容不止这些 红影 发表于 2025-7-7 19:22
粒子小球还是分别往两边跑的,而且小播和小球都能被点击而暂停呢
对,小球最初我设计为小播的一部分,酱紫它的点击是自然而然地继承了,不过后来又改了,它们和小播是平行的关系,即兄弟关系 马黑黑 发表于 2025-7-7 19:22
幼儿园大班的数学课里有取余数的知识扩展介绍的
{:4_173:}你博士班你了不起,公式随意用。。学前小小班还没升到大班 花飞飞 发表于 2025-7-7 19:38
你博士班你了不起,公式随意用。。学前小小班还没升到大班
那得赶紧升级 马黑黑 发表于 2025-7-7 19:23
之所以有关系是因为帖子容器采用自适应宽高的元素,它能根据显示设备的尺寸决定自己的尺寸。
所有的效果都要考虑自适应,对设计贴子的人来说更复杂了。。。
加了自适应的作品,现在手机怎么看都相当完美 马黑黑 发表于 2025-7-7 19:24
谢赞
谢个先先贝{:4_173:} 马黑黑 发表于 2025-7-7 19:39
那得赶紧升级
升级很要紧,因为代码真。函数一大把,学学头不晕{:4_173:}