如何为CSS盒阴影添加动画效果并优化性能

899 阅读12分钟

在本文中,您将学习如何在不降低浏览器性能的情况下为CSS盒阴影添加动画效果。

在CSS中,box-shadow属性用于向Web元素添加阴影,这些阴影可以进行动画处理。然而,阴影动画可能会影响浏览器的性能,在渲染页面时导致卡顿。

本指南适用于具备HTML和CSS动画的前端开发人员。

为什么这很重要

网页的加载时间必须非常短,理想情况下应在五秒以内。研究表明,快速加载的网页能大幅提升转化率。进一步的研究表明,70%的用户表示网站的速度会影响他们购买的意愿。简而言之,快速的网站等于用户的满意。

在我们进一步探讨之前,这里展示了一个关于如何在网页中应用盒阴影动画的演示。您可以滚动并与网页元素进行交互。

三个主要的CSS盒阴影动画事件

因为在幕后发生的事情,CSS盒阴影动画可能会消耗较多的资源。在盒阴影动画(或任何形式的动画)期间,会触发三个主要的进程或事件,它们分别是绘制(Painting)、布局(Layout)和合成(Compositing)。

绘制:在绘制过程中,浏览器会用颜色填充像素,而盒阴影是触发该事件的CSS属性之一。基本上,它会在动画的每一帧中创建一个新的阴影。根据Mozilla的说法,理想的CSS动画应以60fps运行。

布局:一些动画会改变页面的结构,这可能会导致许多样式重新计算。一个很好的例子是侧边栏在展开时推开其他元素。导致此问题的CSS属性包括padding、margin和border。

简单来说,如果动画的属性影响其他元素,它将改变页面的布局,导致重新计算,从而使用了大量的系统资源。

合成:在合成过程中,页面的部分内容发生变化。透明度(opacity)和变换(transform)等CSS属性仅影响应用到的元素。这意味着较少的样式重新计算和更流畅的动画。在这三个过程中,合成是最轻松的过程。

通过浏览器的检查工具,您可以实时观察到这个过程。首先,打开检查工具(以Chrome为例),点击标签页右上角的三个点,然后勾选"更多工具",选择"渲染"。

在渲染部分选择"Paint flashing"选项。

在这个例子中,选择了"Paint flashing"。每次有绘制事件发生时,屏幕都会闪烁绿色:

The navbar:

16654658202.webp

The text cards:

16654658453.webp

The nav links:

16654658733a.webp

当您将鼠标悬停在元素上或刷新页面时,会发现每个带阴影的元素都会闪烁绿色。您也可以通过布局进行相同的实验:只需取消勾选"Paint flashing"并选择"Layout Shift Regions"。

请注意,绘制闪烁在CodePen演示中可能无法正常工作,因此您可以尝试在文本编辑器的实时预览中进行此操作。下面的视频展示了您应该看到的效果。

目标是尽量减少绘制和布局的改变,因为它们会使用更多的系统资源。

性能检查

作为开发人员,您可能在运行阴影动画时没有任何问题,因为您的计算机速度很快。但是您必须考虑那些使用较慢的个人电脑和不稳定的互联网连接的用户。仅仅因为在您的计算机上看起来很好,并不意味着在其他地方也是如此。

盒阴影具有四个值和一个颜色。这四个值分别是阴影的水平位置(x偏移量)、垂直位置(y偏移量)、扩散和模糊半径。典型的阴影动画将涉及对这些值中的一个或多个进行更改:

box-shadow: <x-offset> <y-offset> <spread> <blur> <color>;

让我们从一些HTML代码开始创建一个简单的盒阴影动画:

<body> <div class="box"></div> </body>

这里是一些CSS代码用于设置初始和最终的阴影效果:

.box { box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.5); transition: transform ease 0.5s, box-shadow ease 0.5s; } .box:hover { transform: translateY(-5px); box-shadow: 0px 10px 20px 2px rgba(0, 0, 0, 0.25); }

在这个动画中,我们将更改y偏移量、模糊和扩散的值。我们还将使用更透明的最终阴影。

现在让我们看看在运行这个0.5秒动画时背后发生了什么。在您的浏览器中,通过右键单击并选择"检查"来打开开发者工具。一旦工具打开,切换到"性能"选项卡。您可以记录阴影动画;只需几秒钟就足以看到发生了什么。

16654660084.webp

下面的截图显示了您在Chrome的开发工具中找到的内容。

顶部突出显示了阴影动画的持续时间,包括向上和向下悬停的过程,底部显示了过程的详细信息。详细信息显示脚本运行时间为7ms,渲染时间为55ms,绘制时间为30ms。

现在,这些数字看起来还可以,但当CPU速度慢四倍时会发生什么?您可以在性能选项卡中限制CPU速度。

下面的图片展示了当您在较慢的CPU上运行相同的动画时会发生什么。

16654660345.webp

在这个新的过程中,加载时间为6ms。脚本运行时间增加到了52ms,渲染时间增加到了117ms,绘制时间为72ms。

您还可以限制网络速度,使CPU速度更慢。阴影动画使用了大量的系统资源,我们将尝试减轻部分负载。

需要注意的是,变换(transform)属性对CPU的性能有一定影响。稍后我们将详细介绍。

如何保持最佳性能

如果您必须在网页上动画显示阴影,那么值得优化它们的性能。在本节中,您将了解如何调整阴影动画以减少性能影响。

我们将涵盖以下内容:

  • 动画透明度

  • 使用多个盒阴影层

  • 动画伪元素

  • 使用变换属性

1. 动画透明度:

当只需要改变阴影的透明度时,可以通过动画化opacity属性来实现,而不是使用box-shadow属性。动画opacity属性通常比动画box-shadow属性的性能更好。

2. 使用多个盒阴影层:

在需要多个阴影层的情况下,可以使用多个box-shadow属性来创建每个阴影层,并将每个阴影层的属性值进行动画处理。这样可以减少对单个box-shadow属性的动画,并更好地控制每个阴影层的效果。

3. 动画伪元素:

可以使用伪元素(如::before::after)来创建阴影效果,并对伪元素的属性进行动画处理。这样可以将阴影效果与原始元素的动画分离,减少性能开销。

4. 使用变换属性:

如果仅需要平移或缩放阴影效果,可以使用transform属性来实现动画效果。通过对阴影元素应用transform属性的动画,可以利用硬件加速来提高性能。

通过这些方法,您可以减少盒阴影动画对性能的影响,提高网页的加载和渲染速度。记住,在优化性能方面,始终进行测试和优化以获得最佳的用户体验。

5.动画透明度:

当使用rgba颜色时,透明度由alpha通道控制。在动画阴影时,仅改变alpha通道不会像改变阴影的偏移和扩散值那样对CPU造成负担。因此,在动画阴影时,仅改变alpha通道的值会更加轻量级。

.box { box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.5); transition: box-shadow ease 0.5s; } .box:hover { box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.25); } .box-2 { box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.5); transition: box-shadow ease 0.5s; } .box-2:hover { box-shadow: 0px 20px 40px 0px rgba(0, 0, 0, 0.25); }

在第一个动画中,只有阴影的不透明度在改变,而在第二个动画中,y偏移量从10px变为20px,扩散从20px变为40px。

以下是它们在6倍减速时的性能表现(这样您可以清楚地看到性能图表),首先是只改变不透明度的动画: 16654660626.webp

Layered shadows

如果您观察桌子周围的阴影,或将物体抬离桌面,您会注意到最暗的阴影区域靠近物体,随着向外扩散,阴影逐渐变亮。

通过单个盒阴影来复制这种效果并不容易。多层阴影看起来更好,并且即使添加了阴影层,它们也可以产生更高性能的动画效果。

让我们比较单个盒阴影和多层阴影的性能:

.box {
  box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.5);
  transition: box-shadow ease 0.5s;
}
.box:hover {
  box-shadow: 0px 20px 40px 0px rgba(0, 0, 0, 0.25);
}

这个动画有148ms的渲染时间和133ms的绘制时间。

现在让我们来看一个包含两个盒阴影层的阴影动画:

.box-2 {
  box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.25),
              0px 10px 20px 0px rgba(0, 0, 0, 0.5);
  transition: box-shadow ease 0.5s;
}
.box-2:hover {
  box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.25),
              0px 20px 40px 0px rgba(0,0,0,0.15);
}

差异很明显。多层阴影不仅能产生更好的阴影效果,而且在动画时表现出乎意料地更好。渲染时间从148ms减少到74ms,绘制时间也从133ms减少到74ms。

以下是两者的实时演示对比。

现在,让我们尝试一些不同的方法,在动画期间添加第二个阴影:

.box-2 {
  box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.5);
  transition: box-shadow ease 0.5s;
}
.box-2:hover {
  box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.25),
              0px 20px 40px 0px rgba(0,0,0,0.15);
}

在动画期间添加第二个阴影层并不像一开始就有两个阴影层那样性能好,但相比于单个盒阴影动画的133ms,它仍然有100ms的绘制时间,这是一个改进。

最终,您可以根据自己的喜好决定阴影的外观以及使用的方法。

Animating a Pseudo Element

在这个示例中,我们将复制阴影动画,但不改变box-shadow属性。从之前的演示中,我们可以看到在阴影动画期间仍然有大量的重新绘制。如果您改变了box-shadow的值,就无法避免这个过程。

您将会看到,在本节结束时,重新绘制将几乎完全消除。虽然这需要更多的代码行数,但我们将实现更高性能的阴影动画。

所以,在为盒子创建基本样式之后,创建一个:after伪元素,并为它添加一个box-shadow,这将是动画结束后阴影的最终状态:

.pseudo {
  position: relative;
  transition: box-shadow ease 0.5s, transform ease 0.5s;
  box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.5)/*Initial shadow*/
}
.pseudo::after{
  content: "";
  position: absolute;
  border-radius: 20px;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  box-shadow: 0px 10px 20px 2px rgba(0, 0, 0, 0.25);/*final shadow*/
  transition: opacity ease 0.5s;
}

现在,您只需要在:hover伪类上更改伪元素的不透明度即可:

.pseudo:hover{
  transform: translateY(-10px);
}
.pseudo:hover::after{
  opacity: 1;
}

在视觉上没有太多可比较的东西。真正的区别在于它们的性能。下面是常规box-shadow动画的结果:

166546624712.webp

它的渲染时间为230ms,绘制时间为211ms。现在是伪阴影动画。

这一次,渲染时间为148ms,绘制时间仅为51ms。虽然代码量增加了,但结果是值得努力的。

使用transform属性 这主要适用于主要元素,即带有阴影的盒子。使用transform属性而不是像margin这样会改变布局的属性,可以减少样式重新计算的次数。

可以使用translate或scale属性与该属性一起使用,模拟将元素从页面上抬起,创造出深度的错觉。

一些建议 已经确定了涉及box-shadow属性的任何动画都会影响性能。因此,如果您必须使用CSS盒阴影动画,请记住以下一些建议。

首先,保持它们的数量最少。不要为了而在每个元素上添加阴影。其次,只对交互元素进行动画处理。没有必要对没有功能的任何元素进行动画处理。这将减轻CPU的工作负载,并大大提高性能。

总结

阴影可以在视觉上增强您的网站,但它们也会影响性能,特别是在动画方面。在本文中,我们测试了各种方法来动画阴影并比较它们的性能。动画化阴影会触发三个事件——绘制、布局更改和合成,其中绘制是最费力的。

理想的解决方案是根本不要动画化阴影(因为它们本身看起来就很好!)。如果您真的想要动画化box-shadow属性,那么只改变不透明度而不是改变偏移值将减少重绘。问题在于,您将失去阴影应提供的深度错觉。另一种方法是动画化两个box-shadow图层。这种解决方案在视觉上令人愉悦,并且具有良好的性能,即使有额外的阴影。

最后的选择是不动画化box-shadow,而是动画化提供阴影的伪元素。这大大减少了重绘的数量和CPU在运行动画时的总体工作量。您需要编写更多的代码,但这是确保良好性能的最佳选择。