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"> {<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"> {<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"><<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> /></cl-cd>
<cl-cd data-idx="2"> </cl-cd>
<cl-cd data-idx="3"><<span class="tDarkRed">script</span>></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 = () => h7Ani.playState === <span class="tMagenta">'running'</span> ? h7Ani.pause() : h7Ani.play();</cl-cd>
<cl-cd data-idx="21"><<span class="tDarkRed">/script</span>></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> <output id="rateMsg">1</output></p>
<p>以下是上述示例的完整代码:</p>
<div class='mum'>
<cl-cd data-idx="1"><<span class="tDarkRed">p</span>><<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> /><<span class="tDarkRed">/p</span>></cl-cd>
<cl-cd data-idx="2"><<span class="tDarkRed">p</span>><<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>>电量<<span class="tDarkRed">/button</span>> <<span class="tDarkRed">output</span> <span class="tRed">id</span>=<span class="tMagenta">"rateMsg"</span>><<span class="tDarkRed">/output</span>><<span class="tDarkRed">/p</span>></cl-cd>
<cl-cd data-idx="3"> </cl-cd>
<cl-cd data-idx="4"><<span class="tDarkRed">script</span>></cl-cd>
<cl-cd data-idx="5"> </cl-cd>
<cl-cd data-idx="6"><span class="tBlue">const</span> ani = [</cl-cd>
<cl-cd data-idx="7"> {<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"> {<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"> </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> 10000,</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="tBlue">const</span> h7Ani = h7.animate(ani, aniAttr);</cl-cd>
<cl-cd data-idx="17"> </cl-cd>
<cl-cd data-idx="18">setInterval(() => {</cl-cd>
<cl-cd data-idx="19"> <span class="tBlue">let</span> val = <span class="tRed">Math</span>.round(h7Ani.playbackRate * 100) / 100;</cl-cd>
<cl-cd data-idx="20"> charge.innerHTML = val <= 0.05 ? <span class="tMagenta">'<<span class="tDarkRed">span</span> class="tRed">充电<<span class="tDarkRed">/span</span>>'</span> : <span class="tMagenta">'电量'</span>;</cl-cd>
<cl-cd data-idx="21"> charge.value = val <= 0.05 ? <span class="tMagenta">'0'</span> : <span class="tMagenta">'1'</span>;</cl-cd>
<cl-cd data-idx="22"> rateMsg.value = val;</cl-cd>
<cl-cd data-idx="23"> h7Ani.playbackRate *= 0.9;</cl-cd>
<cl-cd data-idx="24">},1000);</cl-cd>
<cl-cd data-idx="25"> </cl-cd>
<cl-cd data-idx="26">charge.onclick = () => {</cl-cd>
<cl-cd data-idx="27"> <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"> </cl-cd>
<cl-cd data-idx="30"><<span class="tDarkRed">/script</span>></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>
先仔细看了前面一个匀速的旋转+色相变化,看着那个颜色变化比后面这个变速的好看{:4_173:} “我们以四舍五入的方法只取浮点数小数点后两位数,所以电量最终会变为 0,符合现实生活规律,芝诺悖论也因此不攻自破。”
关于芝诺悖论不攻自破不赞同,你只是取两位小数,所以出现0,如果位数不限,不可能变0 ,所以悖论成立{:4_173:} rateMsg.value = val; 这句没看懂,主要没明白rateMsg是什么意思。
然后再往前看开始的设定,这个是自己给的名称吧。 红影 发表于 2024-6-7 09:45
rateMsg.value = val; 这句没看懂,主要没明白rateMsg是什么意思。
然后再往前看开始的设定,这个是自己 ...
这是 <output> 标签的 id,电量信息显示 红影 发表于 2024-6-7 09:38
“我们以四舍五入的方法只取浮点数小数点后两位数,所以电量最终会变为 0,符合现实生活规律,芝诺悖论也因 ...
若以芝诺悖论看,龟兔赛跑,即使比赛中不节外生枝,兔子也永远追不上乌龟。不过它是悖论,不攻自破。它的最大问题是忽视了最重要的东东,基础。
基础这里指客观存在的因素。设龟兔时速分别为5公里、0.5公里,AB两个点距离5公里,比赛双方从A点出发,谁先到B点谁获胜。显然,比赛结果不言而喻,但芝诺悖论不看这些现实存在的速度、距离,而是从另一个角度去论证兔子追不上乌龟,采用的方式是动态追赶中双方位置的动态变化,它只成立于非事实基础上的假定理论层面。
这类悖论其实在主导着很多人虚幻地生活着。 红影 发表于 2024-6-7 09:36
先仔细看了前面一个匀速的旋转+色相变化,看着那个颜色变化比后面这个变速的好看
匀速变化之美往往让人容易接受,不过这样的美偏于平淡,缺失跌宕起伏的或能从中得到意外惊喜的增倍的美。 给小播充电的想法和实现,真是一绝,叹为观止。。{:4_199:} 南无月 发表于 2024-6-7 12:45
给小播充电的想法和实现,真是一绝,叹为观止。。
这个容易想到呀 {:4_170:}一键快充看着极度舒适。 南无月 发表于 2024-6-7 12:47
一键快充看着极度舒适。
太厉害了 马黑黑 发表于 2024-6-7 12:45
这个容易想到呀
反正我看到的时是惊呆了。。。
你脑回路与众不同的。。{:4_199:} 南无月 发表于 2024-6-7 12:48
反正我看到的时是惊呆了。。。
你脑回路与众不同的。。
一样一样的 南无月 发表于 2024-6-7 12:45
给小播充电的想法和实现,真是一绝,叹为观止。。
{:4_190:} 马黑黑 发表于 2024-6-7 12:22
这是标签的 id,电量信息显示
嗯,是自己设置的id明白,以为这个也是命令呢{:4_173:} 马黑黑 发表于 2024-6-7 12:34
若以芝诺悖论看,龟兔赛跑,即使比赛中不节外生枝,兔子也永远追不上乌龟。不过它是悖论,不攻自破。它的 ...
所以这个悖论也挺好玩的,感觉还挺有道理的。现实里是谬论,也许放到某种特定条件里它是成立的。 马黑黑 发表于 2024-6-7 12:37
匀速变化之美往往让人容易接受,不过这样的美偏于平淡,缺失跌宕起伏的或能从中得到意外惊喜的增倍的美。
哈哈,跌宕起伏的更刺激呗{:4_173:} 马黑黑 发表于 2024-6-7 12:48
太厉害了
虽然大容量电池暂时还看不到。。
一键充电这事儿到是先实现了。{:4_170:} 南无月 发表于 2024-6-7 17:58
虽然大容量电池暂时还看不到。。
一键充电这事儿到是先实现了。
秒充 马黑黑 发表于 2024-6-7 13:55
一样一样的
你跟我一样的么,才怪。。{:4_170:}
我有你那么好用的脑袋做梦都笑醒