CSS 探索系列之动画

动画是前端人机交互里重要的一环,合理的动效可以引导用户操作、反馈状态变化、提升页面整体体验。本文聚焦 CSS 体系下两大核心能力:transition(过渡)与 animation(关键帧动画)。

动画基础概念

网页动画原理来自人眼视觉暂留:短时间连续播放多幅静态画面,人眼就会产生物体在运动的错觉。

  • 帧(frame):动画中的每一张静态画面。
  • 帧率(fps):每秒播放的画面数量。
  • 帧时长:单帧画面停留时间,单位 ms。
  • 跳帧(掉帧):某一帧处理耗时过长,挤压后续帧,造成动画卡顿。

常见屏幕刷新率为 60Hz,对应 60fps,单帧理想耗时约 16.7ms;也有 90Hz、120Hz 屏幕,单帧更短。动画帧率贴近屏幕刷新率,观感更流畅。

多种实现方案

CSS transition(过渡)

实现两个状态之间的平滑过渡,需要状态变更触发(hover、class 切换、JS 修改样式)。

特点:浏览器接管插值计算,可硬件加速;仅支持起始、结束两个状态,没有中间关键帧。

CSS animation(关键帧动画)

配合 @keyframes 定义多段关键帧,无需外部事件触发就可以自动播放,支持循环、方向控制、停留状态,CSS 原生能力最强。

Canvas 动画

通过 Canvas API 在画布上不断清除、重绘图形。

适用:大量粒子、复杂游戏画面;完全由 JS 控制渲染逻辑。

SVG 原生动画

<animate><animateTransform> 等标签,直接在 SVG 内部定义属性动画。

优势:矢量无损。

缺点:复杂场景性能较差,只能作用于 SVG 内部元素。

定时器动画

通过 setIntervalsetTimeout 循环修改 DOM 样式实现动画。

原理:定时回调不断更新元素样式。

缺点:容易频繁触发重排、重绘,移动端容易卡顿;定时器时间与浏览器刷新时钟不同步,会出现跳帧。

注意:习惯写 16ms 间隔,目标贴近 60fps,但不能和浏览器刷新节拍对齐,不是最优方案。

requestAnimationFrame

浏览器专门为动画提供的 API,回调执行时机和屏幕刷新节拍对齐。

注意:相比定时器,避免时钟不同步问题,性能更优;依然需要 JS 手动更新每帧样式。

选型建议

  • 简单状态变化:优先 transition
  • 自动播放、循环、多关键帧:优先 animation
  • 大量粒子、游戏:Canvas
  • SVG 图形动效:SVG 原生动画
  • JS 精细控制每帧逻辑:requestAnimationFrame

CSS transition 过渡

transition 实现隐式过渡:只定义初始状态和结束状态,中间所有插值由浏览器自动计算,必须依靠状态变更来触发(hover、增加 class、JS 改样式)。

<style>
    .transition1,
    .transition2 {
        display: inline-block;
        width: 100px;
        height: 100px;
        background-color: #0aa;
        border-radius: 20px;
    }
    .transition1:hover,
    .transition2:hover {
        width: 200px;
        height: 200px;
        background-color: orange;
        transform: rotate(180deg);
        border-radius: 100px;
    }
    .transition2 {
        transition: width 2s, height 2s, background-color 2s, transform 2s, border-radius 2s;
    }
</style>
<section>
    <div class="transition1"></div>
    <div class="transition2"></div>
</section>

transition - DEMO

transition 子属性

transition: <property> <duration> <timing-function> <delay>;

支持多属性逗号分隔:

transition: width 2s, height 2s, transform 2s;
  1. transition-property:指定需要过渡的 CSS 属性;未声明的属性会直接瞬间变化,不参与过渡。
  2. transition-duration:过渡持续时间,单位 sms,默认 0s;不设非 0 时长就看不到过渡。
  3. transition-timing-function 缓动函数,控制动画进度曲线。
    • ease:先快后慢(默认)
    • ease-in:由慢到快(加速)
    • ease-out:由快到慢(减速)
    • ease-in-out:先加速后减速
    • linear:匀速
    • cubic-bezier(x1,y1,x2,y2):自定义贝塞尔曲线
    • steps(n, pos):阶梯式分段动画;step-startstep-end 为简写。
  4. transition-delay:过渡开始前的等待时间;支持负值,负值会直接跳过部分起始阶段。
/* steps(4,end):切4段,共发生4次跳跃;默认position=end可以省略 */
.step-end {
  transition: transform 2s steps(4);
}
/* steps(4,start):一开始就跳第一阶 */
.step-start {
  transition: transform 2s steps(4, start);
}

属性列表长度匹配规则:如果各个子属性逗号分隔的列表长度不一致,会以 transition-property 的长度为准,短的列表循环复用;超出长度的项直接截断。

div {
  /* 要过渡3个属性 */
  transition-property: opacity, left, top;
  /* duration只写2个时长 */
  transition-duration: 3s, 5s;
}

transition 事件

现代浏览器一共提供 4 个 transition 事件,均为 TransitionEvent 接口:

  • transitionrun:过渡任务被创建,延迟阶段一开始就触发,即便还处在 transition-delay 等待期。
  • transitionstart:延迟结束,属性插值变化正式开始时触发;无延迟、负延迟时,与 transitionrun 几乎同时触发。
  • transitionend:过渡完整执行完毕触发;如果中途被打断,则不会触发
  • transitioncancel:过渡被中断、取消时触发;一旦触发该事件,就不会再执行 transitionend

每一个参与过渡的 CSS 属性,分别触发一套完整事件;通过事件对象 event.propertyName 判断是哪个属性:

elem.addEventListener('transitionend', (e) => {
    console.log('过渡结束:', e.propertyName);
});
elem.addEventListener('transitioncancel', (e) => {
    console.log('过渡被中断:', e.propertyName);
});

注意:如果在过渡还没结束时,属性被再次修改,或者元素变成 display: nonetransitionend 将不会触发。

transition 的局限

  • 必须依靠状态事件触发,页面加载不能自动播放。
  • 只有起始、结束两个状态,无法定义中间关键帧。
  • 属于一次性动画,想要重复需要再次触发状态变更。
  • 直接 appendChild 新增元素、或者把 display: none 切回可见,不会产生过渡;DOM 直接落在结束状态。

解决方法:DOM 插入后,使用短暂 setTimeout 延迟再修改样式,浏览器可以捕获到状态差异。

CSS animation 关键帧动画

animation 搭配 @keyframes 关键帧规则,可以定义一套完整动画序列,支持自动播放、循环、反向、结束停留,不需要事件触发。

<style>
    .animation {
        width: 50px;
        height: 50px;
        margin-bottom: 10px;
        background-color: #0aa;
        border-radius: 25px;
        animation: move2 2s ease 1s infinite;
    }
    @keyframes move2 {
        0% {
            transform: translate(0, 0);
            background-color: red;
        }
        100% {
            transform: translate(552px, 0);
            background-color: orange;
        }
    }
</style>
<section>
    <div class="animation"></div>
</section>

animation - DEMO

animation-step - DEMO

animation-direction - DEMO

animation-fill-mode - DEMO

立体动画 - DEMO

animation 子属性

简写完整默认:

animation: none 0s ease 0s 1 normal none running;
  1. animation-name@keyframes 关键帧的名称

  2. animation-duration:单次动画周期时长,默认 0s;不设非 0 时长就看不到动画

  3. animation-timing-function:关键帧之间的缓动函数,用法同 transition

  4. animation-delay:动画开始前延迟时间

  5. animation-iteration-count:循环次数。数字、infinite(无限循环),支持小数

  6. animation-direction:播放方向。normal 正向;reverse 反向;alternate 来回交替;alternate-reverse 先反向再交替

  7. animation-fill-mode:动画前后如何应用样式。none 默认;forwards 结束后停留在最后一帧;backwards 在 delay 阶段应用起始关键帧(reverse 时应用的是结束关键帧);both 同时开启 forwardsbackwards

  8. animation-play-staterunning 运行、paused 暂停;可用 JS 动态控制启停

@keyframes 关键帧

使用百分比标记时间点:

  • 0% 等价别名 from,动画起始;
  • 100% 等价别名 to,动画结束;
  • 中间可以写任意百分比节点,定义中间状态;
  • 如果省略 0%100%,浏览器取元素当前计算样式作为起始、结束。

动画事件

  • animationstart:动画开始
  • animationiteration:每一次新循环开始
  • animationend:动画完整结束
  • animationcancel:动画被中断、取消时触发;一旦触发,就不会再执行 animationend
elem.addEventListener('animationstart', listener);
elem.addEventListener('animationiteration', listener);
elem.addEventListener('animationend', listener);
elem.addEventListener('animationcancel', listener);

animation 优缺点

优点

  • 无需 JS 触发,可自动播放;
  • 支持多关键帧、循环、反向、状态停留;
  • 浏览器做优化,后台标签页会降频。

缺点

  • 运行时难以 JS 动态修改关键帧内容;
  • 精细的时间轴控制能力弱于 JS 动画库。

重启动画小技巧

CSS 本身没有直接重置动画语法,常见方法:

  • JS 移除动画 class,短暂延迟后重新加上;
  • 克隆替换 DOM 节点;
  • 修改 animation-name 切换关键帧名称。

主线程与合成线程

浏览器渲染分为两大线程:

  • 主线程:执行 JS、CSS 样式计算、布局(重排 reflow)、绘制(重绘 repaint),生成图层位图。
  • 合成线程(Compositor):交给 GPU,只做图层的位移、缩放、旋转、透明度,不触发重排重绘,性能极高。

性能等级

  • 合成线程动画(最优):transformopacity;不会触发重排重绘,GPU 加速。
  • 重绘:background-colorbox-shadow;触发绘制,不触发布局。
  • 重排(最差):widthheighttopmarginline-height;每一帧都会重新布局,开销巨大。

开发最佳实践:优先使用 transform + opacity 做动画,尽量避免动画过程修改会触发重排的属性。

备注transform: translateZ(0)translate3d(0,0,0) 可以强制提升到独立合成图层,但不要滥用,图层过多会消耗大量显存。

可动画属性

并不是所有 CSS 属性都支持插值动画,MDN 有完整「动画性 CSS 属性」列表。

能否做过渡、动画,由该属性的「动画类型(animation-type)」决定:

  • 可插值(interpolable):可以计算中间值,实现平滑渐变,transitionanimation 直接生效。
  • 离散(discrete):没有中间插值,值在进度约 50% 时直接跳转,不是平滑过渡。

可插值属性分类(平滑动画)

  • 变换类(性能最优,推荐优先使用):transformrotatescaletranslatetransform-origin

  • 透明度、滤镜:opacityfilterbackdrop-filter

  • 颜色类:colorbackground-colorborder-coloroutline-colorbox-shadowtext-shadow(阴影整体插值)

  • 尺寸、间距(会触发重排,性能较差):widthheightmin-widthmax-widthmin-heightmax-heightmargin-*padding-*border-radiusborder-width

  • 位置(重排,不推荐做动画):topleftrightbottom

  • 字体文本:font-sizefont-weightfont-stretchfont-variation-settingsletter-spacingword-spacingline-heightvertical-align

  • 背景:background-positionbackground-size

  • 其他:z-index(整数插值)、clip-pathflexflex-growflex-shrinkmask 相关属性

  • all 关键字:一次性指定全部可动画属性;不推荐生产使用,大量属性加入过渡,容易引发性能问题。

离散属性(无平滑插值,默认不能做平滑过渡)

  • display
  • position
  • content
  • font-family
  • overflow
  • pointer-events
  • visibility(特殊:visibility: hiddenvisible 有独立插值规则,可见状态会尽量保持到动画后段再跳转)

  • 新特性transition-behavior: allow-discrete 可以让离散属性参与过渡流程;不会产生平滑渐变,只是把值切换纳入过渡的开始、结束时机,而不是默认的进度 50% 跳变。

不要对 auto 值做过渡、动画auto 是运行时布局计算值,规范没有定义插值规则,浏览器行为不一致,效果不可预期。

transition 和 animation 核心对比

对比项 transition animation
触发方式 需要状态变更触发(hover、class、JS) 可自动播放,无需外部触发
关键帧 只有起始、结束两个状态 支持 @keyframes 多关键帧
循环 不能原生循环,需要重复触发 支持 infinite 无限循环
结束状态 触发态还在时停在结束值;触发态取消后过渡回原样式 默认结束后回到动画前样式;fill-mode 可保持结束帧

周边工具库

  • Animate.css:现成预设 CSS 动画类库,直接加 class 使用;v4 版本带 animate__ 前缀,支持 CSS 变量自定义时长延迟;注意做好 prefers-reduced-motion 无障碍兼容。
  • Anime.js:JS 动画库,可以操作 CSS、SVG、DOM 对象,强大时间轴、交错、缓动能力。
  • Lottie-web:解析 AE 导出的 JSON 动效,设计师产出 AE 动画,前端直接渲染,适合复杂业务动效。
  • Three.js:WebGL 3D 渲染,三维场景动画。

参考资料

MDN 使用 CSS transitions

MDN 使用 CSS 动画

MDN CSS 动画属性

© lizhao all right reserved,powered by Gitbook文件修订时间: 2026-08-19 22:08:49

results matching ""

    No results matching ""