介绍
如果你曾留意,在webkit内核的浏览器中,在执行某些CSS操作,尤其是transform和animation,出现“闪烁(flicker)”的情况,那么你很可能听到过“硬件加速”这个术语了。
CPU GPU 和硬件加速
狭义上说,硬件加速指图像处理单元(GPU)通过处理一些吃力的事情来协助你的浏览器渲染页面,而不是一股脑把所有事情丢给CPU去处理。当CSS操作使用硬件加速之后,通常能获得速度上的提升,页面渲染会更快。
如字面意思,CPU和GPU都是处理单元。CPU位于计算机的主板,它处理几乎所有事情,被称为计算机的大脑。GPU位于计算机显卡, 响应处理和渲染图像。此外,GPU被特地设计来执行图形渲染必要的复杂的数学和几何的运算。因此,将操作转移到GPU上可以获取巨大的性能提升,并且减少在移动设备上CPU的占用少.
硬件加速,依赖一个浏览器使用的图层模型,当它在渲染页面的时候。当页面的元素执行了某些操作(如3D变形)元素会被移到它自己的“层(layer)”中,层里可以从页面其余部分中独立出来渲染,之后再组合回去。这隔离了内容的渲染,所以如果元素只是在帧数之间变形了,页面剩余的部分无需再渲染,并且经常提供显著的速度优势。这里值得一提,只有3D变形才有资格拥有自身图层,2D图层则不是。
CSS动画(animation),变形(transform)和过渡(transition)没有自动GPU加速,取而代之的是从浏览器慢吞吞的软件渲染引擎去执行。不过有些浏览器通过某些属性提供了硬件加速,以此了提供更好的渲染性能。举个栗子,opacity
属性是为数不多的可以被加速的属性,因为GPU可以轻易地操作它。基本上,任何你想通过CSS过渡或动画淡化的图层,浏览器实际上都足够聪明的将它扔给GPU去完成操作,并且很快就可以完成。在所有CSS中,opacity
是效率最高的一个,而且使用起来没有任何问题。其它通用硬件加速操作都是CSS 3D变形。
旧方式: translateZ()
或translate3d()
有很长一段时间,我们使用translateZ() 或者translate3d() hack去欺骗浏览器使用硬件加速加速我们的动画或者形变。我们通过将一个简单的3D变形加到一个不会在三维空间变化的元素中。举个栗子,一个在二维空间动画化的元素,可以通过添加简单的规则来打到硬件加速。
transform: translate3d(0, 0, 0);
硬件加速操作会导致创建一个组合层,组合层将会上传到GPU并由GPU组合。不过,强制hack创建组合层并不总能解决页面上的性能瓶颈。图层创建技术能提升页面速度,不过也带来了成本:它们占用系统RAM和GPU上的内存(尤其移动设备上有限制),而且有很多坏的影响(特别是在移动设备上)。所以必须明智地使用它们,你也必须确认硬件加速操作是否真能帮助你页面的性能,页面上的其它操作不会导致性能瓶颈。
为了避免创建层这样的hack,一个新的CSS属性被引进,它使我们能提前告诉浏览器我们将会改变元素的哪些属性,从而允许浏览器提前优化如何操控元素。举个例子,在动画真正开始之前,执行潜在耗时的工作为诸如动画等操作做好准备。这个属性就是新属性will-change。
新方式: 极好的will-change
属性
will-change
属性允许你提前告诉你的浏览器,你可能会改变元素的哪些属性。这样浏览器可以在元素需要之前设置合适的优化。避免了一个不小的启动成本,而这样的成本对页面的响应速度有着副作用。元素可以更快的被改变和渲染,而页面更新有个更平滑的体验。
举个栗子,当我们在一个元素上使用CSS 3D 变形时, 如我们之前提到的,在它们被组合(绘制到屏幕)之前,元素和它的内容会被提升到一个层。虽然设置一个元素进入一个新的图层,是一个相对昂贵的操作,会导致动画的开始有明显的几分之一秒的时延,导致有明显的“闪烁(flicker)”。
为了避免这样的时延,你可以在实际发生之前,告诉浏览器有关的更改。这样,浏览器就有时间去为这些更改做准备,所以当这些更改发生的时候,元素的图层已经被准备好了,可以快速地执行变形动画,渲染元素,更新页面。
像这样简单的添加规则到你需要变形的元素上,就可以提示浏览器即将发生哪些变形:
will-change: transform;
你也可以通过指定你想改变的属性名,向浏览器声明你是想改变元素滚动位置(元素在可视滚动窗口的位置以及它在窗口有多少可见部分),还是它的内容,还是元素中一或多个CSS属性值。如果你希望或计划改变元素的多个值,你可以提供用逗号隔开的值。举个栗子,如果你希望元素被动画化和移动,你可以这样向浏览器声明:
will-change: transform, opacity;
明确指定你想要改变的内容,以便浏览器可以做出针对需要特定更改的关于优化的决策。这明显是一个更好的方式,你可以获取提速,不用求助于hack和强制浏览器去有必要或无必要地,有用或无用地创建新图层
- 除了提示浏览器该元素的更改之外,will-change是否会影响它所应用的元素 *
答案是是和不是,这取决于你指定和告诉浏览器的属性。如果任何非初始值的属性,将会在元素上创建一个层叠上下文,指定这个属性到will-change中,将会在元素上创建一个层叠上下文。举个栗子,当使用非初始值的clip-path和opacity属性都会导致应用它们的元素创建一个层叠上下文(stacking context)。因此,使用一个或全部属性当做will-change的值都会创建一个层叠上下文,尽管在改变发生之前。应用其它的属性也会在元素上创建一个层叠上下文
有些属性会导致为固定定位元素创建一个包含块(container block)。举个栗子,一个变形元素会为其所有定位的后代元素创建一个包含块,即使这些后代元素已经被设置了position: fixed
。所以,如果属性会导致创建包含块,那么指定它为will-change的值也会导致为固定定位的元素产生一个包含块。
最后,如之前陈述的那样,一些will-change的变化导致一个新的合成层。然而在大多数浏览器中,GPU不支持像CPU那样的亚像素平滑处理,这会导致有时候内容模糊。尤其是文本。
除此之外,will-change属性不会直接影响应用它的元素。它仅仅只是一个渲染提示,使浏览器优化将会发生在元素上的改变。
它不会直接影响元素,除了上面提到的层叠上下文和包含块的创建。
使用will-change:该做与不该做的
知道了will-change的作用,可能会让我们产生错觉:只需要让浏览器去优化一切事情。我说的对吧?谁不想对所有更改进行优化,以备时需。
will-change的能力越大,其责任也就越大。will-change需要被明智地使用,否则最终会导致实际上你页面性能上的崩溃
与任何性能问题一样,will-change的副作用无法直接检测到,毕竟,这只是在幕后与浏览器对话的一种方式,所以它可能会难以使用。这里有几件需要在使用的这个属性的时候记住的事情,就是确保最好利用它的同时,避免滥用这个属性而造成损害。
不要用will-change去声明太多属性或者元素
如我之前说的,让浏览器去优化所有元素中的所有属性中可能发生的改变,也许非常诱人,所以一开始的时候添加下面的规则到我们的样式表中,好像是有意义的。
*,
*::before,
*::after {
will-change: all;
}
看起来挺不错的,实际上是非常有害的,而且更无效。不仅是all关键字对will-change来说是个无效值(我们会在文章后面提到哪些用效值和无效值),这样总括式的规则是没有用的。你知道浏览器会尽其所能去尝试优化所有事情(还记得opacity和3D变形吗?),因此,明确的告诉浏览器去优化并不会改变人任何事情或者有任何帮组。实际上,这样做具有一定的危害性,因为一些可能被捆绑到will-change的重度优化,会导致使用大量的机器资源,这样过渡使用会导致页面变慢或崩溃。
给浏览器足够的时间运作
will-change属性这样命名是有一个明显的原因的:告诉浏览器改变将会发生,而不是改变正在发生。使用will-change,我们让浏览器为我们声明的改变做某些优化,浏览器需要有些时间做这些优化,这样到改变发生的时候,优化可以无延迟的应用上。
在元素改变之前立即设置will-change属性没什么卵用。(还不如啥都不要弄,会导致一个新图层的成本,而这时你的动画不会有预先获得一个新图层的资格)。举个栗子,如果变化是发生在鼠标悬停时:
.element:hover {
will-change: transform;
transition: transform 2s;
transform: rotate(30deg) scale(1.5);
}
虽然已经告诉浏览器需要优化的改变,但这是没有用的,有点违背will-change的概念。你应该至少提前一些时间预知哪些事情将要改变,并设置will-change。
举个栗子,如果元素在点击之后改变,那我们在鼠标悬停的时候设置will-change,已经有足够的时间让浏览器去完成优化。鼠标悬停元素距离用户实际点击的时间间隔足够浏览器去建立优化,因为人类反应时间相对比较慢,所以这将会在改变发生之前,给浏览器200毫秒左右的时间窗口,并且这已足够浏览器去设置优化。
.element {
/* style rules */
transition: transform 1s ease-out;
}
.element:hover {
will-change: transform;
}
.element:active {
transform: rotateY(180deg);
}
但如果你希望更改是发生在悬停而非点击的时候呢?上面的声明就如我们之前提到的已经没有用处了。在这种情况下,仍然有方式预知将会发生的行为。举个栗子,悬停在改变元素的祖先元素可能有足够的时间:
.element {
transition: opacity .3s linear;
}
/* declare changes on the element when the mouse enters / hovers its ancestor */
.ancestor:hover .element {
will-change: opacity;
}
/* apply change when element is hovered */
.element:hover {
opacity: .5;
}
但是,将鼠标悬停在祖先元素上,并不始终表面你会跟该元素交互,所以,你可以做诸如设置will-change的事情,当视图在你的应用程序中处于活动状态时,或者元素在视口的可见部分之内时,这会增加与元素交互的机会。
在动画结束之后移除will-change
优化通常带来成本和像我们之前提到的,会占用机器资源。通常浏览器优化行为,会尽快移除这些优化并恢复到正常行为。但是,will-change将覆盖此行为,从而使优化时长比浏览器原本的要长的多。
正因如此,你应该记得在元素完成变形之后移除will-change。这样浏览器可以恢复
要求优化的任何资源。
想要在移除声明在样式表里的will-change是不可能的,这也是为什么几乎总建议你通过JavaScript去设置和取消will-change。通过脚本,你可以向浏览器声明你的变化,并监听元素完成之后移除will-change。举个栗子,像我们之前章节提到的,在样式规则里做的一样,我们可以监听元素或其祖先元素是否悬停,在鼠标进入之后设置will-change。如果你的元素使用的是动画,你可以监听动画是否结束,通过DOM事件animationEnd,一旦事件触发,移除will-change属性。
// Rough generic example
// Get the element that is going to be animated on click, for example
var el = document.getElementById('element');
// Set will-change when the element is hovered
el.addEventListener('mouseenter', hintBrowser);
el.addEventListener('animationEnd', removeHint);
function hintBrowser() {
// The optimizable properties that are going to change
// in the animation's keyframes block
this.style.willChange = 'transform, opacity';
}
function removeHint() {
this.style.willChange = 'auto';
}
Craig Buckler写过一篇关于JavaScript中的CSS动画事件的文章,如果你不熟悉这些的话需要查阅一下这边文章。这里还有一篇有关控制CSS动画和过渡CSS技巧的文章,也值得查阅一下。
保守地在样式表里使用will-change
正如我们在上一节中所看到的,可以使用will-change来提示浏览器有关在几毫秒之内发生的更改。 这是一种可以在样式表中声明更改的用例之一。 尽管建议使用JavaScript进行设置和取消设置,但是在某些情况下可以在样式表中进行设置(并保持设置)。
一个例子是,在少量的元素中设置will-change,如那些可能需要一次次更用户交互的元素,而且他们需要以一种贪婪的方式响应用户的交互。限制元素个数,意味着
不会过度使用浏览器优化,因此不会太伤。举个栗子,当用户需要的它的时候,以滑出的方式变形一个侧边栏。下面是合适的规则:
.sidebar {
will-change: transform;
}
另外一个例子是在几乎不停地变化的元素中使用will-change,如一个元素需要响应鼠标的移动,并在屏幕上跟随鼠标的移动。这种情况,是可以在样式表里面声明will-change值,因为它准确地描述了元素将定期/不断地更改,因此应该对其进行优化。
.annoying-element-stuck-to-the-mouse-cursor {
will-change: left, top;
}
will-change属性值
will-change的值为下列4种的其中一种:auto,scroll-position,contents,和<custom-ident>。
<custom-ident>指定一个或多个你希望更改的属性名。多个属性用逗号隔开。
以下是带有指定属性名称的有效变更声明的示例:
will-change: transform;
will-change: opacity;
will-change: top, left, bottom, right;
<custom-ident>的值不包括will-change,none,all,auto,scroll-position和contents,以及通常从<custom-ident>中排除的值。所以,正如我们文章开头提到的:all的声明是无效的,因此会被浏览器忽略。
auto表示浏览器不需要设置任何除通常情况下的特殊优化。
scroll-position表示你将会改变元素的滚动位置。这是个有用的值,因为当使用它的时候,浏览器将为可滚动元素里的滚动窗口中可见内容进行准备和渲染。浏览器通常只渲染在滚动窗口内的内容,以及一些窗口中过去的内容,平衡内存和跳过渲染节省的时间让滚动看起来漂亮。使用will-change: scroll-position
可以进行更好的渲染优化,以便可以顺利完成更长和/或更快的内容滚动。
content表示元素的内容将会改变。浏览器通常“缓存”元素的渲染,因为大部分的元素不会经常改变,或者只是改变它们的位置。这个值会被浏览器当做一个少去缓存元素的标志,或者避免缓存整个元素,因为如果元素内容会定期改变,那么保持内容的缓存将会毫无用处和浪费时间,浏览器会停止缓存和继续渲染元素每当它的内容改变了。
之前提到的,有些属性指定给will-change是没有效果的,因为浏览器不会为大多数属性执行任何特殊的优化。仍可以安全的指定它们,虽然根本没有效果。其它属性可能会导致层叠上下文的创建(opacity,clip-path等)和包含块。
浏览器支持
Chrome 36+, Opera 24+, and Firefox 36+,Safari9.1,Edge79+ 支持 the will-change 属性。IE不支持
总结
will-change属性将帮助我们编写无hack性能优化的代码,并强调速度和性能对我们CSS操作的重要性。
原文地址:Everything You Need to Know About the CSS will-change Property