Kodokon kodokon.com

过渡与动画:@keyframes、transform、时序

用 transform 和 opacity 在合成器上做动画、组织好你的 @keyframes,并尊重用户的动效偏好。

10 分钟 · 3 题

在 Kodokon 中打开本课

在选择动画什么之前,先回顾一下渲染管线:布局(几何)、绘制(像素)、合成(在 GPU 上组装图层)。对 widthtop 做动画会在每一帧触发一次布局,往往紧接着一次绘制:这正是掉到 60 fps 以下、产生卡顿的配方。transformopacity 由合成器直接处理,无需再经过前两个阶段。专业法则:尽可能只对这两个属性做动画,其余的都用它们来模拟(用 translate 代替 top,用 scale 代替 width)。

CSS
.button {
  transform: translateY(0);
  transition:
    transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1),
    opacity 0.15s ease-out;
}

.button:hover {
  transform: translateY(-2px);
}

.button:active {
  transform: translateY(0) scale(0.97);
}
对合成器友好、带自定义曲线的过渡

当过渡不再够用时(中间步骤、循环、自动开始),就转向 @keyframes。有两个设置决定了一段动画是干净利落还是有视觉 bug:animation-fill-mode 控制开始之前的状态(backwards 在延迟期间应用第一个关键帧)和结束之后的状态(forwards 冻结最后一个关键帧);both 则两者兼备。还要注意,时序函数是应用在每一对关键帧之间,而不是覆盖总时长:你可以在某个关键帧内部重新定义它。

CSS
@keyframes slide-in {
  from {
    transform: translateX(-100%);
    opacity: 0;
  }
  70% {
    transform: translateX(8px);
    opacity: 1;
  }
  to {
    transform: translateX(0);
  }
}

.toast {
  animation: slide-in 0.35s ease-out both;
}
带轻微过冲的入场

有两个值得了解的互补工具。will-change: transform 会在动画开始前把元素提升到它自己的图层上,避免一次迟到的栅格化;但每个图层都会消耗 GPU 内存,所以要在动画即将开始前才应用它、之后再移除,绝不要把它作为永久样式放在几十个元素上。steps(n) 用离散的跳跃取代连续插值:对精灵图、打字机式光标或时钟来说不可或缺。

CSS
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
尊重 prefers-reduced-motion

知识检测

确认你已牢记本课的重点内容。

  1. 对于一段动画位移,为什么应优先选 transform: translateX(100px) 而非 left: 100px
    • transform 对旧版浏览器的支持更好
    • transform 由合成器处理,无需在每一帧重新计算布局
    • left 只对绝对定位元素有效,而 transform 到处都能用
    • transform 接受负值,而 left 不接受
  2. animation-fill-mode: both 有什么作用?
    • 动画先正向播放,然后再反向播放
    • 动画同时应用到元素及其后代
    • 第一个关键帧的样式在延迟期间应用,最后一个关键帧的样式在结束后保持
  3. will-change 的正确用法是什么?
    • * 上全局应用,以加速整个页面
    • 在一段昂贵的动画即将开始前加上它,之后再移除
    • 始终与 transform: translateZ(0) 搭配使用
    • 用它来代替微交互中的 transition