3. CSS过渡(Transition)实战:transition-property、duration、timing-function、delay的完整用法

好,咱们今天来聊聊CSS过渡。说实话,过渡是前端动画里最基础、也最容易被忽视的一块。很多人觉得不就是个transition嘛,写个all 0.3s就完事了。嗯,我以前也这么干过,直到有一次在移动端项目里被性能问题狠狠教育了一顿。

过渡的核心,说白了就是让属性值的变化「不那么生硬」。你想想看,一个按钮hover时突然变红,跟它慢慢变红,用户的感受是完全不同的。今天我就把transition的四个核心属性掰开揉碎了讲给你听。

3.1 transition-property:别偷懒用all

这个属性指定哪些CSS属性要参与过渡。很多人习惯写transition: all 0.3s,我个人建议——别这么干

我曾经在一个大型后台管理系统里用了all,结果页面卡得不行。后来排查发现,连box-shadow、transform这些高消耗属性都在跟着过渡,GPU直接拉满。

正确的做法是:只列出你需要过渡的属性

/* 不推荐 */
.element {
  transition: all 0.3s ease;
}

/* 推荐 */
.element {
  transition: opacity 0.3s ease, transform 0.3s ease;
}

哪些属性可以过渡?我整理了一张表:

属性类别 示例 性能表现
变换类 transform GPU加速,流畅
透明度 opacity GPU加速,流畅
颜色类 color, background-color CPU计算,一般
尺寸类 width, height 触发重排,较慢
定位类 top, left 触发重排,较慢
小技巧:优先使用transform和opacity做动画。这两个属性走合成层,不会触发重排和重绘。我在项目中几乎所有的动效都优先考虑用transform来实现。

3.2 transition-duration:时间不是越长越好

这个控制动画时长。单位是秒(s)或毫秒(ms)。

.box {
  transition-property: opacity;
  transition-duration: 0.3s;  /* 300ms */
}

时长怎么选?我有个经验法则:

  • 100-200ms:微交互,比如按钮点击反馈、开关切换
  • 300-400ms:常规过渡,比如面板展开、元素出现
  • 500-800ms:页面转场、大范围动画
  • 超过1s:慎用,除非是强调性的加载动画

为什么会这样?因为人眼的视觉暂留和注意力集中时间就那么多。太短了看不清,太长了用户会烦躁。我记得有次做登录页的loading动画,设了1.5s,测试的时候自己都觉得煎熬。

3.3 transition-timing-function:动画的灵魂

这个属性决定了动画的「节奏感」。说白了就是速度怎么变化。

常用的几个值:

.box {
  /* 匀速 - 最生硬,不推荐 */
  transition-timing-function: linear;

  /* 慢快慢 - 最自然,推荐 */
  transition-timing-function: ease;

  /* 慢入 - 适合元素出现 */
  transition-timing-function: ease-in;

  /* 慢出 - 适合元素消失 */
  transition-timing-function: ease-out;

  /* 慢入慢出 - 适合强调动画 */
  transition-timing-function: ease-in-out;

  /* 自定义贝塞尔曲线 */
  transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
}

重点来了:cubic-bezier才是真正体现功力的地方。我常用的几个曲线:

  • cubic-bezier(0.68, -0.55, 0.27, 1.55) — 带弹性效果
  • cubic-bezier(0.4, 0, 0.2, 1) — Material Design标准曲线
  • cubic-bezier(0.22, 1, 0.36, 1) — 快速弹出效果

你想想看,一个卡片从底部滑入,如果用linear,就像机器人推出来一样僵硬。但用ease-out,就像被弹簧弹出来,视觉上舒服多了。

3.4 transition-delay:让动画有层次

这个属性设置动画开始前的等待时间。很多人忽略它,但用好delay能让动画有「编排感」。

.item {
  transition: opacity 0.3s ease;
}

/* 列表项依次出现 */
.item:nth-child(1) { transition-delay: 0s; }
.item:nth-child(2) { transition-delay: 0.1s; }
.item:nth-child(3) { transition-delay: 0.2s; }
.item:nth-child(4) { transition-delay: 0.3s; }

我在项目中遇到过这样一个场景:一个弹窗里有三个按钮,如果同时出现,用户会不知道该先看哪个。后来我给每个按钮加了0.05s的延迟差,视觉上就有了「主次感」。

注意:delay不要设太大。超过0.5s的延迟会让用户觉得「卡了」。我一般控制在0.05s到0.3s之间。

3.5 完整实战:一个卡片列表的入场动画

来,咱们写一个完整的例子。假设有一个卡片列表,需要依次淡入并上移:

<div class="card-list">
  <div class="card">卡片1</div>
  <div class="card">卡片2</div>
  <div class="card">卡片3</div>
  <div class="card">卡片4</div>
</div>
.card {
  opacity: 0;
  transform: translateY(20px);
  transition-property: opacity, transform;
  transition-duration: 0.4s;
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

/* 页面加载后添加这个类 */
.card-list.loaded .card {
  opacity: 1;
  transform: translateY(0);
}

/* 依次延迟 */
.card-list.loaded .card:nth-child(1) { transition-delay: 0s; }
.card-list.loaded .card:nth-child(2) { transition-delay: 0.1s; }
.card-list.loaded .card:nth-child(3) { transition-delay: 0.2s; }
.card-list.loaded .card:nth-child(4) { transition-delay: 0.3s; }

这个效果我用了不下几十次。卡片依次出现,每个间隔0.1s,视觉上就像多米诺骨牌一样流畅。而且只用了transform和opacity,性能完全没问题。

3.6 避坑指南

最后,分享几个我踩过的坑:

  • display不能用过渡:display: none到block是瞬间切换的,过渡无效。要用opacity + visibility组合。
  • auto值不能过渡:比如width: auto到width: 100px,浏览器不知道中间值是什么。得用具体数值。
  • height: auto的折叠动画:这个比较麻烦,我一般用max-height来模拟,或者直接用transform: scaleY。
  • 过渡和动画冲突:如果同时用了transition和animation,后者会覆盖前者。注意优先级。

我的习惯:写过渡前先问自己三个问题——

  1. 这个属性会不会触发重排?(会的话尽量换方案)
  2. 时长是否在合理范围内?(300ms左右最安全)
  3. 缓动函数是否自然?(别用linear,除非你故意的)

好了,过渡这块就聊到这儿。说白了就是四个属性:property、duration、timing-function、delay。但真正用好它们,需要你在项目里不断积累手感。下次咱们聊聊更复杂的keyframes动画,那个自由度更高,但也更容易翻车。