马黑黑 发表于 2025-7-7 17:48

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>

马黑黑 发表于 2025-7-7 17:49

帖子代码

<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 17:50

本帖最后由 马黑黑 于 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');

它也是出于自适应窗口尺寸变化的需求,确保小播的杆杆总在相应的位置。在电脑端表现良好,移动端仅做了虚拟设备的测试,也没问题。

花飞飞 发表于 2025-7-7 18:19

先看到面面效果,蓝天白云,高耸的立杆,酷炫的3D旋转小播,超有感染力的画面,加上视频动态效果,整个贴子清澈透亮。。。大爱大赞
小球分别向两个方向做曲线运动,一边向下抛物线,一边向上抛物线,细看还是有些差别的。

花飞飞 发表于 2025-7-7 18:41

小播杆杆的高度设置依据:在1920*1080分别率下,将其设置为190正好令其根部紧贴围栏,此时,papa的高度为788px,故以 190*原始高度/788 计算杆杆的自适应窗口高度尺寸。
现在再来理解这句话,原来高度的设定跟分辨率有关系,
艾玛,进到贴子里后看得晕乎乎。。。{:4_173:}

花飞飞 发表于 2025-7-7 19:12

const ax = key % 2 === 0 ? 126 : 0;这句设定有一部分粒子分跑到另一边,{:4_173:}
看了好久没找出来。。

红影 发表于 2025-7-7 19:21

这三位的小播好像在转动过程中甩出的粒子小球,看着很有意思{:4_187:}

马黑黑 发表于 2025-7-7 19:22

红影 发表于 2025-7-7 19:21
这三位的小播好像在转动过程中甩出的粒子小球,看着很有意思

感谢意思意思

马黑黑 发表于 2025-7-7 19:22

花飞飞 发表于 2025-7-7 19:12
const ax = key % 2 === 0 ? 126 : 0;这句设定有一部分粒子分跑到另一边,
看了好久没找出来。。

幼儿园大班的数学课里有取余数的知识扩展介绍的

红影 发表于 2025-7-7 19:22

粒子小球还是分别往两边跑的,而且小播和小球都能被点击而暂停呢{:4_187:}

红影 发表于 2025-7-7 19:23

这个帖子黑黑把3D玩到了极致,厉害{:4_187:}

马黑黑 发表于 2025-7-7 19:23

花飞飞 发表于 2025-7-7 18:41
小播杆杆的高度设置依据:在1920*1080分别率下,将其设置为190正好令其根部紧贴围栏,此时,papa的高度为78 ...

之所以有关系是因为帖子容器采用自适应宽高的元素,它能根据显示设备的尺寸决定自己的尺寸。

马黑黑 发表于 2025-7-7 19:24

花飞飞 发表于 2025-7-7 18:19
先看到面面效果,蓝天白云,高耸的立杆,酷炫的3D旋转小播,超有感染力的画面,加上视频动态效果,整个贴子 ...

谢赞

马黑黑 发表于 2025-7-7 19:25

红影 发表于 2025-7-7 19:23
这个帖子黑黑把3D玩到了极致,厉害

额,一切在那个理解文章里说了。当然,3d转换内容不止这些

马黑黑 发表于 2025-7-7 19:26

红影 发表于 2025-7-7 19:22
粒子小球还是分别往两边跑的,而且小播和小球都能被点击而暂停呢

对,小球最初我设计为小播的一部分,酱紫它的点击是自然而然地继承了,不过后来又改了,它们和小播是平行的关系,即兄弟关系

花飞飞 发表于 2025-7-7 19:38

马黑黑 发表于 2025-7-7 19:22
幼儿园大班的数学课里有取余数的知识扩展介绍的

{:4_173:}你博士班你了不起,公式随意用。。学前小小班还没升到大班

马黑黑 发表于 2025-7-7 19:39

花飞飞 发表于 2025-7-7 19:38
你博士班你了不起,公式随意用。。学前小小班还没升到大班

那得赶紧升级

花飞飞 发表于 2025-7-7 19:41

马黑黑 发表于 2025-7-7 19:23
之所以有关系是因为帖子容器采用自适应宽高的元素,它能根据显示设备的尺寸决定自己的尺寸。

所有的效果都要考虑自适应,对设计贴子的人来说更复杂了。。。
加了自适应的作品,现在手机怎么看都相当完美

花飞飞 发表于 2025-7-7 19:42

马黑黑 发表于 2025-7-7 19:24
谢赞

谢个先先贝{:4_173:}

花飞飞 发表于 2025-7-7 19:43

马黑黑 发表于 2025-7-7 19:39
那得赶紧升级

升级很要紧,因为代码真。函数一大把,学学头不晕{:4_173:}
页: [1] 2 3 4
查看完整版本: Between Two Worlds