likes
comments
collection
share

CSS Transitions

作者站长头像
站长
· 阅读数 14

人该是自己生活的主宰,不是别人手里的行货。 --王小波

大家好,我是柒八九

前言

今天,我们来讲的轻松的话题。作为一个前端,能画出一手炫酷的动画,是一件很振奋人心的事情。并且网页动画已经成为一个庞大而复杂的工具和技术。类似GSAPFramer MotionReact Spring等库已经涌现,以帮助我们在DOM中添加动画效果。

然而,万丈高楼平地起,最基本和关键的工具是不起眼的CSS过渡(CSS transition)。这是大多数前端开发人员学习的第一个动画工具,它是一个不可或缺的工具。

所以,我们今天就来聊聊这个。

CSS Transitions


你能所学到的知识点

  1. 前置知识点
  2. 牛刀小试
  3. 时间函数
  4. 动画优化
  5. 基于动作驱动的动画
  6. 过渡延迟

前置知识点

前置知识点,只是做一个概念的介绍,不会做深度解释。因为,这些概念在下面文章中会有出现,为了让行文更加的顺畅,所以将本该在文内的概念解释放到前面来。如果大家对这些概念熟悉,可以直接忽略同时,由于阅读我文章的群体有很多,所以有些知识点可能我视之若珍宝,尔视只如草芥,弃之如敝履。以下知识点,请酌情使用

CSS过渡基础知识

在涉及CSS过渡时,有一些基本概念和属性,我们需要了解。这些构成了在Web上创建流畅和精致动画的基础要素。

  1. CSS过渡允许我们在指定的持续时间内平滑地更改属性值

    • 关键的过渡属性包括transition-propertytransition-durationtransition-timing-functiontransition-delay
    • 这些属性确定了要进行动画处理的内容,动画的持续时间,动画的时间函数以及动画开始之前的任何延迟。
  2. transition-property: 此属性定义了要将过渡效果应用于哪些CSS属性

    • 例如,我们可以过渡元素的width属性。
  3. transition-duration: 此属性指定过渡完成所需的时间

    • 我们可以以秒(s)或毫秒(ms)为单位设置它。
  4. transition-timing-function: 此属性控制动画的速度。

    • 它定义了在过渡期间的加速和减速情况。
    • 常见的时间函数包括easelinearease-inease-outease-in-out
  5. transition-delay: 我们可以使用此属性在过渡开始之前引入延迟。

    • 它也可以以秒(s)或毫秒(ms)为单位指定。

    以下是如何在CSS中使用这些属性的示例:

    /* 对width属性应用过渡效果,持续0.5秒,使用ease-in-out时间函数,延迟0.2秒 */
    .element {
      transition-property: width;
      transition-duration: 0.5s;
      transition-timing-function: ease-in-out;
      transition-delay: 0.2s;
    }
    
  6. 触发过渡: 过渡通常在元素的状态发生变化时触发

    • 例如,当我们悬停在按钮上时,可以更改其背景颜色,过渡效果将使颜色平滑地在指定的持续时间内变化。
  7. 多重过渡: 我们可以通过使用逗号分隔的属性值将多个过渡应用于单个元素,从而可以同时对多个属性进行动画处理。


贝塞尔曲线

贝塞尔曲线是一种用于描述二维三维曲线的数学工具,它广泛应用于计算机图形、计算机辅助设计(CAD)、动画制作以及许多其他领域。贝塞尔曲线以其平滑的形状和良好的控制性而闻名,它由一组控制点(也称为"控制顶点"或"控制节点")定义,这些点确定了曲线的形状和特性。

贝塞尔曲线有几种类型,其中最常见的是二次贝塞尔曲线三次贝塞尔曲线

  1. 二次贝塞尔曲线(Quadratic Bezier Curve):

    • 二次贝塞尔曲线由三个点定义:起始点(P0)、控制点(P1)、和结束点(P2)。
    • 曲线从起始点出发,经过控制点,最终到达结束点。
    • 控制点的位置影响了曲线的形状,控制点决定了曲线在该点的切线方向。 CSS Transitions
  2. 三次贝塞尔曲线(Cubic Bezier Curve):

    • 三次贝塞尔曲线由四个点定义:起始点(P0)、第一个控制点(P1)、第二个控制点(P2)、和结束点(P3)。
    • 曲线从起始点出发,经过两个控制点,最终到达结束点。
    • 控制点的位置和数量决定了曲线的形状和弯曲程度。 CSS Transitions

贝塞尔曲线的关键特点包括:

  • 平滑性:贝塞尔曲线始终保持平滑,没有锯齿或尖锐的角。
  • 控制性:通过调整控制点的位置,可以精确控制曲线的形状。
  • 递归性:贝塞尔曲线可以嵌套,也就是说,一个贝塞尔曲线的控制点可以是另一个贝塞尔曲线。

子像素渲染

子像素渲染(Sub-pixel rendering)是一种图形渲染技术,通常用于改善文本和图像在计算机屏幕上的呈现质量。这种技术的主要目标是在像素级别上增加渲染的精度,以获得更清晰和平滑的图像。子像素渲染特别常见于现代操作系统和Web浏览器中的文本呈现。

  1. 子像素定位

    • 通常,屏幕上的每个像素都由红、绿和蓝三个子像素组成,它们的颜色可以独立控制。子像素渲染充分利用了这一特性,通过微调文本和图像的位置来实现更精确的呈现。
  2. 清晰的文本

    • 子像素渲染可以使文本字符的边缘更加平滑和清晰。通过微调字符的位置,字母之间的间隙以及笔画的精确位置,文本可以呈现出更高的清晰度和可读性。
  3. 抗锯齿效果

    • 子像素渲染还有助于减少锯齿(锯齿状边缘)的出现。通过将字符和图像的边缘放在子像素级别,渲染引擎可以创建更平滑的边缘,从而减少锯齿。
  4. 颜色分离

    • 子像素渲染允许文本和图像中的颜色分离到每个子像素。这样,一个像素可以显示多种颜色,提供更丰富的颜色表示能力。
  5. 适用范围

    • 子像素渲染对于高分辨率屏幕尤为重要,因为在低分辨率下可能难以分辨子像素级别的微调。
    • 它在操作系统用户界面、Web浏览器中的文本呈现、图形设计工具等领域都有广泛应用。
  6. CSS和子像素渲染

    • 在CSS中,子像素渲染可以通过一些属性和值来实现,例如text-rendering: optimizeLegibility;,这可以让浏览器尽量优化文本的呈现。
    • 使用-webkit-font-smoothing-moz-osx-font-smoothing属性,可以进一步控制字体在Web浏览器中的平滑渲染。

需要注意的是,子像素渲染可能会对性能产生一定影响,因为它要求更多的计算来确定子像素的精确位置和颜色。因此,在使用子像素渲染时,需要权衡图像质量和性能。子像素渲染在高分辨率显示设备上更为明显,而在低分辨率设备上可能不太明显或无法有效运用。它通常用于确保文本和图像在屏幕上的最佳呈现。


CPU VS GPU

  • CPU 即中央处理单元,是一种硬件组件,它是服务器的核心计算单元。它负责处理操作系统和应用程序运行所需的各类计算任务。
  • 图形处理单元(GPU),是一种与 CPU 类似,但更专业的硬件组件。与普通 CPU 相比,它可以更高效地处理并行运行的复杂数学运算。最初的 GPU 专用于处理游戏和动画中的图形渲染任务,不过现在它们的用途已远超于此。

CSS Transitions

下表展示了CPU和GPU之间的区别:

特点CPUGPU
类型通用组件 - 处理计算机的主要处理功能专用组件 - 处理图形和视频渲染
核心数量2-64个(大多数CPU)数千个
运行过程串行运行进程并行运行进程
擅长处理一次处理一个大型任务一次处理多个较小的任务

CPU是通用处理器,适合处理各种不同类型的任务,而GPU专门设计用于图形和并行计算,适用于需要高吞吐量的任务。这两者通常一起工作,以在计算机系统中提供最佳性能。

视频地址 (需要🪜)

我们在之前计算机底层知识之CPU的文章中,介绍过CPU的组成,有兴趣的同学可以移步观看。


VRAM(视频内存)

视频内存是位于显卡上,有时也位于主板上,可供视频和计算机处理器访问的内存。拥有更多的视频内存,显卡和计算机能够以更快的速度处理更复杂的图形。 CSS Transitions

上面的图片显示,视频内存通常是显卡的一部分,而不是可拆卸的内存模块。在较旧的显卡上,视频内存可能仅为8MB,而在较新的显卡上可能高达数GB。

与计算机中的[RAM](随机存取存储器)类似,视频内存临时存储与图形相关的数据。随着新的图形数据进入显卡,它会替换掉视频内存中不再需要的图形数据。当计算机关闭时,视频内存中的任何图形数据都会被清除。


backface-visibility

backface-visibility 是一个CSS属性,用于控制元素的背面是否可见。这个属性通常用于应用于进行3D变换的元素,比如使用CSS的transform属性进行元素旋转或翻转时,可以通过backface-visibility来指定元素的背面是否可见。

这个属性有两个可能的值:

  1. visible(默认值):表示元素的背面是可见的。这意味着元素在旋转或翻转时,不仅正面可见,而且背面也会显示在屏幕上。

  2. hidden:表示元素的背面是不可见的。这意味着元素在旋转或翻转时,只有正面可见,背面将被隐藏起来,不会呈现在屏幕上。

backface-visibility通常与3D变换一起使用,以控制元素在旋转或翻转时的外观。例如,可以在3D场景中创建卡片翻转的效果,然后使用backface-visibility将背面隐藏,以确保只有正面可见。

示例:

.card {
  // 省去部分代码
  transform-style: preserve-3d; /* 启用3D变换 */
  backface-visibility: hidden; /*隐藏背面*/
  transition: transform 1s;
}

.card:hover{
 transform: rotateY(180deg); /* 以Y轴翻转180度 */
}

上面的示例将一个卡片元素进行了Y轴翻转,并通过backface-visibility: hidden;来确保只有正面可见,背面被隐藏。这样就创建了一个卡片翻转的效果。


2. 牛刀小试

创建动画的主要要素是改变的CSS属性

现在我们对网页中的button做一个实验。

对应的html如下,就是一个简单到令人发指的button

<button class="btn">
  Hello CSS
</button>

然后,对其设置一个.btn的类名。

.btn {
    width: 100px;
    height: 100px;
    border-radius: 50%;
    border: none;
    background: slateblue;
    color: white;
    font-size: 20px;
    font-weight: 500;
    line-height: 1;
  }
  
.btn:hover {
  transform: translateY(-10px);
}

这个代码片段使用了:hover伪类,当用户的鼠标悬停在按钮上时,指定了额外的CSS声明,类似于JavaScript中的onMouseEnter事件。

为了将元素向上移动,我们使用了transform: translateY(-10px)。虽然我们也可以使用margin-top来实现这个效果,但transform: translate更适合这个任务。。

默认情况下,CSS中的更改是瞬间发生的。

所以,我们可以使用名为transition的属性来告诉浏览器从一个状态过渡到另一个状态

.btn {
    /*
      和上面例子代码一致
    */
    transition: transform 250ms;
  }
  
  .btn:hover {
    transform: translateY(-10px);
  }

transition属性可以接受多个值,但只有两个是必需的

  1. 要处理动画的属性名称
  2. 动画的持续时间

如果我们计划对多个属性进行动画处理,可以传递一个用逗号分隔的属性列表

.btn {
  transition: transform 250ms, opacity 400ms;
}
.btn:hover {
  transform: scale(1.2);
  opacity: 0;
}

transition-property可以采用特殊的值:all。当指定为all时,任何发生变化的CSS属性都会进行过渡动画。 尽管使用all可能很诱人,因为它可以节省大量输入,特别是当我们要对多个属性进行动画处理时,但还是建议不要使用它。 如果你打开这个,就打开了潘多拉魔盒。


3. 时间函数

当我们要求一个元素从一个位置过渡到另一个位置时,浏览器需要计算出每个“中间”帧应该是什么样子的。

例如:假设我们正在将一个元素从左移动到右,持续1秒。流畅的动画应该以60帧每秒的速度运行,这意味着我们需要在起始和结束之间计算出60个单独的位置

我们先看一个让每个位置都均匀分布的情况:

CSS Transitions

每个圆圈代表一个时间点。随着圆圈从左到右移动,这些是向用户显示的帧。

在这个动画中,我们使用的是线性(linear)时间函数。这意味着元素以恒定的速度移动;我们的圆圈每一帧都移动相同的距离。

CSS Transitions

CSS中有几种可用的内置时间函数。我们可以使用transition-timing-function属性指定要使用的时间函数:

.btn {
  transition: transform 250ms;
  transition-timing-function: linear;
}

或者,我们可以直接将其传递给transition简写属性:

.btn {
  transition: transform 250ms linear;
}

除了linear我们还有其他的选择:

ease-out

ease-out就像野牛一样冲来,但最后它精力不济。到最后,它就像一只慢慢爬行的乌龟。

CSS Transitions

我们用坐标轴来描述元素随时间的位移图,它会看起来像这样: CSS Transitions

那什么时候会使用ease-out?它最常用于某些东西从屏幕外部进入视图(例如,弹窗出现)的情况。它产生了一种事物从远处急速赶来并停在用户面前的效果。

ease-in

ease-inease-out的反义词。它开始缓慢然后加速

CSS Transitions

正如我们所看到的,ease-out适用于从屏幕外部进入视图的情况。自然而然,ease-in适用于相反情况:将某物移出视口边界。

CSS Transitions

这个组合在某物进入和退出视口时非常有用,比如一个弹窗的显示和隐藏。

ease-in几乎只用于元素以屏幕外或不可见结束的动画;否则,突然的停止可能会令人不适。

ease-in-out

接下来是ease-in-out。它是前两个时间函数的组合:

CSS Transitions

这个时间函数是对称的。它具有相等数量的加速和减速。

CSS Transitions

这个曲线对于在循环中发生的任何事情都很有用(例如,元素一遍又一遍地淡入和淡出)。

ease

ease-in-out不同,它不是对称的;它具有短暂的加速段和大量的减速

CSS Transitions

ease默认值 —— 如果我们没有指定时间函数,将使用ease。老实说,这对大多数情况都感觉正确。如果一个元素移动,而不是进入或退出视口,通常ease是一个不错的选择。

CSS Transitions

时间是恒定的 关于上面所有的例子需要有一个说明:动画经历的时间是恒定的。时间函数描述了一个值如何在固定时间间隔内从0到1,而不是动画应该多快完成。一些时间函数可能会感觉更快或更慢,但在这些示例中,它们都需要完全1秒来完成。

自定义曲线

如果提供的内置选项不符合我们的需求,我们可以使用三次贝塞尔(cubic bézier)时间函数来定义自己的自定义缓动曲线!

.btn {
  transition:
    transform 250ms cubic-bezier(0.1, 0.2, 0.3, 0.4);
}

到目前为止,我们所见到的所有值实际上都只是这个cubic-bezier函数的预设值。它需要4个数字,表示2个控制点。

与此同时,我们可以使用Lea Verou来开始创建自己的贝塞尔时间函数:

CSS Transitions

一旦我们找到一个满意的动画曲线,点击顶部的Copy并将其粘贴到我们的CSS中!

我们还可以从这个扩展的时间函数集合中进行选择。不过要注意:其中一些更奇特的选项在CSS中可能无法正常工作。

CSS Transitions

当我们刚开始尝试使用自定义贝塞尔曲线时,可能很难找到一个感觉自然的曲线。但通过一些实践,这将成为一个非常有表现力的工具。


用三次贝塞尔来表示内置函数

.btn {
  /* ease-out */
  transition-timing-function:
    cubic-bezier(0.215, 0.61, 0.355, 1);
  /* ease-in */
  transition-timing-function:
    cubic-bezier(0.75, 0, 1, 1);
  /* ease-in-out */
  transition-timing-function:
    cubic-bezier(0.645, 0.045, 0.355, 1);
  /* ease */
  transition-timing-function:
    cubic-bezier(0.44, 0.21, 0, 1);
}

这些自定义的时间函数替代方案可以让我们在动画中使用更具表现力的缓动效果。


4. 动画优化

早些时候,我们提到动画应该以60fps的速度运行。然而,当我们进行计算时,我们意识到这意味着浏览器只有16.6毫秒来绘制每一帧。

如果我们的动画计算开销过大,它将会看起来不流畅,而且会出现卡顿。帧会被丢弃,因为设备无法跟得上。 CSS Transitions

我们之前写过像素是怎样练成的有关丢帧的介绍。

动画性能是一个庞杂的领域,不在本文的讨论范围内。但让我们挑选几个比较重要的点来简单说说:

  1. 一些CSS属性比其他属性更耗时。

    • 例如,height是一个非常耗时的属性,因为它影响布局。当一个元素的高度缩小时,会引发一连串的反应;所有兄弟元素都需要向上移动以填充空间!
  2. 其他属性,如background-color,在进行动画时成本较高。

    • 它们不影响布局,但它们需要在每一帧上进行重绘。
  3. 两个属性 — transformopacity — 在进行动画时耗时狠少。

    • 如果一个动画当前调整了类似widthleft这样的属性,通过将其移动到transform,可以显著改善动画性能。

硬件加速

让我们来看一个小例子:(根据浏览器和操作系统的不同,效果可能不同) CSS Transitions

鼠标悬停在我们的Hello World按钮上,仔细观察字母,它们在过渡的开头和结尾似乎位置发生了偏移。

这是因为计算机的CPUGPU之间的数据切换导致的。

当我们使用transformopacity来对元素进行动画时,浏览器有时会尝试优化这个动画。它不会在每一帧上将像素光栅化,而是将一切都作为纹理传输到GPUGPU非常擅长执行这种基于纹理的变换,因此我们得到了非常流畅、性能非常好的动画效果。这被称为硬件加速

问题在于:GPUCPU以不同的方式呈现事物。当CPU将其传递给GPU,反之亦然,就会出现因为数据变更而导致元素位置和样式变化的情况。

我们可以通过添加以下CSS属性来解决这个问题:

.btn {
  will-change: transform;
}

will-change是一个属性,允许我们提示浏览器我们将要对所选元素进行动画处理,并且它应该为此情况进行优化

这意味着浏览器将始终让GPU处理这个元素。不再有CPUGPU之间的切换,也就不再有明显的位置微偏的现象。

will-change让我们可以有意识地选择哪些元素应该使用硬件加速。

硬件加速还有另一个好处:我们可以利用子像素渲染

现在我们有两个元素。一个采用了硬件加速,而另一个没有。它们又一个共同特点就是-当鼠标悬浮在它们上面时,它们会向下移动

<button class="accelerated box">硬件加速</button>
<button class="janky box">非硬件加速</button>
.accelerated.box {
  transition: transform 750ms;
  will-change: transform;
  background: slateblue;
}

.accelerated.box:hover {
  transform: translateY(10px);
}
  
.janky.box {
  transition: margin-top 750ms;
  will-change: margin-top;
  background: deeppink;
}
.janky.box:hover {
  margin-top: 10px;
}

上面的代码中,效果大相径庭,但是硬件加速框移动得比非硬件加速框更加流畅。

margin-top这样的属性不能进行子像素渲染,这意味着它们需要四舍五入到最接近的像素,从而创建出一个阶梯状、不流畅的效果。而transform可以通过GPU的反锯齿技巧在像素之间平滑移动。

CSS Transitions

生活中没有免费的午餐,硬件加速也不例外

通过将一个元素的渲染委托给GPU,它将消耗更多的视频内存(VRAM),这是一种有限的资源,特别是在低端移动设备上。这也是我们为什么,建议不要把xx 设置为all的原因。

硬件加速已经存在很长时间了——实际上比will-change属性还要早!

在很长一段时间内,通过使用3D变换来实现,例如transform: translateZ(0px)。即使值为0px,浏览器仍会将其交给GPU处理,因为在3D空间中移动显然是GPU的强项。还有backface-visibility: hidden

will-change出现时,它旨在为开发人员提供一种适当的、语义化的方式来提示浏览器优化元素。


基于动作驱动的动画

开头我们给出了一个Hello CSS的代码案例。它有一个“对称”的过渡——进入动画退出动画相同:

  1. 当鼠标悬停在元素上时,它在250毫秒内向上移动10像素。
  2. 当鼠标移开时,元素在250毫秒内向下移动10像素。

现在,小可爱产品提出了一个优化点,就是在进入退出想要不同的效果。

  • 进入时快
  • 退出时慢
.btn {
  will-change: transform;
  transition: transform 450ms;
}
  
.btn:hover {
  transition: transform 125ms;
  transform: translateY(-10px);
}

我们对核心代码做一下简单解释:

  1. transition: transform 450ms;

    • transition属性用于定义元素状态变化时的平滑过渡效果。
    • 这行代码指定了按钮元素在transform属性上应用过渡效果,持续时间为450毫秒。这意味着当按钮的transform属性发生变化时,变化将以平滑的方式在450毫秒内发生。
  2. transition: transform 125ms;

    • 这行代码重新定义了按钮元素在鼠标悬停时的transform属性的过渡效果。
    • 它指定了一个更短的过渡时间,为125毫秒。这意味着当鼠标悬停在按钮上时,按钮的transform属性将以更快的速度改变。
  3. transform: translateY(-10px);

    • 这行代码定义了鼠标悬停时按钮的transform属性的新值。它将按钮向上平移了10像素(-10px),创建了一个垂直方向的位移效果。
    • 当用户悬停在按钮上时,按钮将向上移动10像素,创建了一个视觉反馈,以指示按钮可以被点击。

另一个常见的例子是弹窗(modals)。对于弹窗,使用ease-out动画进入,以及更快的ease-in动画退出,通常会很有用。


6. 过渡延迟

最后,让我们谈谈过渡延迟

我相信在项目开发中,或多或少遇到过如下的情况: CSS Transitions 作为开发者,我们可能可以理解为什么会发生这种情况:下拉菜单只在鼠标悬停在上面时保持打开!当我们以对角线移动鼠标来选择子菜单时,我们的光标会超出菜单边界,然后菜单关闭。

这个问题可以以一种相当优雅的方式解决,而无需使用JavaScript。我们可以使用transition-delay

.dropdown {
  opacity: 0;
  transition: opacity 400ms;
+  transition-delay: 300ms;
}
.dropdown-wrapper:hover .dropdown {
  opacity: 1;
  transition: opacity 100ms;
+  transition-delay: 0ms;
}

transition-delay允许我们在一段时间内保持事物的现状不变。在这种情况下,当用户将鼠标移出.dropdown-wrapper时,在300毫秒内不会发生任何事情。如果他们在这300毫秒窗口内重新进入元素,过渡就不会发生。

在经过300毫秒后,过渡会正常启动,下拉菜单会在400毫秒内出现。

到目前为止,我们一直使用transition简写将所有与过渡相关的值捆绑在一起。transition-delay也可以与简写一起使用:

.dropdown {
  opacity: 0;
  transition: opacity 250ms 300ms;
}

我更喜欢使用transition-delay,因为简写感觉模糊不清;我们传递了两个时间数字,但没有指明哪一个是哪一个!

规范明确规定,当传递多个数字时,第一个是持续时间,第二个是延迟。


元素快闪

当在悬停时将一个元素向上或向下移动时,我们需要非常小心,以确保不会出现快闪现象。 CSS Transitions

在我们上面的例子中,其实也会出现这种情况。

问题出现在鼠标靠近元素边界时。悬停效果将元素从鼠标下方移开,这会导致它再次落回鼠标下方,从而再次触发悬停效果...每秒多次。

我们如何解决这个问题呢?关键是将触发与效果分开

<button class="btn">
  <span class="background">
    Hello CSS
  </span>
</button>
.background {
    will-change: transform;
    transition: transform 450ms;
}
  
.btn:hover .background {
  transition: transform 150ms;
  transform: translateY(-10px);
}

/* 开启查看实现原理 */
.btn {
  /* outline: auto; */
}

我们的 <button> 现在有一个新的子元素,.background。这个 span 元素包含了所有的样式(背景颜色、字体等等)。

当我们悬停在这个普通的按钮上时,它会导致子元素从上方露出。然而,按钮本身是静止的。


后记

分享是一种态度

参考资料

全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。

转载自:https://juejin.cn/post/7283047347474104355
评论
请登录