JS原生animate动画之:编写自己的动画
<style>.artbox p { font: normal 18px/24px sans-serif; margin: 12px 0; }
.artbox mark { background: lightblue; padding: 2px 6px;}
#tz { margin: auto; width: 740px; height: 420px; background: #eee; border: 1px solid gray; position: relative; }
#pic { position: absolute; width: 100px; height: 100px; }
.mum { position: relative; margin: 12px 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>如下动画效果是我们本文要实现的目标:</p>
<div id="tz">
<img id="pic" src="https://638183.freep.cn/638183/small/4yc.png" alt="" />
</div>
<p>100×100的图片在宽高尺寸为740×420的场景下运动,从左上角(0,0)出发,旋转着移动到底部的中央(320,320)并在此处停留,倒转后继续移动到右上角(640,0),然后反方向回到原点接着重复运行。全程图片分两个方向旋转。</p>
<p>我们要设计运动路线及运动形态仅需描述左上角、底部中央、右上角的过程,可以分解为五个步骤,我们用键值对来组织每一步的属性状态描述:</p>
<blockquote>
① 从左上角出发 → { left: '0', top: '0' }<br>
② 到达底部中央 → { left: '320px', top: '320px' }<br>
③ 在底部中央旋转 → { transform: 'rotate(720deg)' }<br>
④ 在底部中央逗留 → { left: '320px', top: '320px' }<br>
⑤ 达到右上角 → { left: '640px', top: '0' }
</blockquote>
<p>解释一下整个运动过程:</p>
<p>第 ① 步,图片位于左上角(0,0),注意我们用的是CSS属性 left 和 top 来描述,但是,和CSS的表达方式不同:<mark>animate(keyframes, options)</mark> 的 keyframes 参数要使用键值对的方法来给属性赋值,0 是 0px 的缩写,凡是 0 的值可以不要单位,但它原本是 0px,是字符串,所以 left 键的值 0 要用引号包裹起来,后续各属性值的表达方法也都是同一道理。 </p>
<p>第 ② 步,图片的位置移到(320,320)处,让图片处在底部中央,计算方法是:<mark>left=(场景宽-图片宽)÷2</mark>,<mark>top=场景高-图片高</mark>。</p>
<p>前面 ① 和 ② 两步连起来运行,则会:图片从左上角出发,目的地是(320,320)。动画一开始就旋转,但这两步并没有 transform 的 rotate 语句,旋转从哪儿来呢?看下一步:</p>
<p>第 ③ 步位置属性省略,因为它的上一步和下一步位置不改变。这一步旋转 720度,但并不是说到这里才开始旋转,它其实有三层意思:一,整个图片的运动,从出发点到此处,它要旋转 720 度;二,图片在这个地方,也要旋转 720 度;三,图片从这一步开始,要回退旋转720度。具体原因我们稍后会再做详细解释。</p>
<p>第 ④ 步,发生地还在(320,320)处,仅 left 和 top 属性表述,意思是继续停留在{320,320)处。</p>
<p>第 ⑤ 步,达到终点(640,0)。</p>
<p>在第 ③ 步的解释中关于图片的旋转做了简单说明,这里再结合动画的整体效果需要进一步做解释:整个动画过程有 720 度的顺时针旋转设置,这个设置放在第 ③ 步,其余各个环节都省略了 transform: rotate(xxdeg) 设置。省略不意味着没有,默认情况下,第 ① 步是 0deg,从 ① 到 ② 步是 360deg,从 ② 到 ③ 是 720-360=360deg,③ 又独占 720deg,从 ③ 到 ⑤ 则是倒过来从 720deg 到 0deg 的变化,是回退,所以旋转方向是逆时针,其中,③ 到 ④ 分配 360deg,④ 到 5 分配余下的 360deg。为什么会这样?因为,只要存在某一个 transform 设置,其余各个运动节点也都存在,省略时就是元素的初始值,没有初始值时理论上是 0。而 animate() 函数有一个均摊理念,以旋转为例,假设A、B、C三个节点均不设置 rotate 值、D节点设置 360 度,那么,A是开始节点,默认是 0 度,B是 180 度,C是 360 度,就是说,从A到C要完成 360 度的转动,而D独自拥有360度,它要完成 360 度的旋转动作。余下动画节点,假设各个节点也都不设置 rotate 值,那么,最后一个是 0 度,剩下的其余节点要均摊 0-360=-360度 的旋转,回退旋转。</p>
<p>以上,我们描述了动画从左上角到右上角的路线和运动形态,那么,原路返回又是怎么回事、如何实现?这需要用到 <mark>animate(keyframes, options)</mark> 函数的第二个参数 options,动画属性配置。options 参数以对象的形式组织,放在花括号 {} 里,各键值对间用小角逗号隔开。下面我们给出具体代码,代码中有必要的注释说明:</p>
<div class='mum'>
<cl-cd data-idx="1"><<span class="tDarkRed">style</span>></cl-cd>
<cl-cd data-idx="2">.artbox p { <span class="tBlue">font:</span> normal 18px/24px sans-serif; }</cl-cd>
<cl-cd data-idx="3">#tz { <span class="tBlue">margin:</span> 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">position:</span> relative; }</cl-cd>
<cl-cd data-idx="4">#pic { <span class="tBlue">position:</span> absolute; <span class="tBlue">width:</span> 100px; <span class="tBlue">height:</span> 100px; }</cl-cd>
<cl-cd data-idx="5"><<span class="tDarkRed">/style</span>></cl-cd>
<cl-cd data-idx="6"> </cl-cd>
<cl-cd data-idx="7"><<span class="tDarkRed">div</span> <span class="tRed">id</span>=<span class="tMagenta">"tz"</span>></cl-cd>
<cl-cd data-idx="8"> <<span class="tDarkRed">img</span> <span class="tRed">id</span>=<span class="tMagenta">"pic"</span> src=<span class="tMagenta">"https://638183.freep.cn/638183/small/4yc.png"</span> alt=<span class="tMagenta">""</span> /></cl-cd>
<cl-cd data-idx="9"><<span class="tDarkRed">/div</span>></cl-cd>
<cl-cd data-idx="10"> </cl-cd>
<cl-cd data-idx="11"><<span class="tDarkRed">script</span>></cl-cd>
<cl-cd data-idx="12"><span class="tGreen">//动画属性配置(对象形式)</span></cl-cd>
<cl-cd data-idx="13"><span class="tBlue">const</span> opt = {</cl-cd>
<cl-cd data-idx="14"> <span class="tBlue">duration:</span> 10000, <span class="tGreen">//动画周期时长</span></cl-cd>
<cl-cd data-idx="15"> <span class="tBlue">direction:</span> <span class="tMagenta">'alternate'</span>, <span class="tGreen">//动画方向 :双向</span></cl-cd>
<cl-cd data-idx="16"> <span class="tBlue">iterations:</span> Infinity, <span class="tGreen">//动画重复次数 :永动,Infinity 是JS数字,不用引号</span></cl-cd>
<cl-cd data-idx="17">};</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"><span class="tBlue">const</span> kfs = [</cl-cd>
<cl-cd data-idx="21"> { <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="22"> { <span class="tBlue">left:</span> <span class="tMagenta">'320px'</span>, <span class="tBlue">top:</span> <span class="tMagenta">'320px'</span> },</cl-cd>
<cl-cd data-idx="23"> { <span class="tBlue">transform:</span> <span class="tMagenta">'rotate(720deg)'</span> },</cl-cd>
<cl-cd data-idx="24"> { <span class="tBlue">left:</span> <span class="tMagenta">'320px'</span>, <span class="tBlue">top:</span> <span class="tMagenta">'320px'</span> },</cl-cd>
<cl-cd data-idx="25"> { <span class="tBlue">left:</span> <span class="tMagenta">'640px'</span>, <span class="tBlue">top:</span> <span class="tMagenta">'0'</span> },</cl-cd>
<cl-cd data-idx="26">];</cl-cd>
<cl-cd data-idx="27"> </cl-cd>
<cl-cd data-idx="28"><span class="tGreen">//运行动画并将操作接口赋值给变量 picMove</span></cl-cd>
<cl-cd data-idx="29"><span class="tBlue">const</span> picMove = pic.animate(kfs, opt);</cl-cd>
<cl-cd data-idx="30"> </cl-cd>
<cl-cd data-idx="31"><span class="tGreen">//鼠标指针滑过图片事件 :动画暂停</span></cl-cd>
<cl-cd data-idx="32">pic.onmouseover = () => picMove.pause();</cl-cd>
<cl-cd data-idx="33"> </cl-cd>
<cl-cd data-idx="34"><span class="tGreen">//鼠标指针离开图片事件 :动画继续</span></cl-cd>
<cl-cd data-idx="35">pic.onmouseout = () => picMove.play();</cl-cd>
<cl-cd data-idx="36"><<span class="tDarkRed">/script</span>></cl-cd>
</div>
<p>上面的动画描述,数据都是写死了,这会很不方便,比如场景尺寸和图片尺寸若不同于上面的实例,得一一去修改相应数字。我们可以改进一下,将下面的代码替换 ksf 代码块即可,改好后场景、图片的尺寸变化它都能自适应:</p>
<div class='mum'>
<cl-cd data-idx="1"><span class="tBlue">const</span> w1 = tz.offsetWidth, <span class="tGreen">//场景宽度</span></cl-cd>
<cl-cd data-idx="2"> h1 = tz.offsetHeight, <span class="tGreen">//场景高窟</span></cl-cd>
<cl-cd data-idx="3"> w2 = pic.offsetWidth, <span class="tGreen">//图片宽度</span></cl-cd>
<cl-cd data-idx="4"> h2 = pic.offsetHeight; <span class="tGreen">//图片高度</span></cl-cd>
<cl-cd data-idx="5"> </cl-cd>
<cl-cd data-idx="6"><span class="tBlue">const</span> kfs = [</cl-cd>
<cl-cd data-idx="7"> { <span class="tBlue">left:</span> `0`, <span class="tBlue">top:</span> `0` },</cl-cd>
<cl-cd data-idx="8"> { <span class="tBlue">left:</span> `${(w1 - w2) / 2}px`, <span class="tBlue">top:</span> `${h1 - h2}px` },</cl-cd>
<cl-cd data-idx="9"> { <span class="tBlue">transform:</span> `rotate(720deg)` },</cl-cd>
<cl-cd data-idx="10"> { <span class="tBlue">left:</span> `${(w1 - w2) / 2}px`, <span class="tBlue">top:</span> `${h1 - h2}px` },</cl-cd>
<cl-cd data-idx="11"> { <span class="tBlue">left:</span> `${w1 - w2}px`, <span class="tBlue">top:</span> `0` },</cl-cd>
<cl-cd data-idx="12">];</cl-cd>
</div>
<p>声明部分是获取场景和图片的宽高,然后在 kfs 动画数组变量中,在相应描述语句里,比如底部位置、右上角位置等,按前面提到的计算将变量套进去参与计算即可。需要注意的是,新的代码我们使用反引号 <mark>`</mark> 代替单引号 <mark>'</mark>,这样变量和算式可以放在 <mark>${变量和算式}</mark> 结构内,与其他字符的拼接是一站式的,正如上面的代码所展示的那样;实际上,即使只是表示纯字符,反引号也可以替代小角单引号、双引号,只要喜欢。</p>
</div>
<script>
const w1 = tz.offsetWidth, h1 = tz.offsetHeight, w2 = pic.offsetWidth, h2 = pic.offsetHeight;
const opt = { duration: 10000, direction: 'alternate', iterations: Infinity };
const kfs = [
{ left: `0`, top: `0` },
{ left: `${(w1 - w2) / 2}px`, top: `${h1 - h2}px` },
{ transform: `rotate(720deg)` },
{ left: `${(w1 - w2) / 2}px`, top: `${h1 - h2}px` },
{ left: `${w1 - w2}px`, top: `0` },
];
const picMove = pic.animate(kfs, opt);
pic.onmouseover = () => picMove.pause();
pic.onmouseout = () => picMove.play();
</script>
这个区分很细,比如“是JS数字,不用引号”等,还有反引号和小角单引号的区分等,要想掌握还真的有难度{:4_173:} 红影 发表于 2024-6-14 19:07
这个区分很细,比如“是JS数字,不用引号”等,还有反引号和小角单引号的区分等,要想掌握还真的有难度{:4_ ...
标点符号其实不是个问题 看完这个,再去看《大湖》,知道了为什么会随机从4个角点出来了{:4_204:} CSS:
@keyframes kfs {
0% { left: 0px; top: 0px; } 40% { left: 320px;top: 320px; }
41%,59% { transform: rotate(720deg) ;}
60%{ left: 320px; top: 320px; }
100% { left: 640px; top: 0px }
}
animation kfs 10s linear infinite alternate;
起个网名好难 发表于 2024-6-14 20:38
CSS:
@keyframes kfs {
0% { left: 0px; top: 0px; } 40% { left: 320px;top: 320px; }
对,它们其实是对应的 红影 发表于 2024-6-14 19:16
看完这个,再去看《大湖》,知道了为什么会随机从4个角点出来了
那是另一码事啊,是通过随机设计的算式定义每一次的出发点都随机从四个角中任意一个出来 自己编动画呀,哎。。小白我这会看看就好了。。{:4_170:} 马黑黑 发表于 2024-6-14 19:07
标点符号其实不是个问题
有时差一个小点,就出不来了{:4_173:} 马黑黑 发表于 2024-6-14 20:47
那是另一码事啊,是通过随机设计的算式定义每一次的出发点都随机从四个角中任意一个出来
虽然不一样,通过这个也更容易理解一下那个{:4_187:} 红影 发表于 2024-6-14 22:29
虽然不一样,通过这个也更容易理解一下那个
酱紫 红影 发表于 2024-6-14 22:26
有时差一个小点,就出不来了
这倒也是 南无月 发表于 2024-6-14 21:32
自己编动画呀,哎。。小白我这会看看就好了。。
看着已经是很简单的 了,连个尺子都不用 马黑黑 发表于 2024-6-14 22:38
酱紫
我只是想多看看,努力弄懂它们。 马黑黑 发表于 2024-6-14 22:38
这倒也是
我就干过这种丢三落四的事{:4_173:} 马黑黑 发表于 2024-6-14 22:38
酱紫
反正多看总没坏处{:4_173:} 红影 发表于 2024-6-15 10:46
反正多看总没坏处
道理是酱紫 红影 发表于 2024-6-15 10:26
我就干过这种丢三落四的事
很正常 红影 发表于 2024-6-15 10:26
我只是想多看看,努力弄懂它们。
有些可以懂,有些就可能一辈子也懂不了 马黑黑 发表于 2024-6-15 11:06
道理是酱紫
所以有时间就尽量重新翻翻看看了。