醉美水芙蓉 发表于 昨天 21:53

马黑黑《CSS关键帧动画初步》

<style>
    @import 'https://638183.freep.cn/638183/web/css/hl.css';
    .artBox { margin: 20px auto; font: normal 18px/26px sans-serif; max-width: 1200px; }
    .artBox mark { padding: 2px 6px; background: #ccc; }
    .artBox blockquote { padding: 14px; border: solid gray; border-width: 0 4px; border-radius: 10px; background: #ddd; }
    .artBox p { margin: 10px 0; }
    .artBox pre { font-size: 16px; background: #eee; border-radius: 12px; white-space: pre-wrap; }
    .artBox table { border-collapse: collapse; white-space: pre-wrap; box-sizing: border-box; }
    .artBox th, .artBox td { padding: 8px 10px; border: 1px solid #999; }
    .artBox th { text-align: center; background: #eee; }
    .tRed { color: red; }
    .tGreen { color: green; }
    .tBlue { color: blue; }
    .btnRun { float: right; margin-right: 15px; }
</style>

<div class="artBox">
    <p>CSS关键帧动画由 <mark>@keyframes</mark> 创建一个自定义名称的动画序列,再由元素对应的选择器通过 <mark>animation</mark> 属性调用动画。</p>
   
    <h2>一、创建CSS关键帧动画</h2>
   
    <p>创建CSS关键帧动画用 <span class="tRed">@keyframes</span> 实现,语法结构:</p>
    <pre class="hlCode">@keyframes 名称 {
    时间节点1 { CSS属性 }
    时间节点2 { CSS属性 }
    时间节点N { CSS属性 }
}</pre>

    <p>@keyframes 创建的动画序列以一系列关键帧描述动画形态,包含<span class="tRed">时间节点</span>和<span class="tRed">动画数据</span>。时间节点可以使用百分比或关键字 from 和 to 表示,动画数据则是任意支持关键帧动画的单个或多个CSS属性。试看:</p>
   
    <pre class="hlCode"><span class="tGreen">/* 方法一 :百分比 */</span>
@keyframes move {
    0% { left: 0px; top: 10px; }
    100% { left: 240px; top: 180px; }
}

<span class="tGreen">/* 方法二 :关键字 */</span>
@keyframes move {
    from { left: 0px; top: 10px; }
    to { left: 240px; top: 180px; }
}</pre>

    <p>上述两种关键帧动画的设计等价,但方法一更易于扩展:在0%~100% 之间可以加入任意多个时间节点用以描述更多的关键帧。方法二适用于简易动画,它只关心两个时间节点即 0% 和 100% 的动画状态。</p>
    <p>很多时候元素用于关键帧动画的对应CSS属性已经设置了初始值,若此,关键帧动画开始的时间节点可以省略(特别是基于关键字方法):</p>
    <pre class="hlCode"><span class="tGreen">/* 元素定义了 left和 top 初始值 */</span>
.ball { position: absolute; left: 0px; top: 10px; }

<span class="tGreen">/* keyframes 关键帧动画可以省略 from 关键字属性 */</span>
@keyframes move {
    to { left: 240px; top: 180px; }
}</pre>

    <p>对于追求简洁代码需求而言,这是有效的方法,但一切应根据具体情况而定。</p>

    <h2>二、运行CSS关键帧动画</h2>
   
    <p>CSS关键帧动画由元素运行,通过元素对应CSS选择器的 animation 属性进行设置。animation 属性语法结构如下:</p>
    <pre class="hlCode"><span class="tGreen">/* 红色参数必须,其余可选(缺省时使用默认值) */</span>
    animation: <span class="tRed">①动画名称 ②周期时长</span> ③时间函数 ④延时时间 ⑤运行次数 ⑥运动方向 ⑦结束状态 ⑧播放状态
<span class="tGreen">/* 应用举例 :①move动画,②6秒一周期,③匀速,④延时0秒,⑤运行一次,⑥正向,⑦保留最后状态,⑧运行 */</span>
animation: move 6s linear 0s 1 normal forwards running;
<span class="tGreen">/* 因为 0s、normal、running 属性值都是默认值,可以简化为 */</span>
animation: move 6s linear 1 forwards;</pre>

    <p>animation 是一个简写属性,它包含多个 animation-* 属性:</p>
    <blockquote>
      <ol>
            <li><span class="tRed">animation-name</span> :由 @keyframes 创建的动画名称,支持单个、多个</li>
            <li><span class="tRed">animation-duration</span> :动画周期时长,即完成一个动画周期所需的时间,时间单位</li>
            <li><span class="tRed">animation-timing-function</span> : 时间函数,即动画在每个周期的持续时间内如何进行,例如匀速、先快后慢、两头慢中间快或自定义运动时间曲线等等(文末附详细列表)</li>
            <li><span class="tRed">animation-delay</span> :动画延时时间,本质指动画执行之前等待的时间量,支持正(延后)负(提前)数,时间单位</li>
            <li><span class="tRed">animation-iteration-count</span> :动画运行次数,小于等于0表示不运行,infinite 表示永久运行</li>
            <li><span class="tRed">animation-direction</span> :运动方向,即播放动画的方向与 @keyframes 时间节点指向的关系,值有 <span class="tBlue">normal</span>(默认,正向)、<span class="tBlue">reverse</span>(反向)、<span class="tBlue">alternate</span>(往复)、<span class="tBlue">alternate-reverse</span>(反向往复)</li>
            <li><span class="tRed">animation-fill-mode</span> :动画结束时的状态,确切以言之,该属性设置 CSS 动画在执行之前和之后如何将样式应用于其目标。默认值是 <span class="tBlue">none</span>,可选值 <span class="tBlue">forwards</span>(保留最后帧状态)、<span class="tBlue">backwards</span>(首帧状态)</li>
            <li><span class="tRed">animation-play-state</span> :动画播放状态,设置动画播放(<span class="tBlue">running</span>)或暂停(<span class="tBlue">paused</span>)</li>
      </ol>
    </blockquote>
    <p>属性较多,可以根据需要取舍,逐个设置时顺序不论:</p>
    <pre class="hlCode">.ball {
    <span class="tGreen">/* ... 这里是其它代码 */</span>
    animation-name: move;
    animation-duration: 6s;
    animation-timing-function: linear;
    animation-iteration-count: 1;
    animation-fill-mode: forwards;
}</pre>

    <p>建议尽可能使用 animation 简写属性设置动画。</p>
    <h2>三、动画实例</h2>

    <h3>(一)元素平移<button class="btnRun">运行代码</button></h3>
    <pre class="hlCode preRun">&lt;style&gt;
    .rect {
      width: 40px;
      height: 40px;
      background: cyan;
      transform: translate(0,100px);
      /* 匀速、8秒一个周期、永动、往复(来、回各算一个周期) */
      animation: move linear 8s infinite alternate;
    }

    /* 动画 :水平移动 */
    @keyframes move {
      to { transform: translate(800px,100px); }
    }
&lt;/style&gt;

&lt;div class="rect"&gt;&lt;/div&gt;</pre>

    <h3>(二)元素旋转<button class="btnRun">运行代码</button></h3>
    <pre class="hlCode preRun">&lt;style&gt;
    .rect {
      margin: 200px;
      width: 180px;
      height: 180px;
      background: lightgreen;
      /* 慢-快-慢、5秒一个周期、2秒后开始、转3圈 */
      animation: rot 5s ease-in-out 2s 3;
    }

    /* 动画 :旋转360度 */
    @keyframes rot {
      100% { transform: rotate(360deg); }
    }
&lt;/style&gt;

&lt;div class="rect"&gt;&lt;/div&gt;</pre>

    <h3>(三)元素形状变化(分步)<button class="btnRun">运行代码</button></h3>
    <pre class="hlCode preRun">&lt;style&gt;
    .rect {
      margin: 100px;
      width: 300px;
      height: 300px;
      border: 4px dotted purple;
      /* 匀速、6秒一个周期、分四步(阶跃运动)、保留最后状态 */
      animation: rot 6s steps(4, end) forwards;
      /* animation: rot 6s steps(3, start) forwards; */
    }
   
    /* 动画 :从矩形变成圆形 */
    @keyframes rot {
      0% { border-radius: 0%; }
      100% { border-radius: 50%; }
    }
&lt;/style&gt;

&lt;div class="rect"&gt;&lt;/div&gt;</pre>

    <h3>(四)多个元素转圈圈<button class="btnRun">运行代码</button></h3>
    <pre class="hlCode preRun">&lt;style&gt;
    /* 父元素 */
    .pa { margin: 100px auto; width: 300px; height: 300px; border-radius: 50%; border: 3px dashed gray; position: relative; }

    /* 子元素 */
    .rect {
      position: absolute;
      width: 60px;
      height: 60px;
      background: tan;
      transform-origin: 150px 150px; /* 运动中心点坐标设在父元素中心点 */
      /* 6秒一个周期、匀速、运行一百次 */
      animation: ring 6s linear 100;
    }
   
    /* 第2、3子元素提前运行动画 */
    .rect:nth-of-type(2) { animation-delay: -2s; }
    .rect:nth-of-type(3) { animation-delay: -4s; }

    /* 动画 :旋转一周 */
    @keyframes ring {
      to { transform: rotate(1turn); }
    }
&lt;/style&gt;
&lt;div class="pa"&gt;
    &lt;div class="rect"&gt;&lt;/div&gt;
    &lt;div class="rect"&gt;&lt;/div&gt;
    &lt;div class="rect"&gt;&lt;/div&gt;
&lt;/div&gt;</pre>

    <h3>(五)元素做椭圆运动<button class="btnRun">运行代码</button></h3>
    <pre class="hlCode preRun">&lt;style&gt;
    /* 本例 offset-path 支持百分比,所以是自适应窗口尺寸的 */
    .ball {
      width: 80px;
      height: 80px;
      border-radius: 50%;
      background: linear-gradient(20deg, cyan, green);
      /* 椭圆路径 :以容器(这里是body)宽高各45%做长短轴半径、圆心在body中心点 */
      offset-path: ellipse(45% 45% at 50% 50%);
      animation: move linear 20s infinite;
    }
    /* 动画 :在路径中偏移 */
    @keyframes move {
      0% { offset-distance: 0%; }
      100% { offset-distance: 100%; }
    }
&lt;/style&gt;

&lt;div class="ball"&gt;&lt;/div&gt;</pre>

    <h2>四、使用JS简单控制关键帧动画</h2>
   
    <p>对关键帧动画最常见的需求是动画的播放与暂停操作。这可以使用JS直接操作元素的 animation-play-state 属性:前面已经提到过,属性值 running 表示播放、paused 表示暂停。看实例:</p>
   
    <h3>&#128313;JS简单控制CSS关键帧动画<button class="btnRun">运行代码</button></h3>
    <pre class="hlCode preRun">&lt;style&gt;
    .kite {
      cursor: pointer;
      transform-origin: 100% 0;
      animation: skew ease-out 0.85s infinite alternate;
    }
    /* 动画 :在路径中偏移 */
    @keyframes skew {
      to { transform: skew(5deg); }
    }
&lt;/style&gt;

&lt;img class="kite" src="https://638183.freep.cn/638183/small/kite.png" alt="" title="点击播放/暂停" /&gt;

&lt;script&gt;
    const kite = document.querySelector('.kite');
    kite.onclick = () =&gt; {
      // 使用 getAnimations API 获取元素的CSS动画相关信息
      const { playState } = kite.getAnimations();
      // 如果动画运行中则暂停
      if (playState === 'running') {
            kite.style.setProperty('animation-play-state', 'paused');
      // 反之继续播放
      } else {
            kite.style.setProperty('animation-play-state', 'running');
      }
    };
&lt;/script&gt;</pre>

    <p><mark>Element.getAnimations()</mark> API 不是必须的,上例用到它主要是为了判断元素运行动画的状态(running或paused),从而决定点击图片时动画应暂停还是继续。在实际应用场景中,可以根据诸多情况来决定动画处于何种状态,例如音画帖中关键帧动画与音频的播放、暂停状态的紧密关联。另外,如果由于某种原因不方便直接操作CSS动画播放状态属性,我们可以使用 CSS 变量来代替 animation-play-state 的属性值,直接操作该变量,这样就不用针对每一个元素进行操作,而是批量操作所有使用了该 CSS 变量的所有元素:</p>
   
    <pre class="hlCode">/* CSS 代码 :父元素定义 --state 变量值 */
.pa {
    --state: running;
}
/* 子元素动画简写属性使用 --state 变量给 animation-play-state 属性赋值 */
.son {
    animation: skew ease-out 0.85s infinite alternate var(--state);
}

// JS代码 :批量操作 .pa 下的所有使用了 var(--state) 的动画
const pa = document.querySelector('.pa');
// 如果要播放动画:
pa.style.setProperty('--state', 'running');
// 如果要暂停动画:
pa.style.setProperty('--state', 'paused');</pre>

    <p>对于非无穷大的动画播放次数,有时候可能需要监听动画的开始、结束动作,以便决定动画流程的下一步环节,这时有两个JS事件可用:<mark>animationstart</mark>(动画开始)和 <mark>animationend</mark>(动画结束):</p>

    <h3>&#128313;JS监听动画开始和动画结束事件<button class="btnRun">运行代码</button></h3>
    <pre class="hlCode preRun">&lt;style&gt;
    .rect { left: 20px; top: 100px; width: 200px; height: 100px; display: grid; place-items: center; background: wheat; position: absolute; }
    .ani1 { animation: move linear 16s forwards; }
    .ani2 { animation: move linear 8s reverse forwards; }
    @keyframes move {
      to { left: calc(100% - 220px); }
    }
&lt;/style&gt;

&lt;div class="rect"&gt;点击开始动画&lt;/div&gt;

&lt;script&gt;
    const rect = document.querySelector('.rect');
    const anis = ['ani1', 'ani2']; // 动画数组
    let idx = 0; // 动画运行计数
   
    // 动画开始事件
    rect.onanimationstart = () =&gt; rect.textContent = `动画 :${anis}`;
   
    // 动画结束事件
    rect.onanimationend = () =&gt; {
      rect.classList.remove(anis);
      idx += 1;
      setTimeout( () =&gt; rect.classList.add(anis), 0);
    };
   
    rect.onclick = () =&gt; {
      // 若动画尚未开始
      if (!rect.getAnimations()) {
            rect.classList.add(anis); // 开始动画
      // 若动画已经开始
      } else {
            const { playState } = rect.getAnimations(); // 获取动画状态
            // 暂停或继续动画
            rect.style.setProperty(
                'animation-play-state',
                playState === 'running' ? 'paused' : 'running'
            );
      }
      
    };
&lt;/script&gt;</pre>
    <p>JS 操作 CSS 关键帧动画远不止这些,限于篇幅,本文点到而止。</p>

    <h2>附:animation-timing-function 属性值及其含义</h2>
    <table>
      <tr>
            <th style="width: 120px">属性值</th>
            <th>属性值说明</th>
      </tr>
      <tr>
            <td>linear</td>
            <td>匀速,对应于贝赛尔曲线 cubic-bezier(0, 0, 1, 1)【第一控制点 {0,0},第二控制点{1,1}】</td>
      </tr>
      <tr>
            <td>ease</td>
            <td>慢-快-慢(缺省默认,与 ease-in-out相似,但在开始时加速更陡,对应于 cubic-bezier(0.25, 0.1, 0.25, 1.0)</td>
      </tr>
      <tr>
            <td>ease-in</td>
            <td>先慢后快,对应于 cubic-bezier(0.42, 0.0, 1.0, 1.0)</td>
      </tr>
      <tr>
            <td>ease-out</td>
            <td>先快后慢,对应于 cubic-bezier(0.0, 0.0, 0.58, 1.0)</td>
      </tr>
      <tr>
            <td>ease-in-out</td>
            <td>慢-快-慢,开始时表现同 ease-in,结束时表现同 ease-out,对应于 cubic-bezier(0.42, 0.0, 0.58, 1.0)</td>
      </tr>
      <tr>
            <td>cubic-bezier(x1, y1, x2, y2)</td>
            <td>三次贝塞尔曲线,速度曲线,n1~y2可能的值都是从 0 到 1 的数值</td>
      </tr>
      <tr>
            <td>step-start</td>
            <td>在变化过程中,都是以下一帧的显示效果来填充间隔动画</td>
      </tr>
      <tr>
            <td>step-end</td>
            <td>在变化过程中,都是以上一帧的显示效果来填充间隔动画</td>
      </tr>
      <tr>
            <td>steps()        </td>
            <td>阶跃函数,用于实现逐帧动画(如精灵图切换),动画过程是断断续续的。可以传入两个参数,第一个是一个大于0的整数,他是将间隔动画等分成指定数目的小间隔动画,然后根据第二个参数来决定显示效果。第二个参数设置后其实和step-start,step-end同义,在分成的小间隔动画中判断显示效果</td>
      </tr>
    </table>
</div>

<script>
    var btns = document.querySelectorAll('.btnRun');
    var pres = document.querySelectorAll('.preRun');

    btns.forEach((btn, idx) => {
      btn.onclick = () => {
            runCodes(pres);
      };
    });
   
    function runCodes(pre) {
      var code = pre.textContent;
      var previewWindow = window.open('', 'showWin', 'width=1280,height=720');
      previewWindow.document.open();
      previewWindow.document.write(code);
      previewWindow.document.close();
    }
</script>

klxf 发表于 昨天 22:24

系统、实用 的教程,谢谢分享

醉美水芙蓉 发表于 昨天 22:37

klxf 发表于 2026-7-28 22:24
系统、实用 的教程,谢谢分享

谢谢友友光临!
页: [1]
查看完整版本: 马黑黑《CSS关键帧动画初步》