你不得不知道的关于CSS的will-change属性的所有事

介绍

如果你曾留意,在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变形加到一个不会在三维空间变化的元素中。举个栗子,一个在二维空间动画化的元素,可以通过添加简单的规则来打到硬件加速。

css 复制代码
transform: translate3d(0, 0, 0);

硬件加速操作会导致创建一个组合层,组合层将会上传到GPU并由GPU组合。不过,强制hack创建组合层并不总能解决页面上的性能瓶颈。图层创建技术能提升页面速度,不过也带来了成本:它们占用系统RAM和GPU上的内存(尤其移动设备上有限制),而且有很多坏的影响(特别是在移动设备上)。所以必须明智地使用它们,你也必须确认硬件加速操作是否真能帮助你页面的性能,页面上的其它操作不会导致性能瓶颈。

为了避免创建层这样的hack,一个新的CSS属性被引进,它使我们能提前告诉浏览器我们将会改变元素的哪些属性,从而允许浏览器提前优化如何操控元素。举个例子,在动画真正开始之前,执行潜在耗时的工作为诸如动画等操作做好准备。这个属性就是新属性will-change。

新方式: 极好的will-change属性

will-change属性允许你提前告诉你的浏览器,你可能会改变元素的哪些属性。这样浏览器可以在元素需要之前设置合适的优化。避免了一个不小的启动成本,而这样的成本对页面的响应速度有着副作用。元素可以更快的被改变和渲染,而页面更新有个更平滑的体验。

举个栗子,当我们在一个元素上使用CSS 3D 变形时, 如我们之前提到的,在它们被组合(绘制到屏幕)之前,元素和它的内容会被提升到一个层。虽然设置一个元素进入一个新的图层,是一个相对昂贵的操作,会导致动画的开始有明显的几分之一秒的时延,导致有明显的"闪烁(flicker)"。

为了避免这样的时延,你可以在实际发生之前,告诉浏览器有关的更改。这样,浏览器就有时间去为这些更改做准备,所以当这些更改发生的时候,元素的图层已经被准备好了,可以快速地执行变形动画,渲染元素,更新页面。

像这样简单的添加规则到你需要变形的元素上,就可以提示浏览器即将发生哪些变形:

css 复制代码
will-change: transform;

你也可以通过指定你想改变的属性名,向浏览器声明你是想改变元素滚动位置(元素在可视滚动窗口的位置以及它在窗口有多少可见部分),还是它的内容,还是元素中一或多个CSS属性值。如果你希望或计划改变元素的多个值,你可以提供用逗号隔开的值。举个栗子,如果你希望元素被动画化和移动,你可以这样向浏览器声明:

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去声明太多属性或者元素

如我之前说的,让浏览器去优化所有元素中的所有属性中可能发生的改变,也许非常诱人,所以一开始的时候添加下面的规则到我们的样式表中,好像是有意义的。

css 复制代码
*,
*::before,
*::after {
	will-change: all;
}

看起来挺不错的,实际上是非常有害的,而且更无效。不仅是all关键字对will-change来说是个无效值(我们会在文章后面提到哪些用效值和无效值),这样总括式的规则是没有用的。你知道浏览器会尽其所能去尝试优化所有事情(还记得opacity和3D变形吗?),因此,明确的告诉浏览器去优化并不会改变人任何事情或者有任何帮组。实际上,这样做具有一定的危害性,因为一些可能被捆绑到will-change的重度优化,会导致使用大量的机器资源,这样过渡使用会导致页面变慢或崩溃。

给浏览器足够的时间运作

will-change属性这样命名是有一个明显的原因的:告诉浏览器改变将会发生,而不是改变正在发生。使用will-change,我们让浏览器为我们声明的改变做某些优化,浏览器需要有些时间做这些优化,这样到改变发生的时候,优化可以无延迟的应用上。

在元素改变之前立即设置will-change属性没什么卵用。(还不如啥都不要弄,会导致一个新图层的成本,而这时你的动画不会有预先获得一个新图层的资格)。举个栗子,如果变化是发生在鼠标悬停时:

css 复制代码
.element:hover {
	will-change: transform;
	transition: transform 2s;
	transform: rotate(30deg) scale(1.5);
}

虽然已经告诉浏览器需要优化的改变,但这是没有用的,有点违背will-change的概念。你应该至少提前一些时间预知哪些事情将要改变,并设置will-change。

举个栗子,如果元素在点击之后改变,那我们在鼠标悬停的时候设置will-change,已经有足够的时间让浏览器去完成优化。鼠标悬停元素距离用户实际点击的时间间隔足够浏览器去建立优化,因为人类反应时间相对比较慢,所以这将会在改变发生之前,给浏览器200毫秒左右的时间窗口,并且这已足够浏览器去设置优化。

css 复制代码
.element {
	/* style rules */
	transition: transform 1s ease-out;
}
.element:hover {
	will-change: transform;
}
.element:active {
	transform: rotateY(180deg);
}

但如果你希望更改是发生在悬停而非点击的时候呢?上面的声明就如我们之前提到的已经没有用处了。在这种情况下,仍然有方式预知将会发生的行为。举个栗子,悬停在改变元素的祖先元素可能有足够的时间:

css 复制代码
.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属性。

javascript 复制代码
// 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,如那些可能需要一次次更用户交互的元素,而且他们需要以一种贪婪的方式响应用户的交互。限制元素个数,意味着 不会过度使用浏览器优化,因此不会太伤。举个栗子,当用户需要的它的时候,以滑出的方式变形一个侧边栏。下面是合适的规则:

css 复制代码
.sidebar {
	will-change: transform;
}

另外一个例子是在几乎不停地变化的元素中使用will-change,如一个元素需要响应鼠标的移动,并在屏幕上跟随鼠标的移动。这种情况,是可以在样式表里面声明will-change值,因为它准确地描述了元素将定期/不断地更改,因此应该对其进行优化。

css 复制代码
.annoying-element-stuck-to-the-mouse-cursor {
	will-change: left, top;
}

will-change属性值

will-change的值为下列4种的其中一种:auto,scroll-position,contents,和。

指定一个或多个你希望更改的属性名。多个属性用逗号隔开。 以下是带有指定属性名称的有效变更声明的示例:

css 复制代码
will-change: transform;
will-change: opacity;
will-change: top, left, bottom, right;

的值不包括will-change,none,all,auto,scroll-position和contents,以及通常从中排除的值。所以,正如我们文章开头提到的: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

相关推荐
BillKu14 小时前
vue3 样式 css、less、scss、sass 的说明
css·less·sass·scss
乖女子@@@16 小时前
css3新增-网格Grid布局
前端·css·css3
Sapphire~19 小时前
重学前端013 --- 响应式网页设计 CSS网格布局
前端·css
二十雨辰20 小时前
歌词滚动效果
前端·css
dllmayday1 天前
FontForge 手工扩展 iconfont.ttf
css·qt
BUG创建者1 天前
html获取16个随机颜色并不重复
css·html·css3
面向星辰1 天前
html中css的四种定位方式
前端·css·html
Async Cipher1 天前
CSS 权重(优先级规则)
前端·css
草字2 天前
css flex布局,设置flex-wrap:wrap换行后,如何保证子节点被内容撑高后,每一行的子节点高度一致。
前端·javascript·css
在芜湖工作的二狗子2 天前
如何用AI Agent提高程序员写作效率
css