马黑黑 发表于 2024-6-28 12:50

CSS关键帧动画实现小球循环碰撞

<style>
.mum { position: relative; margin: 0; padding: 10px; font: normal 16px/20px Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; color: black; background: rgba(240, 240, 240,.95); box-shadow: 2px 2px 4px gray; border: thick groove lightblue; border-radius: 6px; }
.mum ::selection { background-color: rgba(0,100,100,.35); }
.mum div { margin: 0; padding: 0; }
.mum cl-cd { display: block; position: relative; margin: 0 0 0 50px; padding: 0 0 0 10px; white-space: pre-wrap; overflow-wrap: break-word; border-left: 1px solid silver; }
.mum cl-cd::before { position: absolute; content: attr(data-idx); width: 50px; color: gray; text-align: right; transform: translate(-70px); }
.tRed { color: red; }
.tBlue { color: blue; }
.tGreen { color: green; }
.tDarkRed { color: darkred; }
.tMagenta { color: magenta; }

#stage { --state: running; margin: 20px auto; padding: 6px 0; width: 600px; height: 40px; border: 1px solid gray; position: relative; }
.ball { position: absolute; left: 0; width: 40px; height: 40px; border-radius: 50%; background: linear-gradient(pink, darkgreen); cursor: pointer; animation: 2s linear forwards var(--state); }
#b1 { animation-name: move1; }
#b2 { left: 300px; background: linear-gradient(plum, navy); }
@keyframes move1 { from { transform: translate(0px); } to { transform: translate(260px); } }
@keyframes move2 { from {transform: translate(260px); } to { transform: translate(0px); } }
</style>

<h2>效果:</h2>
<div id="stage">
        <div id="b1" class="ball"></div>
        <div id="b2" class="ball"></div>
</div>
<h2>代码:</h2>
<div class='mum'>
<cl-cd data-idx="1">&lt;<span class="tDarkRed">style</span>&gt;</cl-cd>
<cl-cd data-idx="2">#stage { <span class="tBlue">--state:</span> running; <span class="tBlue">margin:</span> 20px auto; <span class="tBlue">padding:</span> 6px 0; <span class="tBlue">width:</span> 600px; <span class="tBlue">height:</span> 40px; <span class="tBlue">border:</span> 1px solid gray; <span class="tBlue">position:</span> relative; }</cl-cd>
<cl-cd data-idx="3">.ball { <span class="tBlue">position:</span> absolute; <span class="tBlue">left:</span> 0; <span class="tBlue">width:</span> 40px; <span class="tBlue">height:</span> 40px; <span class="tBlue">border-radius:</span> 50%; <span class="tBlue">background:</span> linear-gradient(pink, darkgreen); <span class="tBlue">cursor:</span> pointer; <span class="tBlue">animation:</span> 2s linear forwards <span class="tBlue">var</span>(--state); }</cl-cd>
<cl-cd data-idx="4">#b1 { <span class="tBlue">animation-name:</span> move1; }</cl-cd>
<cl-cd data-idx="5">#b2 { <span class="tBlue">left:</span> 300px; <span class="tBlue">background:</span> linear-gradient(plum, navy); }</cl-cd>
<cl-cd data-idx="6">@keyframes move1 { from { <span class="tBlue">transform:</span> translate(0px); } to { <span class="tBlue">transform:</span> translate(260px); } }</cl-cd>
<cl-cd data-idx="7">@keyframes move2 { from {<span class="tBlue">transform:</span> translate(260px); } to { <span class="tBlue">transform:</span> translate(0px); } }</cl-cd>
<cl-cd data-idx="8">&lt;<span class="tDarkRed">/style</span>&gt;</cl-cd>
<cl-cd data-idx="9">&nbsp;</cl-cd>
<cl-cd data-idx="10">&lt;<span class="tDarkRed">div</span> <span class="tRed">id</span>=<span class="tMagenta">"stage"</span>&gt;</cl-cd>
<cl-cd data-idx="11">&nbsp; &nbsp; &lt;<span class="tDarkRed">div</span> <span class="tRed">id</span>=<span class="tMagenta">"b1"</span> class=<span class="tMagenta">"ball"</span>&gt;&lt;<span class="tDarkRed">/div</span>&gt;</cl-cd>
<cl-cd data-idx="12">&nbsp; &nbsp; &lt;<span class="tDarkRed">div</span> <span class="tRed">id</span>=<span class="tMagenta">"b2"</span> class=<span class="tMagenta">"ball"</span>&gt;&lt;<span class="tDarkRed">/div</span>&gt;</cl-cd>
<cl-cd data-idx="13">&lt;<span class="tDarkRed">/div</span>&gt;</cl-cd>
<cl-cd data-idx="14">&nbsp;</cl-cd>
<cl-cd data-idx="15">&lt;<span class="tDarkRed">script</span>&gt;</cl-cd>
<cl-cd data-idx="16"><span class="tBlue">var</span> running = true; <span class="tGreen">//运行开关</span></cl-cd>
<cl-cd data-idx="17"><span class="tGreen">//小球1动画结束 :处理俩球的动画名称</span></cl-cd>
<cl-cd data-idx="18">b1.onanimationend = () =&gt; {</cl-cd>
<cl-cd data-idx="19">&nbsp; &nbsp; <span class="tBlue">var</span> name = <span class="tRed">window</span>.getComputedStyle(b1).getPropertyValue(<span class="tMagenta">'animation-name'</span>);</cl-cd>
<cl-cd data-idx="20">&nbsp; &nbsp; <span class="tBlue">if</span>(name === <span class="tMagenta">'move1'</span>) {</cl-cd>
<cl-cd data-idx="21">&nbsp; &nbsp; &nbsp; &nbsp; b1.style.animationName = <span class="tMagenta">''</span>;</cl-cd>
<cl-cd data-idx="22">&nbsp; &nbsp; &nbsp; &nbsp; b2.style.animationName = <span class="tMagenta">'move1'</span>;</cl-cd>
<cl-cd data-idx="23">&nbsp; &nbsp; }<span class="tBlue">else</span>{</cl-cd>
<cl-cd data-idx="24">&nbsp; &nbsp; &nbsp; &nbsp; b1.style.animationName = <span class="tMagenta">'move1'</span>;</cl-cd>
<cl-cd data-idx="25">&nbsp; &nbsp; }</cl-cd>
<cl-cd data-idx="26">};</cl-cd>
<cl-cd data-idx="27"><span class="tGreen">//小球2动画结束 :处理俩球的动画名称</span></cl-cd>
<cl-cd data-idx="28">b2.onanimationend = () =&gt; {</cl-cd>
<cl-cd data-idx="29">&nbsp; &nbsp; <span class="tBlue">var</span> name = <span class="tRed">window</span>.getComputedStyle(b2).getPropertyValue(<span class="tMagenta">'animation-name'</span>);</cl-cd>
<cl-cd data-idx="30">&nbsp; &nbsp; <span class="tBlue">if</span>(name === <span class="tMagenta">'move1'</span>) {</cl-cd>
<cl-cd data-idx="31">&nbsp; &nbsp; &nbsp; &nbsp; b2.style.animationName = <span class="tMagenta">'move2'</span>;</cl-cd>
<cl-cd data-idx="32">&nbsp; &nbsp; }<span class="tBlue">else</span>{</cl-cd>
<cl-cd data-idx="33">&nbsp; &nbsp; &nbsp; &nbsp; b1.style.animationName = <span class="tMagenta">''</span>;</cl-cd>
<cl-cd data-idx="34">&nbsp; &nbsp; &nbsp; &nbsp; b1.style.animationName = <span class="tMagenta">'move2'</span>;</cl-cd>
<cl-cd data-idx="35">&nbsp; &nbsp; }</cl-cd>
<cl-cd data-idx="36">};</cl-cd>
<cl-cd data-idx="37"><span class="tGreen">//小球点击事件 :播放、暂停切换</span></cl-cd>
<cl-cd data-idx="38">b1.onclick = b2.onclick = () =&gt; {</cl-cd>
<cl-cd data-idx="39">&nbsp; &nbsp; stage.style.setProperty(<span class="tMagenta">'--state'</span>, [<span class="tMagenta">'running'</span>,<span class="tMagenta">'paused'</span>][+running]);</cl-cd>
<cl-cd data-idx="40">&nbsp; &nbsp; running = !running;</cl-cd>
<cl-cd data-idx="41">};</cl-cd>
<cl-cd data-idx="42">&lt;<span class="tDarkRed">/script</span>&gt;</cl-cd>
</div>

<script>
var running = true;
b1.onanimationend = () => {
        var name = window.getComputedStyle(b1).getPropertyValue('animation-name');
        if(name === 'move1') {
                b1.style.animationName = '';
                b2.style.animationName = 'move1';
        }else{
                b1.style.animationName = 'move1';
        }
};
b2.onanimationend = () => {
        var name = window.getComputedStyle(b2).getPropertyValue('animation-name');
        if(name === 'move1') {
                b2.style.animationName = 'move2';
        }else{
                b1.style.animationName = '';
                b1.style.animationName = 'move2';
        }
};
b1.onclick = b2.onclick = () => {
        stage.style.setProperty('--state', ['running','paused'][+running]);
        running = !running;
};
</script>

马黑黑 发表于 2024-6-28 13:10

本帖最后由 马黑黑 于 2024-6-28 13:11 编辑

本示例:

(一)俩小球的布局需要注意一下,球1 left 为 0,球2 left 是 300px。酱紫俩小球的运行距离设定能达到预期:球1球2都能触碰左或右边界,俩球相碰撞的状态也恰到好处——亲密接触而不越位;

(二)关键帧动画两个,move1 和 move2,凭借 transform 属性 translate 平移的特征,两个 @keyframes 的设计仅需互换一下 from 和 to 的属性值即可,大大减少编程过程中的计算开销;

(三)为方便管理,俩小球各自定义好 id 表示,b1 和 b2;

(四)在 CSS 中设定好 b1 的 animation-name,即动画名称,为 move1;

(五)JS中,设计一个变量做关键帧动画的运行开关,小球被点击时根据开关变量值决定动画暂停还是继续;

(六)基于动画,元素原生拥有 animationend 事件(还有 animationstart 和 animationiteration 事件), animationend 事件指动画结束事件。注意,只有非循环运行的动画此属性才能生效,因此在 .ball 选择器的 animation 属性中使用了 animation-duration 缺省值,运行一次;.ball 选择器的 animation 属性还有一个奇怪的现象:没有设置动画名称!这是因为另外设置,在 CSS 中给 #b1 设置、在 JS 中动态设置;

(七)俩小球的 animationend 事件中,使用 window.getComputedStyle(element) 函数获得当前小球的动画名称,并依此做相关处理,以 操控小球运动结束时下一步做什么。这是巧妙的设计,但只是简简单单的实现算法而已。

梦江南 发表于 2024-6-28 13:14

代码真奇妙!小球碰撞像打台球一样,老师真厉害!{:4_187:}

马黑黑 发表于 2024-6-28 13:14

梦江南 发表于 2024-6-28 13:14
代码真奇妙!小球碰撞像打台球一样,老师真厉害!

{:4_173:}

梦江南 发表于 2024-6-28 13:18

马黑黑 发表于 2024-6-28 13:14


下午问好老师!辛苦了!{:4_190:}

红影 发表于 2024-6-28 16:36

能量守恒定律的代码实现{:4_173:}

红影 发表于 2024-6-28 16:37

看到这个,想起黑黑还做过给小球挂上线,组成了牛顿摆{:4_187:}

红影 发表于 2024-6-28 16:42

马黑黑 发表于 2024-6-28 13:10
本示例:

(一)俩小球的布局需要注意一下,球1 left 为 0,球2 left 是 300px。酱紫俩小球的运行距离设 ...

这个动画名称的来回切换的设计很巧妙,极大地简化了代码{:4_199:}

马黑黑 发表于 2024-6-28 16:44

红影 发表于 2024-6-28 16:37
看到这个,想起黑黑还做过给小球挂上线,组成了牛顿摆

嗯,都是用CSS关键帧动画做的,不过做的方法不同。

马黑黑 发表于 2024-6-28 16:44

梦江南 发表于 2024-6-28 13:18
下午问好老师!辛苦了!

{:4_191:}

马黑黑 发表于 2024-6-28 16:45

红影 发表于 2024-6-28 16:36
能量守恒定律的代码实现

理想环境下:真空、无摩擦力

马黑黑 发表于 2024-6-28 16:47

本帖最后由 马黑黑 于 2024-6-28 16:50 编辑 <br /><br />红影 发表于 2024-6-28 16:42
<p>这个动画名称的来回切换的设计很巧妙,极大地简化了代码
我试了一下另一种JS优化方法,不需要 window.getComputedStyle(ele) 函数,不过设计的依据过于抽象,想想还是这个示例一楼的实现方法牢靠。JS代码如下:<br><br></p>

<div class='mum'>
<cl-cd data-idx="1"><span class="tBlue">var</span> running = true, idx = 0;</cl-cd>
<cl-cd data-idx="2">&nbsp;</cl-cd>
<cl-cd data-idx="3">b1.onanimationend = () =&gt; {</cl-cd>
<cl-cd data-idx="4">&nbsp; &nbsp; <span class="tBlue">let</span> ar = [<span class="tMagenta">''</span>, <span class="tMagenta">'move1'</span>, <span class="tMagenta">'move2'</span>, <span class="tMagenta">''</span>];</cl-cd>
<cl-cd data-idx="5">&nbsp; &nbsp; b1.style.animationName = ar;</cl-cd>
<cl-cd data-idx="6">&nbsp; &nbsp; idx = (idx +1) % 4;</cl-cd>
<cl-cd data-idx="7">&nbsp; &nbsp; b2.style.animationName = ar;</cl-cd>
<cl-cd data-idx="8">};</cl-cd>
<cl-cd data-idx="9">&nbsp;</cl-cd>
<cl-cd data-idx="10">b2.onanimationend = () =&gt; {</cl-cd>
<cl-cd data-idx="11">&nbsp; &nbsp; <span class="tBlue">let</span> ar = [<span class="tMagenta">''</span>, <span class="tMagenta">'move2'</span>, <span class="tMagenta">''</span>, <span class="tMagenta">'move2'</span>];</cl-cd>
<cl-cd data-idx="12">&nbsp; &nbsp; b2.style.animationName = ar;</cl-cd>
<cl-cd data-idx="13">&nbsp; &nbsp; idx = (idx +1) % 4;</cl-cd>
<cl-cd data-idx="14">&nbsp; &nbsp; b1.style.animationName = ar;</cl-cd>
<cl-cd data-idx="15">};</cl-cd>
<cl-cd data-idx="16">&nbsp;</cl-cd>
<cl-cd data-idx="17">b1.onclick = b2.onclick = () =&gt; {</cl-cd>
<cl-cd data-idx="18">&nbsp; &nbsp; stage.style.setProperty(<span class="tMagenta">'--state'</span>, [<span class="tMagenta">'running'</span>,<span class="tMagenta">'paused'</span>][+running]);</cl-cd>
<cl-cd data-idx="19">&nbsp; &nbsp; running = !running;</cl-cd>
<cl-cd data-idx="20">};</cl-cd>
</div>

红影 发表于 2024-6-29 22:09

马黑黑 发表于 2024-6-28 16:45
理想环境下:真空、无摩擦力

完全理想化的环境还是挺难的{:4_173:}

红影 发表于 2024-6-29 22:10

马黑黑 发表于 2024-6-28 16:47
本帖最后由 马黑黑 于 2024-6-28 16:50 编辑
我试了一下另一种JS优化方法,不需要 window.getComputedSt ...

是啊,这个的确太难理解了。

红影 发表于 2024-6-29 22:11

马黑黑 发表于 2024-6-28 16:44
嗯,都是用CSS关键帧动画做的,不过做的方法不同。

黑黑厉害,脑筋很灵活{:4_199:}

马黑黑 发表于 2024-6-29 23:01

红影 发表于 2024-6-29 22:11
黑黑厉害,脑筋很灵活

嗯,比小学三年级的学生灵活那么一点点

马黑黑 发表于 2024-6-29 23:02

红影 发表于 2024-6-29 22:10
是啊,这个的确太难理解了。

但是也挺好用的

马黑黑 发表于 2024-6-29 23:02

红影 发表于 2024-6-29 22:09
完全理想化的环境还是挺难的

不存在的

红影 发表于 2024-6-30 15:38

马黑黑 发表于 2024-6-29 23:01
嗯,比小学三年级的学生灵活那么一点点

什么话,大学生也比不过你啊,说到这我后悔没读博,否则可以说博士生也比不过你了{:4_173:}

红影 发表于 2024-6-30 15:39

马黑黑 发表于 2024-6-29 23:02
但是也挺好用的

还是能学一点是一点吧,这个扩展的还没能力学{:4_173:}
页: [1] 2 3 4
查看完整版本: CSS关键帧动画实现小球循环碰撞