马黑黑 发表于 2024-6-15 10:19

JS原生animate动画之:编写自己的动画(续篇)

本帖最后由 马黑黑 于 2024-6-15 10:27 编辑 <br /><br /><style>
.art p { font: normal 18px/24px sans-serif; margin: 12px 0; }

#tz { margin: 20px auto; width: 740px; height: 420px; background: #eee; border: 1px solid gray; pointer-events: none; position: relative; }
#tz::before { content: ''; position: absolute; left: 20px; top: 20px; width: 100px; height: 100px; background: url('https://638183.freep.cn/638183/small/4yc.png') no-repeat center/cover; pointer-events: auto; cursor: pointer; }

.mum { position: relative; margin: 20px 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: thin solid 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="art">
        <p>在<a href="https://www.huachaowang.com/forum.php?mod=viewthread&tid=76540&extra=page%3D1" target="_blank">《JS原生animate动画之:编写自己的动画》</a>一文,我们介绍了使用数组形式将多个动画节点的描述以对象形式封装起来,变量是一个数组,数组元素是有键值对的对象,每一个对象对应于动画的某个时间节点。先来复习一下它的结构,我们以描述运动对象旋转360度为例:</p>
        <div class='mum'>
<cl-cd data-idx="1"><span class="tBlue">const</span> kfs = [</cl-cd>
<cl-cd data-idx="2">&nbsp; &nbsp; { <span class="tBlue">transform:</span> <span class="tMagenta">'rotate(0)'</span> },</cl-cd>
<cl-cd data-idx="3">&nbsp; &nbsp; { <span class="tBlue">transform:</span> <span class="tMagenta">'rotate(360deg)'</span> }</cl-cd>
<cl-cd data-idx="4">];</cl-cd>
        </div>
        <p>上述代码,变量 kfs 是我们声明的 animate() 动画函数所需的第一个 keyframes 的变量,kfs = [...] 这样的结构表明这是一个数组。数组的内容即数组元素由两个对象组成,每一个对象里,transform 是健名,'rotate(...)' 是键值,表示旋转N度。两个对象描述动画运行中在运行时间线上开始和结束时运动对象旋转的角度。这就是关键帧动画,animate() 执行时会将运动周期应旋转的角度均匀地分摊给整个运动过程上的每一帧,达成丝滑流畅的动画效果。</p>
        <p>这是我们业已学会的 keyframes 参数值的表达方式,注意它的结构是<span class="tRed">数组包裹对象</span>、每一个对象所描述的是动画在时间线上某个节点的运动状态。每一个对象可以拥有多个CSS属性,比如 left、color、filter 等等,共同描述该事件节点上运动对象(即HTML元素)的属性变化,即关键帧动画形态。</p>
        <p>下面介绍另一种关键帧的表达形式,<span class="tRed">对象包裹数组</span>,和前面的数组包裹对象的形式刚好相反。同样以运动对象旋转360度为例,看看它的结构:</p>
        <div class='mum'>
<cl-cd data-idx="1"><span class="tBlue">const</span> kfs = {</cl-cd>
<cl-cd data-idx="2">&nbsp; &nbsp; <span class="tBlue">transform:</span> [<span class="tMagenta">'rotate(0)'</span>, <span class="tMagenta">'rotate(360deg)'</span>],</cl-cd>
<cl-cd data-idx="3">};</cl-cd>
        </div>
        <p>就是说,kfs 变量这里是一个JS对象(Object)变量,不再是数组变量了。然后,kfs 对象值中,属性名做健名,再用数组将该属性在动画过程中的变化表达出来。上例,我们希望运动对象旋转 360 度,那么,开始时是 0 度,第一个数组元素 'rotate(0)' 描述的就是这个;第二个数组元素,'rotate(360deg)',描述的是动画周期结束时旋转的总度数。可以看出来,对象包裹数组不是纯粹意义上的对象包裹数组这样的结构,而是对象中的健名在各个节点上的变化值用数组来表示,每一个数组元素表示的是在动画时间线上某一节点的关键帧形态——具体说是该属性(如transform)在这个关键帧上是什么值。</p>
        <p>此法的特点是,以对象的形式组织关键帧动画,以元素的属性做对象的键名,属性的值使用数组描述不同时间节点上的变化。从形式上看,它和CSS的表述方式更为接近,熟悉CSS的朋友很容易上手。确实它很方便,比方说,我们希望运动对象在旋转的同时还能变换色相,那么,加个键值对就行:</p>
        <div class='mum'>
<cl-cd data-idx="1"><span class="tBlue">const</span> kfs = {</cl-cd>
<cl-cd data-idx="2">    <span class="tBlue">transform:</span> [<span class="tMagenta">'rotate(0)'</span>, <span class="tMagenta">'rotate(360deg)'</span>],</cl-cd>
<cl-cd data-idx="3">&nbsp; &nbsp; <span class="tBlue">filter:</span> [<span class="tMagenta">'hue-rotate(0)'</span>, <span class="tMagenta">'hue-rotate(360deg)'</span>],</cl-cd>
<cl-cd data-idx="4">};</cl-cd>
        </div>
        <p>看到了吧?我们加了一个CSS滤镜 filter,滤镜的值从 'hue-rotate(0)' 变为 'hue-rotate(360deg)' ,色相转换。这样运动形态又加了一个功能,通过滤镜实现颜色的变化。</p>
        <p>还可以继续加属性。假设我们希望运动对象除了旋转、变换颜色外,还从左上角移到右下角,那么:</p>
        <div class='mum'>
<cl-cd data-idx="1"><span class="tBlue">const</span> kfs = {</cl-cd>
<cl-cd data-idx="2">&nbsp; &nbsp; <span class="tBlue">transform:</span> [<span class="tMagenta">'rotate(0)'</span>, <span class="tMagenta">'rotate(360deg)'</span>],</cl-cd>
<cl-cd data-idx="3">&nbsp; &nbsp; <span class="tBlue">filter:</span> [<span class="tMagenta">'hue-rotate(0)'</span>, <span class="tMagenta">'hue-rotate(360deg)'</span>],</cl-cd>
<cl-cd data-idx="4">&nbsp; &nbsp; <span class="tBlue">left:</span> [<span class="tMagenta">'0'</span>, <span class="tMagenta">'640px'</span>],</cl-cd>
<cl-cd data-idx="5">&nbsp; &nbsp; <span class="tBlue">top:</span> [<span class="tMagenta">'0'</span>, <span class="tMagenta">'320px'</span>],</cl-cd>
<cl-cd data-idx="6">};</cl-cd>
        </div>
        <p>各个属性的值的总数不一定要对称,可以根据设计在不同属性上使用更多的数组元素来描述更多的细节。举例来说,我们希望运动对象先水平方向移动到水平线中央,然后再折向前往右下角,那么可以这样:</p>
        <div class='mum'>
<cl-cd data-idx="1"><span class="tBlue">const</span> kfs = {</cl-cd>
<cl-cd data-idx="2">&nbsp; &nbsp; <span class="tBlue">transform:</span> [<span class="tMagenta">'rotate(0)'</span>, <span class="tMagenta">'rotate(360deg)'</span>],</cl-cd>
<cl-cd data-idx="3">&nbsp; &nbsp; <span class="tBlue">filter:</span> [<span class="tMagenta">'hue-rotate(0)'</span>, <span class="tMagenta">'hue-rotate(360deg)'</span>],</cl-cd>
<cl-cd data-idx="4">&nbsp; &nbsp; <span class="tBlue">left:</span> [<span class="tMagenta">'0'</span>, <span class="tMagenta">'640px'</span>],</cl-cd>
<cl-cd data-idx="5">&nbsp; &nbsp; <span class="tBlue">top:</span> [<span class="tMagenta">'0'</span>, <span class="tMagenta">'0'</span>, <span class="tMagenta">'320px'</span>],</cl-cd>
<cl-cd data-idx="6">};</cl-cd>
        </div>
        <p>top 属性我们多加了一个节点,'0',表示动画运行时间线上,top 属性分为三个关键帧,第一、二个关键帧 top 的属性值都是 0,第三个关键帧为 320px。animate() 函数会均摊时间,所以,动画运行到一半时,运动对象的位置在上边缘中央处,符合我们的设计需求。</p>
        <p>对等功能的<span class="tRed">数组包裹对象</span>代码也提供一份,大家可以比较一下,看看哪一种形式更合适自己:</p>
        <div class='mum'>
<cl-cd data-idx="1"><span class="tBlue">const</span> kfs = [</cl-cd>
<cl-cd data-idx="2">&nbsp; &nbsp; { <span class="tBlue">transform:</span> <span class="tMagenta">'rotate(0)'</span>, <span class="tBlue">left:</span> <span class="tMagenta">'0'</span>, <span class="tBlue">top:</span> <span class="tMagenta">'0'</span>},</cl-cd>
<cl-cd data-idx="3">&nbsp; &nbsp; { <span class="tBlue">top:</span> <span class="tMagenta">'0'</span> },</cl-cd>
<cl-cd data-idx="4">&nbsp; &nbsp; { <span class="tBlue">transform:</span> <span class="tMagenta">'rotate(360deg)'</span>, <span class="tBlue">left:</span> <span class="tMagenta">'640px'</span>, <span class="tBlue">top:</span> <span class="tMagenta">'320px'</span> },</cl-cd>
<cl-cd data-idx="5">];</cl-cd>
        </div>
        <p>最后给出一个完整的演示实例,代码附后:</p>
        <div id="tz" title="暂停/继续"></div>
        <p>完整代码:</p>
        <div class='mum'>
<cl-cd data-idx="1">&lt;<span class="tDarkRed">style</span>&gt;</cl-cd>
<cl-cd data-idx="2">#tz { <span class="tBlue">margin:</span> 20px auto; <span class="tBlue">width:</span> 740px; <span class="tBlue">height:</span> 420px; <span class="tBlue">background:</span> #eee; <span class="tBlue">border:</span> 1px solid gray; <span class="tBlue">pointer-events:</span> none; <span class="tBlue">position:</span> relative; }</cl-cd>
<cl-cd data-idx="3">#tz::before { <span class="tBlue">content:</span> <span class="tMagenta">''</span>; <span class="tBlue">position:</span> absolute; <span class="tBlue">left:</span> 20px; <span class="tBlue">top:</span> 20px; <span class="tBlue">width:</span> 100px; <span class="tBlue">height:</span> 100px; <span class="tBlue">background:</span> url(<span class="tMagenta">'https://638183.freep.cn/638183/small/4yc.png'</span>) no-repeat center/cover; <span class="tBlue">pointer-events:</span> auto; <span class="tBlue">cursor:</span> pointer; }</cl-cd>
<cl-cd data-idx="4">&lt;<span class="tDarkRed">/style</span>&gt;</cl-cd>
<cl-cd data-idx="5">&nbsp;</cl-cd>
<cl-cd data-idx="6">&lt;<span class="tDarkRed">div</span> <span class="tRed">id</span>=<span class="tMagenta">"tz"</span> title=<span class="tMagenta">"暂停/继续"</span>&gt;&lt;<span class="tDarkRed">/div</span>&gt;</cl-cd>
<cl-cd data-idx="7">&nbsp; &nbsp; </cl-cd>
<cl-cd data-idx="8">&lt;<span class="tDarkRed">script</span>&gt;</cl-cd>
<cl-cd data-idx="9">&nbsp;</cl-cd>
<cl-cd data-idx="10"><span class="tGreen">//运动属性配置</span></cl-cd>
<cl-cd data-idx="11"><span class="tBlue">const</span> opt = {</cl-cd>
<cl-cd data-idx="12">&nbsp; &nbsp; <span class="tBlue">duration:</span> 6000, <span class="tGreen">//周期时长</span></cl-cd>
<cl-cd data-idx="13">&nbsp; &nbsp; <span class="tBlue">iterations:</span> Infinity, <span class="tGreen">// 运行次数</span></cl-cd>
<cl-cd data-idx="14">&nbsp; &nbsp; <span class="tBlue">direction:</span> <span class="tMagenta">'alternate'</span>, <span class="tGreen">//双向运行动画</span></cl-cd>
<cl-cd data-idx="15">&nbsp; &nbsp; <span class="tBlue">pseudoElement:</span> <span class="tMagenta">'::before'</span>, <span class="tGreen">//指定伪元素 ::before 为运动对象</span></cl-cd>
<cl-cd data-idx="16">};</cl-cd>
<cl-cd data-idx="17">&nbsp;</cl-cd>
<cl-cd data-idx="18"><span class="tGreen">//运动关键帧描述 :对象形式,各属性键使用数组表达关键帧属性值</span></cl-cd>
<cl-cd data-idx="19"><span class="tBlue">const</span> kfs = {</cl-cd>
<cl-cd data-idx="20">&nbsp; &nbsp; <span class="tBlue">transform:</span> [<span class="tMagenta">'rotate(0)'</span>, <span class="tMagenta">'rotate(720deg)'</span>],</cl-cd>
<cl-cd data-idx="21">&nbsp; &nbsp; <span class="tBlue">filter:</span> [<span class="tMagenta">'hue-rotate(0)'</span>, <span class="tMagenta">'hue-rotate(360deg)'</span>],</cl-cd>
<cl-cd data-idx="22">&nbsp; &nbsp; <span class="tBlue">left:</span> [<span class="tMagenta">'0'</span>, <span class="tMagenta">'640px'</span>],</cl-cd>
<cl-cd data-idx="23">&nbsp; &nbsp; <span class="tBlue">top:</span> [<span class="tMagenta">'0'</span>, <span class="tMagenta">'0'</span>, <span class="tMagenta">'320px'</span>],</cl-cd>
<cl-cd data-idx="24">};</cl-cd>
<cl-cd data-idx="25">&nbsp;</cl-cd>
<cl-cd data-idx="26"><span class="tGreen">//调用 element.animate(keyframes, options) 函数并获取操作接口 move</span></cl-cd>
<cl-cd data-idx="27"><span class="tBlue">const</span> move = tz.animate(kfs, opt);</cl-cd>
<cl-cd data-idx="28">&nbsp;</cl-cd>
<cl-cd data-idx="29"><span class="tGreen">//控制动画 :暂停与继续</span></cl-cd>
<cl-cd data-idx="30">tz.onclick = () =&gt; {</cl-cd>
<cl-cd data-idx="31">&nbsp; &nbsp; <span class="tBlue">if</span>(move.playState === <span class="tMagenta">'running'</span>) move.pause();</cl-cd>
<cl-cd data-idx="32">&nbsp; &nbsp; <span class="tBlue">else</span> move.play();</cl-cd>
<cl-cd data-idx="33">};</cl-cd>
<cl-cd data-idx="34">&lt;<span class="tDarkRed">/script</span>&gt;</cl-cd>
        </div>
</div>

<script>
(function() {
        const opt = {
                duration: 6000,
                iterations: Infinity,
                direction: 'alternate',
                pseudoElement: '::before',
        };
        const kfs = {
                transform: ['rotate(0)', 'rotate(720deg)'],
                filter: ['hue-rotate(0)', 'hue-rotate(360deg)'],
                left: ['0', '640px'],
                top: ['0', '0', '320px'],
        };
        const move = tz.animate(kfs, opt);
        tz.onclick = () => {
                if(move.playState === 'running') move.pause();
                else move.play();
        };
})();
</script>

红影 发表于 2024-6-15 13:01

虽然跟着一步步看是看懂了,但是top: ['0', '0', '320px'],这样的表述最不容易懂{:4_173:}

红影 发表于 2024-6-15 13:08

我个人还是觉得数组包裹对象的形式更容易懂些{:4_204:}

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

红影 发表于 2024-6-15 13:01
虽然跟着一步步看是看懂了,但是top: ['0', '0', '320px'],这样的表述最不容易懂

这个,单单设置top属性,整个过程分三个节点,也就是分三段来完成。本例,其他的属性分两段完成。

对象形式的特色是单独描述每一个属性,针对单一属性而言,设置更为自由,不用考虑其他属性的情况。就是说,行进路线设计中,top我就需要它分三段走完,left分两段,设置属性时彼此间不互相干扰,而运行时达到预期效果。

马黑黑 发表于 2024-6-15 13:15

红影 发表于 2024-6-15 13:08
我个人还是觉得数组包裹对象的形式更容易懂些

这会因人而异。很多人会愿意像设置每一个CSS属性那样去设置动画。

马黑黑 发表于 2024-6-15 13:23

红影 发表于 2024-6-15 13:08
我个人还是觉得数组包裹对象的形式更容易懂些

比如,本例的 top,我改成酱紫:

top: ['0', '200px', '30px', '180px', '10px', '320px'],

路线挺好看的。这里分成6个节点,如果用 数组包裹对象,你需要六个 {} 来描述。你比较一下哪一种更方便

红影 发表于 2024-6-15 14:10

马黑黑 发表于 2024-6-15 13:14
这个,单单设置top属性,整个过程分三个节点,也就是分三段来完成。本例,其他的属性分两段完成。

对 ...

这个更简洁,理解起来却是更难{:4_173:}

红影 发表于 2024-6-15 14:11

马黑黑 发表于 2024-6-15 13:15
这会因人而异。很多人会愿意像设置每一个CSS属性那样去设置动画。

我喜欢一步步来,虽然麻烦,但会更心里有数{:4_173:}

红影 发表于 2024-6-15 14:13

马黑黑 发表于 2024-6-15 13:23
比如,本例的 top,我改成酱紫:

top: ['0', '200px', '30px', '180px', '10px', '320px'],


前期我愿意用6个的方式设置,熟悉了会考虑简洁的,笨人笨办法的招儿{:4_173:}

小辣椒 发表于 2024-6-15 17:08

估计今天又拖地了,瞬间新的思路出来了{:4_173:}

马黑黑 发表于 2024-6-15 17:35

小辣椒 发表于 2024-6-15 17:08
估计今天又拖地了,瞬间新的思路出来了

哪有那么夸张

小辣椒 发表于 2024-6-15 17:42

马黑黑 发表于 2024-6-15 17:35
哪有那么夸张

我没记错吧,看见你自己说拖地会有灵感{:4_170:}

马黑黑 发表于 2024-6-15 17:50

红影 发表于 2024-6-15 14:13
前期我愿意用6个的方式设置,熟悉了会考虑简洁的,笨人笨办法的招儿

二者:

数组包裹对象:每一个 {} 里所描述的,是对应关键帧的所有属性的集合,依据时间线综合陈述关键时间节点上所有的属性特征,从整体看,这是顺序叙事方式,符合绝大多数人的描述习惯。

对象包裹数组:单独陈述每一个属性,被陈述的属性值将在运动的时间线上展开变化,例如,描述left属性值,有三个数组元素,['0', '200px', '450px'],它表示这三个值按三个段落均匀地铺开在运动时间性上,有多少个值,该属性的关键帧节点就有多少个。这是按属性去描述,多个属性的描述组合起来共同对运动对象产生作用。这种方式,在时间线上不能直观看到运动对象的变化结果,属线性思维的描述风格,理工男女可能更喜欢。

马黑黑 发表于 2024-6-15 17:52

红影 发表于 2024-6-15 14:11
我喜欢一步步来,虽然麻烦,但会更心里有数

就是像传统的文学创作一样,平铺直叙,一切了然于胸

马黑黑 发表于 2024-6-15 17:53

小辣椒 发表于 2024-6-15 17:42
我没记错吧,看见你自己说拖地会有灵感

但灵感的来源不是仅仅依靠拖地

小辣椒 发表于 2024-6-15 17:54

马黑黑 发表于 2024-6-15 17:53
但灵感的来源不是仅仅依靠拖地

反正拖地也是一个因素{:4_170:}

马黑黑 发表于 2024-6-15 17:55

红影 发表于 2024-6-15 14:10
这个更简洁,理解起来却是更难

这可能是先入为主的效应吧。就像电影、电视剧翻拍,由于前一个版本已经深入人心,后面的版本再怎么优秀都会被否认。

马黑黑 发表于 2024-6-15 17:55

小辣椒 发表于 2024-6-15 17:54
反正拖地也是一个因素

挺好的

醉美水芙蓉 发表于 2024-6-15 19:06

马黑黑 发表于 2024-6-15 19:37

醉美水芙蓉 发表于 2024-6-15 19:06
这个效果会变色好看!

{:4_190:}
页: [1] 2 3 4
查看完整版本: JS原生animate动画之:编写自己的动画(续篇)