你了解 Transition 吗?一起来深入了解下Transition!

你了解 transition 吗?你可能并不了解 transition?下面本篇文章就来通过图文结合的方式带大家深入了解一下transition,希望对大家有所帮助!

你了解 Transition 吗?一起来深入了解下Transition!

这篇文章我们深入学习 Transition 动画。没错,CSS3 Transition 动画。你可能会问,不是很简单吗,这什么好讲的?

确实,Transition 动画使用起来非常容易。只需要给元素加上 transition-delay, transition-duration, transition-property, transition-timing-function 属性就可以有过滤效果。更简单的用法是直接使用简写的 transition 属性:

transition:    ;// transition-delay 默认为 0// transition-property 默认为 all// transition-timing-function 默认为 easetransition: 0.3s;

由于 transition 动画用起来几乎没有成本,一直以来也没有太深入学习,最近翻看源代码和 MDN 文档之后发现有些知识没有理解到位,于是乎有了这篇文章,希望对读者更深入了解 Transition 动画有所帮助。(学习视频分享:css视频教程)

为了尽量降低阅读理解成本,这篇文章会写得稍微啰嗦一点点,大部分示例都会配图 ——【多图预警开始!】

什么是 Transition?

简单的说就是过渡动画,通常修改 DOM 节点的样式都是立即更新在页面上的,例如修改宽高,修改透明度,修改背景色等等。

例如当鼠标移动至按钮上时,为了突出按钮的可交互,会在 hover 时修改它的样式,让用户注意到它。没有加 transition 过渡动画,给用户的感觉会很僵很生硬。

.button {  // ...  background-color: #00a8ff;}.button:hover {  background-color: #fbc531;  transform: scale(1.2);}

1.gif

加上 transition 一行代码之后,变化就会比较顺滑。

.button {  // ...  transition: 1s;}// ...

这个例子中我们修改了 background-colortransform,结合 transition 属性,浏览器就会自动让属性值随着时间变化,从旧值逐步过渡到过渡新值,视觉上就是动画效果。

2.gif

区分于 Animation,Transition 动画侧重于表现一次过渡效果,从开始到结束的变化。而 Animation 不需要变化,可以循环播放 ▶️。

需要注意,并不是所有的属性变化都会有过渡效果

有些 CSS 属性只支持枚举值,非黑即白,不存在中间状态,例如 visibility: visible; 被修改成 visibility: hidden; 不会有动画效果,因为不存在可见又不可见的中间状态。在浏览器上的表现是 duration 到了之后元素立即突变为 hidden。

.button:hover {  //...  visibility: hidden;}

3.gif

有些属性虽然是可计算数值,但天生注定不能有过渡效果,例如 transition-delaytransition-duration 都是立即生效,这里值得补一句由于 transition-* 属性是即时生效,这行代码如果是 hover 时才加上,那么效果会是 hover 时有动画,移出时没有动画。

即使是可过渡的属性变化,也可能因为无法计算中间状态而失去过渡效果。例如 box-shadow 属性虽然支持 transition 的动画的,但如果从 “outset” 切换到 inset,也是突变的。

.button {  // ...  box-shadow: 0 0 0 1px rgb(0 0 0 / 15%);  transition: 1s;}.button:hover {  // ...  box-shadow: inset 0 0 0 10px rgb(0 0 0 / 15%);}

4.gif
从表现上看,box-shadow 的变化是 hover 上去立马就生效了。如果某个属性值是连续可计算的数值,但是变化前后变成散列的枚举值,那么过渡也不会生效。例如从 height: 100px => height: auto 是不会有动画的。

以上的内容回顾了 Transition 的基本用法,下面我们来看一个在实际开发场景中会遇到的问题。

为什么 Transition 动画没有生效?

场景题:假设我们现在接到一个自定义下拉选择器的动画需求,设计师给到的效果图如下:

5.gif

这是很常见的出现-消失动画,在很多组件库里面都会出现,点击触发器(按钮)时才在页面上渲染 Popup (下拉内容),并且 Popup 出现的同时需要有渐现和下滑的动画;展开之后再次点击按钮,Popup 需要渐隐和上滑。

平时使用的时候并没有过多注意它的实现,不妨现在让我们动手试验一下。

暂时忽略 popup 的内容,用了个 div 来占位模拟,HTML 结构很简单。

在点击按钮的时候,让 popup 显示/隐藏,然后切换 popup.active 类名。

const btn = document.querySelector("#button");const popup = document.querySelector("#popup");if (!popup.classList.contains("active")) {    popup.style.display = "block";    popup.classList.add("active");} else {    popup.style.display = "none";    popup.classList.remove("active");}

编写 CSS 样式,在不 active 时透明度设置为 0,向上偏移,active 时则不偏移且透明度设置为 1。

#popup {  display: none;  opacity: 0;  transform: translateY(-8px);  transition: 1s;  &.active {    opacity: 1;    transform: translateY(0%);  }}

完整代码 在这里,看起来代码没什么问题,点击按钮切换的时候,popup 应该会有动画过渡效果。然而实际运行效果:

6.gif

硬邦邦地完全没有过渡效果,这是为啥?明明已经设置了 transition,且 opacitytranslateY 都是可计算可过渡的数值,也产生了变化,浏览器为什么不认呢?

在查文档之前,我们先尝试使用万精油 setTimeout

方案一:setTimeout 万精油

修改 JS 代码:

btn.addEventListener("click", () => {  if (!popup.classList.contains("active")) {    popup.style.display = "block";    setTimeout(() => {      popup.classList.add("active");    }, 0);  } else {    popup.classList.remove("active");    setTimeout(() => {      popup.style.display = "none";    }, 600);  }});

可以看到添加了 setTimeout 之后,transition 动画就生效了。

7.gif

隐藏时的 setTimeout 600ms 对应 CSS 中设置的 transition: 0.6s,就是动画完成之后才将 display 设置为 none

主要困惑的点在于为什么显示的时候也需要加 setTimeout 呢?setTimeout 0 在这里起到的作用是什么?带着问题去翻看规范文档。

在规范文档的 Starting of transitions 章节找到下面这段话:

When a style change event occurs, implementations must start transitions based on the computed values that changed in that event. If an element is not in the document during that style change event or was not in the document during the previous style change event, then transitions are not started for that element in that style change event.

翻译一下,当样式变更事件发生时,实现(浏览器)必须根据变更的属性执行过渡动画。但如果样式变更事件发生时或上一次样式变更事件期间,元素不在文档中,则不会为该元素启动过渡动画。

结合浏览器构建 RenderTree 的过程,我们可以很清晰地定位到问题:当样式变更时间发生时,display: none 的 DOM 元素并不会出现在 RenderTree 中(style.display='block' 不是同步生效的,要在下一次渲染的时候才会更新到 Render Tree),不满足 Starting of transitions 的条件。

8.gif

所以 setTimeout 0 的作用是唤起一次 MacroTask,等到 EventLoop 执行回调函数时,浏览器已经完成了一次渲染,再加上 .active 类名,就有了执行过渡动画的充分条件。

优化方案二:精准卡位 requestAnimationFrame

既然目的为了让元素先出现到 RenderTree 中,和渲染相关,很容易想到可以将 setTimeout 替换成 requestAnimationFrame,这样会更精准,因为 requestAnimation 执行时机和渲染有关。

if (!popup.classList.contains("active")) {    popup.style.display = "block";    requestAnimationFrame(() => {        popup.classList.add("active");    });}

补充一个小插曲:在查找资料的过程中了解到 requestAnimationFrame 的规范是要求其回调函数在 Style/Layout 等阶段之前执行,起初 Chrome 和 Firefox 是遵循规范来实现的。而 Safari 和 Edge 是在执行的时机是在之后。从现在的表现上来看,Chrome 和 Firefox 也改成了在之后执行,翻看以前的文档会说需要嵌套两层 requestAnimationFrame,现在已经不需要了。Is requestAnimationFrame called at the right point?

优化方案三:Force Reflow

在规范文档中,还留意到以下这句话:

Implementations typically have a style change event to correspond with their desired screen refresh rate, and when up-to-date computed style or layout information is needed for a script API that depends on it.

意思是说,浏览器通常还会在两种情况下会产生样式变更事件,一是满足屏幕刷新频率(不就是 requestAnimationFrame?),二是当 JS 脚本需要获取最新的样式布局信息时。

在 JS 代码中,有些 API 被调用时,浏览器会同步地计算样式和布局,频繁调用这些 API(offset*/client*/getBoundingClientRect/scroll*/…等等)通常会成为性能瓶颈。

9.gif

然而在这个场景却可以产生奇妙的化学反应:

if (!popup.classList.contains("active")) {  popup.style.display = "block";  popup.scrollWidth;  popup.classList.add("active");}

注意看,我们只是 display 和 add class 之间读取了一下 scrollWidth,甚至没有赋值,过渡动画就活过来了。

10.gif

原因是 scrollWidth 强制同步触发了重排重绘,再下一行代码时,popup 的 display 属性已经更新到 Render Tree 上了。

优化方案四:过渡完了告诉我 onTransitionEnd

现在【出现】动画已经搞明白了,在看开源库的源码中发现像 vue, bootstrap, 完整代码0 等库都是使用了 force reflow 的方法,而 antd 所使用的 完整代码1 库则是通过设置 setTimeout。

【消失】动画还不够优雅,前面我们是直接写死 setTimeout 600,让元素在动画结束时消失的。这样编码可复用性差,修改动画时间还得改两处地方(JS + CSS),有没有更优雅的实现?

popup.classList.remove("active");setTimeout(() => {    popup.style.display = "none";}, 600);

文档中也提到了 完整代码2,包括 transitionruntransitionstarttransitionendtransitioncancel,看名字就知道事件代表什么意思,这里可以用 transitionend 进行代码优化。

if (!popup.classList.contains("active")) {    popup.style.display = "block";    popup.scrollWidth;    popup.classList.add("active");} else {    popup.classList.remove("active");    popup.addEventListener('transitionend', () => {        popup.style.display = "none";    }, { once: true })}

需要注意 transition events 同样也有冒泡、捕获的特性,如果有嵌套 transition 时需要留意 event.target

到这里我们已经用原生 JS 完成了一个出现、消失的动画实现,完整的代码在完整代码3。文章的最后,我们参照 vue-transition 来开发一个 React Transition 的单个元素动画过渡的最小实现。

仿 v-transition 实现一个 React Transition 组件

11.gif

根据动画过程拆分成几个过程:

enter 阶段渲染 DOM 节点,初始化动画初始状态(添加 *-enter 类名)enter-active 阶段执行 transition 过渡动画(添加 *-enter-active 类名)enter-active 过渡完成之后进入正常展示阶段(移除 *-enter-active 类名)

enter-to 和 leave-to 暂时用不上,leave 阶段和 enter 基本一致也不再赘述。

直接看代码:

export const CSSTransition = (props: Props) => {  const { children, name, active } = props;  const nodeRef = useRef(null);  const [renderDOM, setRenderDOM] = useState(active);  useEffect(() => {    requestAnimationFrame(() => {      if (active) {        setRenderDOM(true);        nodeRef.current?.classList.add(`${name}-enter`);        // eslint-disable-next-line @typescript-eslint/no-unused-expressions        nodeRef.current?.scrollWidth;        nodeRef.current?.classList.remove(`${name}-enter`);        nodeRef.current?.classList.add(`${name}-enter-active`);        nodeRef.current?.addEventListener("transitionend", (event) => {          if (event.target === nodeRef.current) {            nodeRef.current?.classList.remove(`${name}-enter-active`);          }        });      } else {        nodeRef.current?.classList.add(`${name}-leave`);        // eslint-disable-next-line @typescript-eslint/no-unused-expressions        nodeRef.current?.scrollWidth;        nodeRef.current?.classList.remove(`${name}-leave`);        nodeRef.current?.classList.add(`${name}-leave-active`);        nodeRef.current?.addEventListener("transitionend", (event) => {          if (event.target === nodeRef.current) {            nodeRef.current?.classList.remove(`${name}-leave-active`);            setRenderDOM(false);          }        });      }    });  }, [active, name]);  if (!renderDOM) {    return null;  }  return cloneElement(Children.only(children), {    ref: nodeRef  });};

这个组件接收三个 props,分别是

children 需要做过渡动画的 ReactElement,只允许传一个 Elementname 过渡动画的 css 类名前缀active 布尔值,用于区分是进场还是消失

使用方式:

    // 一个需要做过渡动画的 ReactElement

借助 transition-delay,加一点技巧实现 stagger 效果:

12.gif

完整的示例代码在完整代码4,注意:这只是个快速实现用于演示的示例,有非常多的问题没有考虑在内,仅可用于学习参考。

结语

原本以为非常基础简单的知识点,分分钟可以写完这篇文章。没想到中途查文档,看资料,制作演示 DEMO 还是花了不少时间。好在整理资料的过程中也理清了很多知识点。希望这篇文章对你熟悉 Transition 动画有所帮助 。

相关推荐:完整代码5

以上就是你了解 Transition 吗?一起来深入了解下Transition!的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1623678.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月24日 08:22:20
下一篇 2025年12月24日 08:22:41

相关推荐

  • 快速提升开发技能的 20 个 CSS 小技巧

    本篇文章给大家分享20 个 css 小技巧,用于快速提升开发技能,快来收藏吧,希望对大家有所帮助! flexbox 内容换行 当我们使用 flexbox 布局的时候,默认情况下,在容器宽度不够时,可能就会出现这样的情况。 这个主要是因为 flex-wrap 的默认值是 nowrap,所以我们需要这样…

    2025年12月24日 好文分享
    000
  • HTML+CSS+JS实现雪花飘扬(代码分享)

    使用html+css+js如何实现下雪特效?下面本篇文章给大家分享一个html+css+js实现雪花飘扬的示例,希望对大家有所帮助。 很多南方的小伙伴可能没怎么见过或者从来没见过下雪,今天我给大家带来一个小Demo,模拟了下雪场景,首先让我们看一下运行效果 可以点击看看在线运行:http://hai…

    2025年12月24日 好文分享
    000
  • 手把手带你使用纯CSS实现饼状图

    如何仅使用一个 div 配合 css 实现饼状图?下面本篇文章就来给大家看看实现方法,希望对大家有所帮助。 本文为译文「意译」完整的代码请滑到文末。 我们只使用一个div,仅采用css实现饼状图。 HTMl 结构 60% 我们添加了几个 css 的变量: –p:进度条的百分比(纯数字,不带%),饼…

    2025年12月24日 好文分享
    000
  • 总结整理:需要避坑的五大常见css错误(收藏)

    本篇文章给大家总结5个最常见的css错误,并介绍一下避坑方法,希望对大家有所帮助! 正如我们今天所知,CSS语言是web的一个重要组成部分。它使我们有能力绘制元素在屏幕、网页或其他媒体中的展示方式。 它简单、强大,而且是声明式的。我们可以很容易地实现复杂的事情,如暗黑/光明模式。然而,对它有很多误解…

    2025年12月24日
    000
  • css怎么降低背景透明度

    css降低背景透明度的方法:1、使用opacity属性,只需要给背景元素设置“opacity: 透明度值;”样式即可;2、使用filter属性,只需要给背景元素设置“filter: opacity(透明度值);”样式即可。 本教程操作环境:windows7系统、CSS3&&HTML5…

    2025年12月24日
    000
  • 怎么用css样式把图片改为灰色

    在css中,可以利用filter属性来把图片改为灰色,该属性可以给图片添加滤镜效果,只需要给图片元素添加“filter: grayscale(灰度数值%);”样式即可将图片设置为灰色。 本教程操作环境:windows7系统、CSS3&&HTML5版、Dell G3电脑。 在css中,…

    2025年12月24日
    000
  • css怎么实现图片放大缩小动画

    方法:1、使用“@keyframes 动画名称{}”规则和“transform:scale(缩放比例);”语句创建放大缩小动画;2、使用“图片元素{animation:动画名称 时间 infinite;}”语句缩放动画应用于图片元素中。 本教程操作环境:windows7系统、CSS3&&am…

    2025年12月24日
    000
  • css怎么实现鼠标移上去旋转效果

    方法:1、用“@keyframes 动画名{100% {transform:rotate(角度)}”创建旋转动画;2、用“元素:hover{animation:动画名 时间 linear infinite}”设置在鼠标移上元素时触发动画。 本教程操作环境:windows7系统、CSS3&&a…

    2025年12月24日
    000
  • css怎么实现梯形

    css实现梯形的方法:1、创建三个div元素;2、利用border属性分别将第一和第三个div元素设置为直角三角形;3、将第二个div元素设置为正方形;4、使用transform属性将两个直角三角形和一个正方形拼接成一个梯形。 本教程操作环境:windows7系统、CSS3&&HTM…

    2025年12月24日 好文分享
    000
  • css怎么调整中文字间距

    在css中,可以利用letter-spacing属性来调整中文字间距,该属性的作用就是增加或减少字符间的空白,即设置字符间距;只需要给中文的文本元素添加“letter-spacing:间距值”样式即可。 本教程操作环境:windows7系统、CSS3&&HTML5版、Dell G3电…

    2025年12月24日
    000
  • css图片的边框怎么设置颜色为渐变色

    在css中,可以利用border-image属性和linear-gradient()函数来将图片边框的颜色设置为渐变色,语法“border:边框大小 solid;border-image:linear-gradient(…) 1;}”。 本教程操作环境:windows7系统、CSS3&a…

    2025年12月24日
    000
  • 手把手教你CSS架构之SMACSS

    本篇文章给大家带来了关于css架构smacss的相关知识,其中会讲到什么是smacss以及该架构分类的相关问题,希望对大家有帮助。 因为 CSS 只有一个作用域,如果不注意维护 CSS 代码,则会导致我们写的代码难于阅读和维护,于是我们借助网页本身是有层次的,抽象出来了BEM 方法论。 BEM 简单…

    2025年12月24日 好文分享
    000
  • css3怎样设置旋转点位置

    在css中,可以利用“transform-Origin”属性设置元素旋转点的位置,该属性允许更改转换元素的位置,可以分别设置元素转换后与X、Y和Z轴之间的位置,语法为“transform-origin:X轴方向 Y轴方向 Z轴方向;”。 本教程操作环境:windows10系统、CSS3&&a…

    2025年12月24日
    000
  • css3的圆角边框属性是什么

    css3圆角边框属性是“border-radius”,该属性是一个复合属性,语法为“border-radius:1-4 length|%;”;属性的四个值分别控制着元素左上角、右上角、右下角和左下角的圆角边框样式。 本教程操作环境:windows10系统、CSS3&&HTML5版、D…

    2025年12月24日
    000
  • css3中用什么连接class

    在css3中,可以利用“.class”选择器连接class,该选择器用于选取带有指定类(class)的元素,选中指定元素后就可以设置选中元素的样式,语法为“.class{css样式代码;}”。 本教程操作环境:windows10系统、CSS3&&HTML5版、Dell G3电脑。 c…

    2025年12月24日
    000
  • css3使用什么属性表示左浮动

    在css中,可以使用“float:left”来表示左浮动,float属性用于定义元素向哪个方向浮动,当属性值为right时表示元素右浮动,当属性值为none时表示元素不浮动。 本教程操作环境:windows10系统、CSS3&&HTML5版、Dell G3电脑。 css3使用什么属性…

    2025年12月24日
    000
  • 值得收藏的CSS盒子模型属性详解

    本篇文章给大家带来了css中关于盒子模型的诸多属性详细解析,其中包括边框、边距、圆角等等,希望对大家有帮助。 CSS盒子模型 一、什么是盒子模型 所有HTML元素可以看作盒子,在CSS中,”box model”这一术语是用来设计和布局时使用。 CSS盒模型本质上是一个盒子,封…

    2025年12月24日 好文分享
    000
  • 你必须了解后端也得会的两万字CSS技术

    本篇文章给大家带来了css知识总结,其中系统的从零开始讲解了css的使用方法,希望对大家有帮助。 第一部分:CSS的基本使用 (1)CSS是什么? CSS全称Cascading Style Sheets,翻译:层叠(级联)样式表。 如果说HTML是网页的结构,那么CSS就是网页化妆师。 (2)CSS…

    2025年12月24日 好文分享
    100
  • 绚丽的极光用CSS也能实现!

    在上次写完这篇文章 — 巧用渐变实现高级感拉满的背景光动画 之后,文章下面的评论有同学留言,使用 css 可以实现极光吗? 像是这样: .g-aurora { … transform: rotate(45deg) scaleX(1.4); mix-blend-mode: color-…

    2025年12月24日
    000
  • 一文通过动画来快速学习 css !

    本篇文章带大家深入解析一下如何实现动画效果,通过动画来快速学习 css ,希望对大家有所帮助! #hellocss { background-color: blue; color: yellow; width: 20px; transition-property: width; transition…

    2025年12月24日 好文分享
    000

发表回复

登录后才能评论
关注微信