作者简介
Kay Huang,携程高级视觉设计师,专注于前端样式与动画领域。
一 、背景
携程火车票营销页使用 css 制作动画很多年了,这大大提高了动画给予页面丰富的视觉体验。不过,在开发的过程中,也遇到了一些性能相关问题和用户反馈,比如头部动画卡顿、页面打开时间较长、页面打开后部分数据加载时间较长等问题。为解决这些问题,我们借助性能检测工具定位问题,并查阅源码、文档等资源解决问题,形成了这篇文章。
二、渲染优化
要优化动画性能,首先要了解浏览器是如何进行元素渲染的,浏览器的渲染流程有以下四步:
a. 计算元素的样式(可能通过脚本重新计算);
b. 生成每个元素的几何形状和位置(布局);
c. 绘制图层的每个像素(初始化绘图并且进行绘图);
d. 将图层绘制到屏幕上(合并渲染层)。
对于 CSS3 动画来说,每一帧都要经历上述过程。关于最后一步合并渲染层(可以类比 Photoshop 的图层),浏览器会在特定的场合创建独立的渲染层,每个渲染层由 GPU 独立绘制,互不影响,最后浏览器再把各个渲染层合并。这是一种代价较低的操作。
理论上说,FPS 越高,动画会越流畅,目前大多数设备的屏幕刷新率为 60 次/秒,所以通常来讲 FPS 为 60frame/s 时动画效果最好,也就是每帧的消耗时间(帧预算)为 16.67ms。
三、解决方案
如上所说浏览器有整理工作要做,因此所有工作需要尽量在 10 ms 内完成。如果制作的动画触发了布局,那就相当于要进行第二步重新绘制,如果重新绘制的话浏览器渲染的时间肯定超过 16ms,那么我们的页面就会出现卡顿,如果是移动端的话那就会更慢,所以我们如果要优化的话那就要从第一步直接跳到第四步。
下面我写了七种优化动画性能的方法,有直接从第一步跳到第四步的也有一些其他平时优化注意事项。
3.1 开启 GPU 加速
Transform 属性可以向元素应用 2D 或者 3D 转换,可以对元素进行选择、缩放、移动和倾斜。
在日常中我们可以使用 left/top,translate 来实现元素的位移,但是其实性能上还是有一定区别的因为 transform 属性不会改变自己和他周围元素的布局,他会对元素的整体产生影响。
让我们先用 top/left 属性创建一个动画看看效果。
<style>
.heart{animation: heartbeat 4s infinite;width:50px;height:50px;background:red;position:absolute;left:30px;top:30px;}
@keyframes heartbeat{
0%{top:30px;left:30px;}
25%{top:30px;left:230px;}
50%{top:230px;left:230px;}
75%{top:230px;left:30px;}
}
</style>
用 chrome 浏览器的 DevTools 查看可以看到红色方块都是布局重绘。
图中有那么多的红色方框与帧数是因为浏览器会做大量的计算,动画就会卡顿。
因为每一帧的变化浏览器都在进行布局、绘制、把新的位图交给 GPU 内存,虽然只改变元素位置但是很可能要同步改变他的子元素的位置,那浏览器就要重新计算布局,计算完后主线程再来重新生成该元素的位图。
现在,将动画用 transform 代替:
<style>
.heart{animation: heartbeat 1s;width:50px;height:50px;background:red;}
@keyframes heartbeat{
0%{transform: translate(30px,30px);}
25%{transform: translate(30px,230px);}
50%{transform: translate(230px,230px);}
75%{transform: translate(230px,30px);}
}
</style>
再次分析,可以看到,没有发生红色的重绘方块且只有一条帧数,就知道动画肯定会流畅丝滑。
因为 transform 属性不会改变自己和他周围元素的布局,他会对元素的整体产生影响。
我们通过节点的 transform 可以修改节点的位置、旋转、大小等。我们平常会使用 left 和 top 属性来修改节点的位置,但正如上面所述,left 和 top 会触发重布局,修改时的代价相当大。取而代之的更好方法是使用 translate,这个不会触发重布局。
3.2 避免使用影响性能的 CSS 属性
这些属性会影响性能,因为它们需要进行复杂的计算和渲染,尤其是在动画中使用时。这些属性可能会导致浏览器进行重排和重绘,从而影响页面的性能和流畅度。
如果您必须使用这些属性,请尽可能减少它们的使用。例如,您可以尝试使用 CSS3 的 transform 属性来实现 box-shadow 和 border-radius 的效果,因为它们可以更好地利用浏览器的硬件加速。
例如,下面是一个使用 box-shadow 属性的示例:
.box {
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
这个 box-shadow 属性会在元素周围添加一个阴影效果,但是它会影响性能,因为浏览器需要进行复杂的计算和渲染。为了优化性能,我们可以使用 CSS3 的 transform 属性来实现相同的效果:
.box { transform: translateZ(0); box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);}
这个 transform 属性会启用硬件加速,从而提高性能。同时,我们仍然可以使用 box-shadow 属性来添加阴影效果。
3.3 避免使用复杂的选择器
选择器和动画之间存在一定的关系。在 CSS 动画中,选择器的复杂度越高,样式计算的时间就越长。在动画中使用复杂的选择器会导致浏览器需要更长的时间来计算样式,从而影响动画的性能和流畅度。
例如,当我们使用复杂的选择器来选择元素,并为它们添加动画效果时,浏览器需要花费更长的时间来计算样式,从而影响动画的性能和流畅度。相反,当我们使用简单的选择器来选择元素,并为它们添加动画效果时,浏览器可以更快地计算样式,从而提高动画的性能和流畅度。
假设我们有一个列表,其中包含多个项目。我们想要为这些项目添加一个简单的动画效果,当鼠标悬停在项目上时,项目的背景色会渐变为蓝色。我们可以使用以下 CSS 代码来实现这个效果:
/* 使用类选择器来选择所有项目 */
.item {
background-color: #fff; /* 初始背景色为白色 */
transition: background-color 0.3s ease; /* 添加背景色渐变动画 */
}
/* 当鼠标悬停在项目上时,将背景色渐变为蓝色 */
.item:hover {
background-color: #007bff; /* 背景色渐变为蓝色 */
}
在这个例子中,我们使用了类选择器来选择所有的项目,并为它们添加了一个初始的背景色和一个背景色渐变动画。当鼠标悬停在项目上时,我们使用: hover 伪类选择器来选择当前悬停的项目,并将其背景色渐变为蓝色。
这个例子中的选择器非常简单,浏览器可以很快地计算样式,从而提高动画的性能和流畅度。相比之下,如果我们使用复杂的选择器来选择项目,并为它们添加动画效果,浏览器需要花费更长的时间来计算样式,从而影响动画的性能和流畅度。
3.4 使用 will-change
使用 will-change 属性来告诉浏览器哪些元素将要进行动画,以便浏览器提前进行优化。
will-change 属性是 CSS3 的一个新属性,它可以告诉浏览器哪些元素将要进行动画,从而使浏览器可以提前进行优化,提高动画的性能和流畅度。
例如,如果您要对某个元素进行动画,您可以在 CSS 中添加以下代码:
#textbox {
opacity: 1; /* 初始透明度为1 */
transition: opacity 0.3s ease; /* 添加透明度渐变动画 */
will-change: opacity; /* 告知浏览器我们将会修改透明度 */
}
在这个例子中,我们使用 will-change 属性来告知浏览器我们将会修改文本框的透明度,从而使浏览器可以提前进行优化。当动画开始时,浏览器已经准备好了相应的资源,从而可以更快地渲染动画,提高动画的性能和流畅度。
需要注意的是,will-change 属性应该谨慎使用,因为它可能会导致浏览器提前分配额外的内存和资源,从而影响页面的性能。因此,只有在必要的情况下才应该使用 will-change 属性。
CSS3 will-change 属于 web 标准属性,兼容性这块 Chrome/FireFox/Opera 都是支持的。
使用 will-change 属性是优化 CSS 动画的重要技巧之一,可以提高动画的性能和流畅度。
3.5 使用 requestAnimationFrame
requestAnimationFrame 代替 setTimeout 或 setInterval 来执行动画,因为它可以最大程度地利用浏览器的优化。
requestAnimationFrame 是浏览器提供的一个 API,它可以让我们在下一次浏览器绘制之前执行动画。与 setTimeout 或 setInterval 相比,requestAnimationFrame 可以更好地利用浏览器的优化,从而提高动画的性能和流畅度。
例如,如果您要对某个元素进行动画,您可以使用 requestAnimationFrame 来执行动画:
function animate() {
// 更新元素的样式
element.style.transform = 'translateX(100px)';
// 使用requestAnimationFrame执行下一帧动画
requestAnimationFrame(animate);
}
// 开始执行动画
requestAnimationFrame(animate);
在上面的代码中,我们使用 requestAnimationFrame 来执行动画。在每一帧动画中,我们更新元素的样式,然后使用 requestAnimationFrame 执行下一帧动画。这样可以最大程度地利用浏览器的优化,提高动画的性能和流畅度。
需要注意的是,requestAnimationFrame 并不是所有浏览器都支持,因此在使用它时需要进行兼容性处理。通常情况下,我们可以使用一个polyfill来实现 requestAnimationFrame 的兼容性。
3.6 避免在动画中使用 JavaScript 操作 DOM
在动画中使用 JavaScript 操作 DOM 会影响性能,主要是因为 DOM 操作是非常耗费资源的,因为这会引起重排和重绘。每次操作 DOM 都会触发浏览器重新计算元素的布局和重新绘制元素,这些操作会消耗大量的 CPU 资源和内存,导致动画卡顿或者不流畅。
在动画中,如果需要频繁地操作DOM,就会导致性能问题。例如,如果在动画中使用 JavaScript 来改变元素的位置、尺寸、样式等属性,就会触发 DOM 操作,影响动画的流畅度。
如果必须使用 JavaScript 操作 DOM,请尽可能减少它们的使用。例如,您可以在动画开始前将需要操作的元素缓存到变量中,然后在动画中直接使用这些变量,而不是每次都重新查找元素。
另外,还可以使用 CSS3 的动画属性来代替 JavaScript 操作 DOM。例如,使用 animation 属性可以实现复杂的动画效果,而不需要使用 JavaScript 操作 DOM,下面会详细说为什么用尽量用 css 动画而不使用 javascript 动画。
3.7 用 CSS 动画尽量不使用 JavaScript 动画
因为前者可以更好地利用浏览器的优化。
假设我们有一个按钮,当用户点击按钮时,我们想要将一个文本框从屏幕上移除,并在移除时添加一个简单的动画效果。我们可以使用以下 JavaScript 代码来实现这个效果:
var textbox = document.getElementById('textbox'); // 获取文本框元素
var button = document.getElementById('button'); // 获取按钮元素
button.addEventListener('click', function() {
textbox.style.opacity = 0; // 文本框透明度渐变为0
setTimeout(function() {
textbox.parentNode.removeChild(textbox); // 移除文本框元素
}, 300); // 延迟300毫秒后移除文本框元素
});
在这个例子中,我们使用 JavaScript 操作 DOM 元素,通过获取文本框和按钮元素,并在按钮被点击时逐渐将文本框的透明度降低到 0,然后在 300 毫秒后移除文本框元素。
然而,这种方法会导致浏览器进行重排和重绘,从而影响动画的性能和流畅度。相反,我们可以使用 CSS3 的 transition 属性来实现一个简单的动画效果,而无需使用 JavaScript 操作 DOM 元素。
例如,我们可以使用以下 CSS 代码来实现一个简单的动画效果,当用户点击按钮时,文本框会逐渐消失:
#textbox {
opacity: 1; /* 初始透明度为1 */
transition: opacity 0.3s ease; /* 添加透明度渐变动画 */
}
#textbox.hide {
opacity: 0; /* 透明度渐变为0 */
}
在这个例子中,我们使用 CSS3 的 transition 属性来实现一个简单的透明度渐变动画。当用户点击按钮时,我们使用 JavaScript 为文本框添加一个 hide 类,这个类会将文本框的透明度逐渐降低到 0,从而实现文本框逐渐消失的动画效果。
这个例子中的动画效果可以直接作用于 DOM 元素,而无需使用 JavaScript 操作 DOM 元素,从而提高动画的性能和流畅度。
在动画中使用 CSS 动画可以更好地利用浏览器的硬件加速,从而提高动画的性能和流畅度。相比之下,JavaScript 动画通常需要更多的计算和操作,从而影响动画的性能和流畅度。
当然,在某些情况下,JavaScript 动画可能是必要的。例如,在需要与用户交互的动画中,JavaScript 动画可以更好地控制动画的行为。但是,在这种情况下,我们仍然应该尽可能减少 JavaScript 操作的次数,以提高动画的性能和流畅度。
四、结论
动画给予了页面丰富的视觉体验。我们应该尽力避免使用会触发重布局和重绘的属性,以免失帧。最好提前申明动画,这样能让浏览器提前对动画进行优化。由于 GPU 的参与,现在用来做动画的最好属性是如下几个:* opacity* translate* rotate* scale,Javascript 优化、减少 Layout 和 Paint。希望对大家了解浏览器的渲染机制和日常的动画开发有所帮助。
性能优化是一件不断持续,不断深入的事情。我们通过本文中所介绍的改进措施对页面性能实现了很大的优化,达到了不错的效果。后续也会在此基础之上对还可提高的地方继续加深,后续提供通用的解决方案。