马黑黑 发表于 2024-6-7 07:58

JS原生animate动画函数之复合动画与缓动动画

本帖最后由 马黑黑 于 2024-6-7 08:01 编辑 <br /><br /><style>
.artBox p { margin: 12px 0; font: normal 1.2em / 1.5em sans-serif; }
.artBox div { margin: 12px 0; }

.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; }
</style>

<div class="artBox">
        <p>这里的<span class="rBlue">复合动画</span>指动画的混合运行,每一帧执行两个或以上的动画,比如元素在旋转的同时还变换色相。这在CSS关键帧动画中非常简单,在动画帧描述中用分号隔开不同的动画描述即可。而在JS原生animate动画中实现复合动画,我们需要在 keyframes 参数即动画参数上做文章:keyframes 参数是动画各帧的描述对象,它可以是数组形式的对象集合,试看如下代码:</p>
        <div class='mum'>
<cl-cd data-idx="1"><span class="tBlue">const</span> ani = [</cl-cd>
<cl-cd data-idx="2">&nbsp; &nbsp; {<span class="tBlue">transform:</span> <span class="tMagenta">'rotate(0)'</span>, <span class="tBlue">filter:</span> <span class="tMagenta">'hue-rotate(0)'</span>},</cl-cd>
<cl-cd data-idx="3">&nbsp; &nbsp; {<span class="tBlue">transform:</span> <span class="tMagenta">'rotate(360deg)'</span>, <span class="tBlue">filter:</span> <span class="tMagenta">'hue-rotate(240deg)'</span>}</cl-cd>
<cl-cd data-idx="4">];</cl-cd>
        </div>
        <p>以上,我们定义了一个名为 ani 的动画数组,数组的子项是两个对象,每一个对象都有两个键值对,健名分别是 transform 和 filter,分别用以描述转换动画和滤镜动画。首尾帧规定了旋转动画从 0 到 360 度、色相从 0 到 240 度进行旋转、变色。如果需要,每一帧还可以添加别的动画,都是用键值对的方式表达,两个键值对之间用小角逗号隔开即可,最末一个键值对后面有没有逗号都可以,从美观的角度考虑应该不要。</p>
        <p>我们给animate函数加上 options 参数,动画就可以立马运行。下面的代码,在<a href="https://www.huachaowang.com/forum.php?mod=viewthread&tid=76375&extra=page%3D1" target="_blank">《初识JS原生animate动画函数》</a>的基础上略作改动,图片除了旋转还改变色相,动画可控,可以将代码复制到<a href="http://mhh.52qingyin.cn/api/pcode/" target="_blank">pencil code</a>运行以查看效果:</p>
        <div class='mum'>
<cl-cd data-idx="1">&lt;<span class="tDarkRed">img</span> <span class="tRed">id</span>=<span class="tMagenta">"h7"</span> alt=<span class="tMagenta">""</span> width=<span class="tMagenta">"200"</span> src=<span class="tMagenta">"https://638183.freep.cn/638183/web/svg/sunfl-2.svg"</span> /&gt;</cl-cd>
<cl-cd data-idx="2"> </cl-cd>
<cl-cd data-idx="3">&lt;<span class="tDarkRed">script</span>&gt;</cl-cd>
<cl-cd data-idx="4"><span class="tGreen">// ani数组 :keyframes参数,旋转、变色动画描述(首帧可以省略)</span></cl-cd>
<cl-cd data-idx="5"><span class="tBlue">const</span> ani = [</cl-cd>
<cl-cd data-idx="6">    {<span class="tBlue">transform:</span> <span class="tMagenta">'rotate(0)'</span>, <span class="tBlue">filter:</span> <span class="tMagenta">'hue-rotate(0)'</span>},</cl-cd>
<cl-cd data-idx="7">    {<span class="tBlue">transform:</span> <span class="tMagenta">'rotate(360deg)'</span>, <span class="tBlue">filter:</span> <span class="tMagenta">'hue-rotate(240deg)'</span>}</cl-cd>
<cl-cd data-idx="8">];</cl-cd>
<cl-cd data-idx="9"> </cl-cd>
<cl-cd data-idx="10"><span class="tGreen">// aniAttr对象 :options参数,动画属性值列表</span></cl-cd>
<cl-cd data-idx="11"><span class="tBlue">const</span> aniAttr = {</cl-cd>
<cl-cd data-idx="12">    <span class="tBlue">duration:</span> 8000,</cl-cd>
<cl-cd data-idx="13">    <span class="tBlue">iterations:</span> Infinity,</cl-cd>
<cl-cd data-idx="14">};</cl-cd>
<cl-cd data-idx="15"> </cl-cd>
<cl-cd data-idx="16"><span class="tGreen">// 运行并获得动画操作入口 h7Ani</span></cl-cd>
<cl-cd data-idx="17"><span class="tBlue">const</span> h7Ani = h7.animate(ani, aniAttr);</cl-cd>
<cl-cd data-idx="18"> </cl-cd>
<cl-cd data-idx="19"><span class="tGreen">//图片单击事件 : 暂停或继续动画</span></cl-cd>
<cl-cd data-idx="20">h7.onclick = () =&gt; h7Ani.playState === <span class="tMagenta">'running'</span> ? h7Ani.pause() : h7Ani.play();</cl-cd>
<cl-cd data-idx="21">&lt;<span class="tDarkRed">/script</span>&gt;</cl-cd>
</div>
        <p>复合动画的关键是animate函数参数1 keyframes 的表达,其方式是用数组来组织各帧动画,每一帧的动画用JS对象来表示,对象里头每一个键值对描述一种动画在该帧的状态,两个键值对间用小角逗号隔开。</p>
        <p><span class="tBlue">缓动动画</span>则是高级动画绕不开的技术话题。所谓缓动就是缓冲,指的是运动的物体由于受到阻力或加速度的影响,在运动过程中速度不断衰减或不断提升。加速的动画称为缓入动画,减速的动画叫做缓出动画。本文示例演示缓出动画,旋转的图片持续减速,以至最终停下——不过由于使用了 Infinity 动画属性,理论上旋转的图片是永远停不下来的,感兴趣的朋友请查阅<a href="https://cn.bing.com/search?q=%E8%8A%9D%E8%AF%BA%E6%82%96%E8%AE%BA&form=ANSPH1&refig=6662423f35f045b381200e742e310d7c&pc=U531" target="_blank">芝诺悖论</a>。</p>
        <p>animate动画对象有一个 playbackRate 属性,可读写,用于返回或设置动画的播放速率。我们可以借助定时器 setInterval 在设定时间范围内持续改变其值,例如乘以0.9,动画的播放速率就会不断地慢下来。假设我们的上述动画是环保而高端的9块9包邮的可充电设备,现在我们给它加入耗电机制,电力的减退导致它的运行速度不断减缓,直到我们给它充满电再让它满血回归:</p>
       
        <p><img id="h7" alt="" width="200" src="https://638183.freep.cn/638183/web/svg/sunfl-2.svg" /></p>
        <p><button id="charge" type="button" value="0">电量</button> &nbsp;<output id="rateMsg">1</output></p>
        <p>以下是上述示例的完整代码:</p>
        <div class='mum'>
<cl-cd data-idx="1">&lt;<span class="tDarkRed">p</span>&gt;&lt;<span class="tDarkRed">img</span> <span class="tRed">id</span>=<span class="tMagenta">"h7"</span> alt=<span class="tMagenta">""</span> width=<span class="tMagenta">"200"</span> src=<span class="tMagenta">"https://638183.freep.cn/638183/web/svg/sunfl-2.svg"</span> /&gt;&lt;<span class="tDarkRed">/p</span>&gt;</cl-cd>
<cl-cd data-idx="2">&lt;<span class="tDarkRed">p</span>&gt;&lt;<span class="tDarkRed">button</span> <span class="tRed">id</span>=<span class="tMagenta">"charge"</span> type=<span class="tMagenta">"button"</span> value=<span class="tMagenta">"btn"</span>&gt;电量&lt;<span class="tDarkRed">/button</span>&gt; &nbsp;&lt;<span class="tDarkRed">output</span> <span class="tRed">id</span>=<span class="tMagenta">"rateMsg"</span>&gt;&lt;<span class="tDarkRed">/output</span>&gt;&lt;<span class="tDarkRed">/p</span>&gt;</cl-cd>
<cl-cd data-idx="3">&nbsp;</cl-cd>
<cl-cd data-idx="4">&lt;<span class="tDarkRed">script</span>&gt;</cl-cd>
<cl-cd data-idx="5">&nbsp;</cl-cd>
<cl-cd data-idx="6"><span class="tBlue">const</span> ani = [</cl-cd>
<cl-cd data-idx="7">&nbsp; &nbsp; {<span class="tBlue">transform:</span> <span class="tMagenta">'rotate(0)'</span>, <span class="tBlue">filter:</span> <span class="tMagenta">'hue-rotate(0)'</span>},</cl-cd>
<cl-cd data-idx="8">&nbsp; &nbsp; {<span class="tBlue">transform:</span> <span class="tMagenta">'rotate(1800deg)'</span>, <span class="tBlue">filter:</span> <span class="tMagenta">'hue-rotate(240deg)'</span>}</cl-cd>
<cl-cd data-idx="9">];</cl-cd>
<cl-cd data-idx="10">&nbsp;</cl-cd>
<cl-cd data-idx="11"><span class="tBlue">const</span> aniAttr = {</cl-cd>
<cl-cd data-idx="12">&nbsp; &nbsp; <span class="tBlue">duration:</span> 10000,</cl-cd>
<cl-cd data-idx="13">&nbsp; &nbsp; <span class="tBlue">iterations:</span> Infinity,</cl-cd>
<cl-cd data-idx="14">};</cl-cd>
<cl-cd data-idx="15">&nbsp;</cl-cd>
<cl-cd data-idx="16"><span class="tBlue">const</span> h7Ani = h7.animate(ani, aniAttr);</cl-cd>
<cl-cd data-idx="17">&nbsp;</cl-cd>
<cl-cd data-idx="18">setInterval(() =&gt; {</cl-cd>
<cl-cd data-idx="19">&nbsp; &nbsp; <span class="tBlue">let</span> val = <span class="tRed">Math</span>.round(h7Ani.playbackRate * 100) / 100;</cl-cd>
<cl-cd data-idx="20">&nbsp; &nbsp; charge.innerHTML = val &lt;= 0.05 ? <span class="tMagenta">'&lt;<span class="tDarkRed">span</span> class="tRed"&gt;充电&lt;<span class="tDarkRed">/span</span>&gt;'</span> : <span class="tMagenta">'电量'</span>;</cl-cd>
<cl-cd data-idx="21">&nbsp; &nbsp; charge.value = val &lt;= 0.05 ? <span class="tMagenta">'0'</span> : <span class="tMagenta">'1'</span>;</cl-cd>
<cl-cd data-idx="22">&nbsp; &nbsp; rateMsg.value = val;</cl-cd>
<cl-cd data-idx="23">&nbsp; &nbsp; h7Ani.playbackRate *= 0.9;</cl-cd>
<cl-cd data-idx="24">},1000);</cl-cd>
<cl-cd data-idx="25">&nbsp;</cl-cd>
<cl-cd data-idx="26">charge.onclick = () =&gt; {</cl-cd>
<cl-cd data-idx="27">&nbsp; &nbsp; <span class="tBlue">if</span>(charge.value === <span class="tMagenta">'0'</span>) h7Ani.playbackRate = 1; </cl-cd>
<cl-cd data-idx="28">};</cl-cd>
<cl-cd data-idx="29">&nbsp;</cl-cd>
<cl-cd data-idx="30">&lt;<span class="tDarkRed">/script</span>&gt;</cl-cd>
        </div>
        <p>重点在 setInterval 定时器,它每隔 1000 毫秒令 h7Ani 动画的播放速率变为上一秒速率的 90%,从而达到动画速率持续下降也就是缓动的目的,代码在第 23 行。定时器还顺带改变充电按钮的文本,并实时显示当前电量。关于电量的计算,为了避免科学计数的出现影响按钮操作,我们以四舍五入的方法只取浮点数小数点后两位数,所以电量最终会变为 0,符合现实生活规律,芝诺悖论也因此不攻自破。</p>
        <p>充电按钮的点击事件,负责给电器充电,但只有在电量不足 5% 时才能充电。这是标准的超级快充,点击一下就能充满电,处于充电行业全宇宙遥遥领先的地位。</p>
</div>

<script>
(function() {
        const ani = [
                {transform: 'rotate(0)', filter: 'hue-rotate(0)'},
                {transform: 'rotate(3600deg)', filter: 'hue-rotate(240deg)'}
        ];

        const aniAttr = {
                duration: 10000,
                iterations: Infinity,
        };

        const h7Ani = h7.animate(ani, aniAttr);

        setInterval(() => {
                let val = Math.round(h7Ani.playbackRate * 100) / 100;
                charge.innerHTML = val <= 0.05 ? '<span class="tRed">充电</span>' : '电量';
                charge.value = val <= 0.05 ? '0' : '1';
                rateMsg.value = val;
                h7Ani.playbackRate *= 0.9;
        },1000);

        charge.onclick = () => {
                if(charge.value === '0') {
                        h7Ani.playbackRate = 1;
                        rateMsg.value = '1';
                }
        };
})();
</script>

红影 发表于 2024-6-7 09:36

先仔细看了前面一个匀速的旋转+色相变化,看着那个颜色变化比后面这个变速的好看{:4_173:}

红影 发表于 2024-6-7 09:38

“我们以四舍五入的方法只取浮点数小数点后两位数,所以电量最终会变为 0,符合现实生活规律,芝诺悖论也因此不攻自破。”

关于芝诺悖论不攻自破不赞同,你只是取两位小数,所以出现0,如果位数不限,不可能变0 ,所以悖论成立{:4_173:}

红影 发表于 2024-6-7 09:45

rateMsg.value = val; 这句没看懂,主要没明白rateMsg是什么意思。

然后再往前看开始的设定,这个是自己给的名称吧。

马黑黑 发表于 2024-6-7 12:22

红影 发表于 2024-6-7 09:45
rateMsg.value = val; 这句没看懂,主要没明白rateMsg是什么意思。

然后再往前看开始的设定,这个是自己 ...

这是 <output> 标签的 id,电量信息显示

马黑黑 发表于 2024-6-7 12:34

红影 发表于 2024-6-7 09:38
“我们以四舍五入的方法只取浮点数小数点后两位数,所以电量最终会变为 0,符合现实生活规律,芝诺悖论也因 ...

若以芝诺悖论看,龟兔赛跑,即使比赛中不节外生枝,兔子也永远追不上乌龟。不过它是悖论,不攻自破。它的最大问题是忽视了最重要的东东,基础。

基础这里指客观存在的因素。设龟兔时速分别为5公里、0.5公里,AB两个点距离5公里,比赛双方从A点出发,谁先到B点谁获胜。显然,比赛结果不言而喻,但芝诺悖论不看这些现实存在的速度、距离,而是从另一个角度去论证兔子追不上乌龟,采用的方式是动态追赶中双方位置的动态变化,它只成立于非事实基础上的假定理论层面。

这类悖论其实在主导着很多人虚幻地生活着。

马黑黑 发表于 2024-6-7 12:37

红影 发表于 2024-6-7 09:36
先仔细看了前面一个匀速的旋转+色相变化,看着那个颜色变化比后面这个变速的好看

匀速变化之美往往让人容易接受,不过这样的美偏于平淡,缺失跌宕起伏的或能从中得到意外惊喜的增倍的美。

南无月 发表于 2024-6-7 12:45

给小播充电的想法和实现,真是一绝,叹为观止。。{:4_199:}

马黑黑 发表于 2024-6-7 12:45

南无月 发表于 2024-6-7 12:45
给小播充电的想法和实现,真是一绝,叹为观止。。

这个容易想到呀

南无月 发表于 2024-6-7 12:47

{:4_170:}一键快充看着极度舒适。

马黑黑 发表于 2024-6-7 12:48

南无月 发表于 2024-6-7 12:47
一键快充看着极度舒适。

太厉害了

南无月 发表于 2024-6-7 12:48

马黑黑 发表于 2024-6-7 12:45
这个容易想到呀

反正我看到的时是惊呆了。。。
你脑回路与众不同的。。{:4_199:}

马黑黑 发表于 2024-6-7 13:55

南无月 发表于 2024-6-7 12:48
反正我看到的时是惊呆了。。。
你脑回路与众不同的。。

一样一样的

马黑黑 发表于 2024-6-7 13:55

南无月 发表于 2024-6-7 12:45
给小播充电的想法和实现,真是一绝,叹为观止。。

{:4_190:}

红影 发表于 2024-6-7 16:47

马黑黑 发表于 2024-6-7 12:22
这是标签的 id,电量信息显示

嗯,是自己设置的id明白,以为这个也是命令呢{:4_173:}

红影 发表于 2024-6-7 16:50

马黑黑 发表于 2024-6-7 12:34
若以芝诺悖论看,龟兔赛跑,即使比赛中不节外生枝,兔子也永远追不上乌龟。不过它是悖论,不攻自破。它的 ...

所以这个悖论也挺好玩的,感觉还挺有道理的。现实里是谬论,也许放到某种特定条件里它是成立的。

红影 发表于 2024-6-7 16:50

马黑黑 发表于 2024-6-7 12:37
匀速变化之美往往让人容易接受,不过这样的美偏于平淡,缺失跌宕起伏的或能从中得到意外惊喜的增倍的美。

哈哈,跌宕起伏的更刺激呗{:4_173:}

南无月 发表于 2024-6-7 17:58

马黑黑 发表于 2024-6-7 12:48
太厉害了

虽然大容量电池暂时还看不到。。
一键充电这事儿到是先实现了。{:4_170:}

马黑黑 发表于 2024-6-7 17:59

南无月 发表于 2024-6-7 17:58
虽然大容量电池暂时还看不到。。
一键充电这事儿到是先实现了。

秒充

南无月 发表于 2024-6-7 17:59

马黑黑 发表于 2024-6-7 13:55
一样一样的

你跟我一样的么,才怪。。{:4_170:}
我有你那么好用的脑袋做梦都笑醒
页: [1] 2 3 4 5 6
查看完整版本: JS原生animate动画函数之复合动画与缓动动画