关键帧动画:与过渡的区别是「自己走」而不是「被状态推着走」

① 最小可运行

正在加载
/* 关键帧:名字 + 若干进度点 */
@keyframes nudge { from { transform: translateX(0); } to { transform: translateX(28px); } }
.nudge { animation: nudge 1s ease-in-out infinite alternate; }

/* 隐式关键帧:0%/100% 只写一个也可以,另一个自动取元素的「当前计算值」 */
@keyframes half { 50% { transform: scale(1.2); } }     /* 起终点由元素自身样式决定 */

② animation 属性族(九件事)


属性取值要点
animation-name关键帧名 / none多个动画用逗号并列
animation-duration时间简写里第一个时间是时长、第二个是延迟,顺序反了含义就变
animation-timing-function缓动它作用于每两个关键帧之间,也可以写在关键帧内部单独指定
animation-delay时间(可为负)负延迟 = 从动画中段开始播,做「错峰循环」非常有用
animation-iteration-count数字 / infinite / 小数小数会播到该比例
animation-directionnormal/reverse/alternate/alternate-reversealternate 让来回共用一个关键帧定义
animation-fill-modenone/forwards/backwards/bothforwards 结束后停在最后一帧;backwards 让延迟期间就显示第一帧(进入动画常配 both)
animation-play-staterunning/paused可被 CSS 类控制,无需 JS 计时
animation-compositionreplace/add/accumulate与内联样式/其它动画叠加时不覆盖(新)

优先级提醒:进行中的动画声明高于普通声明与 !important(第十二章的层叠表里排在最上)。所以「动画一跑,hover 就无效」是正常行为,要停下来必须先结束动画。

③ 进入动画与错峰(stagger)

第一行
第二行 delay .12s
第三行 delay .24s
@keyframes rise { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
.item { animation: rise .5s ease-out both; }
/* 用自定义属性把延迟交给结构,避免给每一项写一条规则(第十四章的用法): */
.item { animation-delay: calc(var(--i, 0) * 80ms); }   /* 
  • */
  • ④ 多个动画并列与 steps() 逐帧

    /* 淡入 + 平移循环同时挂:逗号分隔,第一个名字对应第一个时长 */
    .multi { animation: fade-in .6s ease-out both, nudge 1.4s ease-in-out infinite alternate; }
    /* 文字三点加载:给伪元素的 content 做关键帧(content 是少数可被 steps 离散插值的属性) */
    @keyframes dots { 0%,20% { content: ""; } 40% { content: "."; } 60% { content: ".."; } 80%,100% { content: "..."; } }
    .loading::after { content: ""; animation: dots 1.4s steps(1, end) infinite; }
    /* 骨架屏:background-position 动画(见示例中的 shimmer 条) */

    ⑤ will-change 与合成层:先别急着写

    /* 作用:提前把元素提升为合成层,避免动画开始时才建层造成的一帧卡顿 */
    .box { will-change: transform; }
    /* 三条纪律:
       ① 只给「马上要动」的元素加,别全局加(每个合成层都吃显存,几十层就负收益);
       ② 动画结束后用 JS 移除,或写在 :hover 的下拉里(配合过渡);
       ③ 现代 Chrome/Edge/Safari 对 transform/opacity 动画已自动分层,实测收益常为零 → 先用 DevTools 的
          Rendering → Paint flashing / Layers 面板确认,再决定加不加。
       副作用:will-change 会创建层叠上下文(第十二章)与包含块,可能改变 fixed 的参考系。 */

    ⑥ 滚动驱动动画(新增能力)

    在这个盒子内滚动,进度条会跟着滚动位置增长 —— 由 animation-timeline: scroll(self) 驱动,不需要任何 JS 监听 scroll。

    滚动到底部时进度条应铺满。

    .bar { animation: grow linear both; animation-timeline: scroll(self); }
    /* 也可以用命名滚动轴 / 元素可见度:animation-timeline: view(block 20% 60%)  */
    /* 浏览器基线:scroll-driven animations 目前在 Chromium 系(Chrome/Edge 115+)可用,
       Safari/Firefox 仍在演进 —— 属于渐进增强:不支持时元素按 both 停在末帧或首帧,
       所以必须保证「动画不跑时视图也成立」。请以 caniuse 当前数据为准。 */

    ⑦ 动画的排错清单

    1. 「动画不跑」→ 检查 animation-name 拼写、duration 是否为 0、元素是否 display: none。
    2. 「重放不生效」→ 同名动画重复触发需要先移除类并强制回流(或改 animation-name),示例上方的复选框演示了这个手法。
    3. 「动画结束后跳回原位」→ 缺 animation-fill-mode: forwards。
    4. 「抖动/闪烁」→ 多半是动画在触发布局;改用 transform/opacity(18a 第⑤节)。
    5. 「没有降级」→ 用 prefers-reduced-motion 关掉无限循环动画(第十三章)。