CSS关键帧动画:元素绕椭圆圆周运动的实现(二)
本帖最后由 马黑黑 于 2024-5-22 07:49 编辑 <br /><br /><style>.art { position: relative; }
.art p { font-size: 18px; margin: 12px 0; }
.tMid { text-align: center; }
.eBox { margin: 20px auto; width: 400px; height: 240px; border: 1px solid gray; border-radius: 50%; }
li-zi { position: absolute; width: 30px; height: 30px; background: plum; offset-path: path('M0 120 A200 120 0 1 1 400 120 A200 120 0 1 1 0 120'); }
.ani { animation: move 10s linear infinite, size 10s linear infinite; }
.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; }
@keyframes move { from { offset-distance: 0; } to { offset-distance: 100%; } }
@keyframes size {
0%, 50%, 100% { transform: scale(1); }
25% { transform: scale(0.5); }
75% { transform: scale(1.5); }
}
</style>
<div class="art">
<p>上一讲,<a href="https://www.huachaowang.com/forum.php?mod=viewthread&tid=76070" target="_blank">《CSS关键帧动画:元素绕椭圆圆周运动的实现(一)》</a>,我们用两个CSS属性 ① offset-path 设计路径、② offset-distance 控制元素在路径上的偏移位置,实现了绝对定位的子元素在相对定位的父元素场景下做椭圆运动。椭圆运动往往能模拟出一定效果的3d运动,为了让效果显得更生动逼真,我们还需要让运动中的元素在运动周期中能变小变大,规则是,元素离观测点最近时最大、反正最小。椭圆路径始于左中(M0 120),按顺时针方向运动,移动到 25% 即椭圆的最顶端时离我们的眼睛(观测点)最远,到 50% 即右中时和出发点同在一个水平位置,到 75% 即最底端时离我们最近。按这个规律,我们设计一组CSS关键帧动画,就叫它 size 吧,用来控制元素在移动时的尺寸变化:</p>
<div class='mum'>
<cl-cd data-idx="1"> @keyframes size {</cl-cd>
<cl-cd data-idx="2"> 0%, 50%, 100% { <span class="tBlue">transform:</span> scale(1); }</cl-cd>
<cl-cd data-idx="3"> 25% { <span class="tBlue">transform:</span> scale(0.5); }</cl-cd>
<cl-cd data-idx="4"> 75% { <span class="tBlue">transform:</span> scale(1.5); }</cl-cd>
<cl-cd data-idx="5"> }</cl-cd>
</div>
<p>这里,我们设计了三种变化状态,最小 0.5,最大 1.5,正常 1,分别对应于运动路径上的 ① 0%、50%、100%,② 25%,③ 75%,然后将这个动画追加给 li-zi 的 animation 属性——我们早已知道,animation 属性支持多个关键帧动画。下面是效果和代码:<br> </p>
<div class="eBox"><li-zi class="ani"></li-zi></div>
<p><br></p>
<div class='mum'>
<cl-cd data-idx="1"><<span class="tDarkRed">style</span>> </cl-cd>
<cl-cd data-idx="2"> .eBox {</cl-cd>
<cl-cd data-idx="3"> <span class="tBlue">margin:</span> 20px auto;</cl-cd>
<cl-cd data-idx="4"> <span class="tBlue">width:</span> 400px;</cl-cd>
<cl-cd data-idx="5"> <span class="tBlue">height:</span> 240px;</cl-cd>
<cl-cd data-idx="6"> <span class="tBlue">border:</span> 1px solid gray;</cl-cd>
<cl-cd data-idx="7"> <span class="tBlue">border-radius:</span> 50%;</cl-cd>
<cl-cd data-idx="8"> <span class="tBlue">position:</span> relative;</cl-cd>
<cl-cd data-idx="9"> }</cl-cd>
<cl-cd data-idx="10"> li-zi {</cl-cd>
<cl-cd data-idx="11"> <span class="tBlue">position:</span> absolute;</cl-cd>
<cl-cd data-idx="12"> <span class="tBlue">width:</span> 30px;</cl-cd>
<cl-cd data-idx="13"> <span class="tBlue">height:</span> 30px;</cl-cd>
<cl-cd data-idx="14"> <span class="tBlue">background:</span> plum;</cl-cd>
<cl-cd data-idx="15"> <span class="tBlue">offset-path:</span> path(<span class="tMagenta">'M0 120 A200 120 0 1 1 400 120 A200 120 0 1 1 0 120'</span>);</cl-cd>
<cl-cd data-idx="16"> <span class="tBlue">animation:</span> move 10s linear infinite, size 10s linear infinite;</cl-cd>
<cl-cd data-idx="17"> }</cl-cd>
<cl-cd data-idx="18"> @keyframes move {</cl-cd>
<cl-cd data-idx="19"> from { <span class="tBlue">offset-distance:</span> 0; }</cl-cd>
<cl-cd data-idx="20"> to { <span class="tBlue">offset-distance:</span> 100%; }</cl-cd>
<cl-cd data-idx="21"> }</cl-cd>
<cl-cd data-idx="22"> @keyframes size {</cl-cd>
<cl-cd data-idx="23"> 0%, 50%, 100% { <span class="tBlue">transform:</span> scale(1); }</cl-cd>
<cl-cd data-idx="24"> 25% { <span class="tBlue">transform:</span> scale(0.5); }</cl-cd>
<cl-cd data-idx="25"> 75% { <span class="tBlue">transform:</span> scale(1.5); }</cl-cd>
<cl-cd data-idx="26"> }</cl-cd>
<cl-cd data-idx="27"><<span class="tDarkRed">/style</span>></cl-cd>
<cl-cd data-idx="28"> </cl-cd>
<cl-cd data-idx="29"><<span class="tDarkRed">div</span> class=<span class="tMagenta">"eBox"</span>></cl-cd>
<cl-cd data-idx="30"> <<span class="tDarkRed">li-zi</span>><<span class="tDarkRed">/li-zi</span>></cl-cd>
<cl-cd data-idx="31"><<span class="tDarkRed">/div</span>></cl-cd>
</div>
<p>留意观察第 16 行代码,animation属性调用了两个关键帧动画,彼此间用小角逗号隔开,两组动画的运行参数保持一致以确保动作协调。</p>
<p>以上只是一个 li-zi 元素实例的运动展示,如果是多个元素前后拉开距离在同一个跑道上运动,那又如何实现呢?思路其实也简单:利用 animation-delay 实现,该属性表示动画延时执行的时间,使用负值表示提前执行。下面的实例,我们利用 JS 批量生成 12 个 li-zi 元素,每一个 li-zi 元素背景色随机产生,animation-delay 属性值取负数值,按照动画总时长除以 li-zi 总数乘以序列 id (具体看第36行代码)以便让小矩形在路径上均匀分布。</p>
<p>效果:</p>
<div class="eBox" id="lzpa"></div>
<p>完整代码:<br></p>
<div class='mum'>
<cl-cd data-idx="1"><<span class="tDarkRed">style</span>> </cl-cd>
<cl-cd data-idx="2"> .eBox {</cl-cd>
<cl-cd data-idx="3"> <span class="tBlue">margin:</span> 20px auto;</cl-cd>
<cl-cd data-idx="4"> <span class="tBlue">width:</span> 400px;</cl-cd>
<cl-cd data-idx="5"> <span class="tBlue">height:</span> 240px;</cl-cd>
<cl-cd data-idx="6"> <span class="tBlue">border:</span> 1px solid gray;</cl-cd>
<cl-cd data-idx="7"> <span class="tBlue">border-radius:</span> 50%;</cl-cd>
<cl-cd data-idx="8"> <span class="tBlue">position:</span> relative;</cl-cd>
<cl-cd data-idx="9"> }</cl-cd>
<cl-cd data-idx="10"> li-zi {</cl-cd>
<cl-cd data-idx="11"> <span class="tBlue">position:</span> absolute;</cl-cd>
<cl-cd data-idx="12"> <span class="tBlue">width:</span> 30px;</cl-cd>
<cl-cd data-idx="13"> <span class="tBlue">height:</span> 30px;</cl-cd>
<cl-cd data-idx="14"> <span class="tBlue">background:</span> plum;</cl-cd>
<cl-cd data-idx="15"> <span class="tBlue">offset-path:</span> path(<span class="tMagenta">'M0 120 A200 120 0 1 1 400 120 A200 120 0 1 1 0 120'</span>);</cl-cd>
<cl-cd data-idx="16"> <span class="tBlue">animation:</span> move 10s linear infinite, size 10s linear infinite;</cl-cd>
<cl-cd data-idx="17"> }</cl-cd>
<cl-cd data-idx="18"> @keyframes move {</cl-cd>
<cl-cd data-idx="19"> from { <span class="tBlue">offset-distance:</span> 0; }</cl-cd>
<cl-cd data-idx="20"> to { <span class="tBlue">offset-distance:</span> 100%; }</cl-cd>
<cl-cd data-idx="21"> }</cl-cd>
<cl-cd data-idx="22"> @keyframes size {</cl-cd>
<cl-cd data-idx="23"> 0%, 50%, 100% { <span class="tBlue">transform:</span> scale(1); }</cl-cd>
<cl-cd data-idx="24"> 25% { <span class="tBlue">transform:</span> scale(0.5); }</cl-cd>
<cl-cd data-idx="25"> 75% { <span class="tBlue">transform:</span> scale(1.5); }</cl-cd>
<cl-cd data-idx="26"> }</cl-cd>
<cl-cd data-idx="27"><<span class="tDarkRed">/style</span>></cl-cd>
<cl-cd data-idx="28"> </cl-cd>
<cl-cd data-idx="29"><<span class="tDarkRed">div</span> class=<span class="tMagenta">"eBox"</span> <span class="tRed">id</span>=<span class="tMagenta">"lzpa"</span>><<span class="tDarkRed">/div</span>></cl-cd>
<cl-cd data-idx="30"> </cl-cd>
<cl-cd data-idx="31"><<span class="tDarkRed">script</span>></cl-cd>
<cl-cd data-idx="32"> <span class="tBlue">var</span> total = 12;</cl-cd>
<cl-cd data-idx="33"> <span class="tBlue">for</span>(<span class="tBlue">var</span> i = 0; i < total; i ++) {</cl-cd>
<cl-cd data-idx="34"> <span class="tBlue">let</span> lz = <span class="tRed">document</span>.createElement(<span class="tMagenta">'li-zi'</span>);</cl-cd>
<cl-cd data-idx="35"> lz.style.cssText += `</cl-cd>
<cl-cd data-idx="36"> <span class="tBlue">animation-delay:</span> -${10 / total * i}s;</cl-cd>
<cl-cd data-idx="37"> <span class="tBlue">background-color:</span> #${<span class="tRed">Math</span>.random().toString(16).substring(2, 8)};</cl-cd>
<cl-cd data-idx="38"> `;</cl-cd>
<cl-cd data-idx="39"> lzpa.appendChild(lz);</cl-cd>
<cl-cd data-idx="40"> }</cl-cd>
<cl-cd data-idx="41"><<span class="tDarkRed">/script</span>></cl-cd>
</div>
<p>注意,第 36 行代码,10 来源于 animation 属性中设置的 animation-duration 即第 16 行代码中的 10s,这个数值若发生变化这里也要跟着改。</p>
<p>这里只是演示,实际应用时应当根据需要做必要的修改,反正万变不离其宗,懂得做的都会懂。</p>
</div>
<script>
var total = 12;
for(var i = 0; i < total; i ++) {
let lz = document.createElement('li-zi');
lz.className = 'ani';
lz.style.cssText += `
animation-delay: -${10 / total * i}s;
background-color: #${Math.random().toString(16).substring(2, 8)};
`;
lzpa.appendChild(lz);
}
</script>
这个一步步讲解下来特别好,先理解单个的,再理解多个元素的,循序渐进,真棒{:4_199:} “animation属性调用了两个关键帧动画”
这个设计好,两个动作能同时实现了{:4_199:} 第 36 行代码,若animation-delay 属性值不取负数值,元素是一个个出来再全体旋转,取了负值是直接就全体旋转了,这个很奇妙。 单个变化的在等分的位置上,感觉多个的最大最小不在25%和75%的位置上呢,可能是视觉错觉。{:4_204:} 本帖最后由 马黑黑 于 2024-5-22 11:42 编辑
红影 发表于 2024-5-22 10:18
单个变化的在等分的位置上,感觉多个的最大最小不在25%和75%的位置上呢,可能是视觉错觉。
就在那里的,这是可想而知的:你试一下去掉关键帧动画,给 li-zi 元素的CSS样式加上:
offset-distance: 25%;
然后依次改一下百分比,看看元素在百分之几里观测点最近。 红影 发表于 2024-5-22 10:12
第 36 行代码,若animation-delay 属性值不取负数值,元素是一个个出来再全体旋转,取了负值是直接就全体旋 ...
这个知识点也是之前讲过的,而且大量应用于HTML粒子 红影 发表于 2024-5-22 10:08
“animation属性调用了两个关键帧动画”
这个设计好,两个动作能同时实现了
如果有足够的耐心,就设计一个 @keyframes 也是可以的 红影 发表于 2024-5-22 10:06
这个一步步讲解下来特别好,先理解单个的,再理解多个元素的,循序渐进,真棒
记住一个人相对容易,记住很多人相对困难{:4_170:} 多个粒子用一条线串着特别好看,如果把粒子变成别图案的话。。{:4_199:} 近大远小的变化得十分丝滑流畅~~舒适度极佳 。。 南无月 发表于 2024-5-22 12:52
近大远小的变化得十分丝滑流畅~~舒适度极佳 。。
CSS动画用得好还是非常理想的 南无月 发表于 2024-5-22 12:51
多个粒子用一条线串着特别好看,如果把粒子变成别图案的话。。
有无(线)限可能 马黑黑 发表于 2024-5-22 17:34
CSS动画用得好还是非常理想的
这个用得是真好。。主要是老师算得比较精准。。 马黑黑 发表于 2024-5-22 17:35
有无(线)限可能
{:4_170:}还是有线好,我把上面的粒子变成立体小珠子,跟项链一样漂亮极了 南无月 发表于 2024-5-22 17:47
还是有线好,我把上面的粒子变成立体小珠子,跟项链一样漂亮极了
漂亮那是自然 南无月 发表于 2024-5-22 17:46
这个用得是真好。。主要是老师算得比较精准。。
有金算盘的 马黑黑 发表于 2024-5-22 18:02
漂亮那是自然
出来就惊艳到了,各种变化和效果都可以。。{:4_173:}
就是老师代码灵活又漂亮 马黑黑 发表于 2024-5-22 18:03
有金算盘的
金算盘打得精。。自动加漂亮滤镜{:4_170:} 南无月 发表于 2024-5-22 20:30
金算盘打得精。。自动加漂亮滤镜
是滴