关于CSS“可变属性”(will-change),你需要知道所有的事情

原文: Everything You Need to Know About the CSS will-change Property by Sara Soueidan

此文在CC-By 3.0 许可下使用 


简介

如果你在执行CSS某些特定的操作时,注意到基于WebKit的浏览器上的“闪光”,特别是CSS变换和动画,那么你很有可能在以前遇到过“硬件加速”这个词。


CPU、GPU和硬件加速

简而言之, 硬件加速就是图形处理单元(GPU)将会协助您的浏览器在呈现页面方面做一些繁重的工作,而不是完全把它抛给中央处理单元(CPU)去做 。当一个CSS操作处于硬件加速状态时,它通常会像页面呈现变快一样得到速度的提升。

正如他们的名字所显示的,CPU和GPU都是处理单元。CPU位于电脑的主板,它负责处理几乎所有一切,并被称为为计算机的大脑。GPU位于电脑的显卡,它负责处理和绘制图形。此外,GPU专门负责执行绘制图形所需的复杂的数学和几何云端。因此,GPU的卸载操作能够获得巨大的性能,并且能减少移动设备上CPU的争用。

硬件加速(也称为GPU 加速) 依赖于浏览器使用的分层模型,因为它能够呈现页面。当特定的操作(如3D变换)在一个页面上的元素进行执行时,该元素就会被转移到它自己的“层”,这里可以独立于其他的页面进行呈现,并且在稍后进行合成(绘制到屏幕上)。这些都与其所呈现的内容隔离,所以,当如果元素的转变是两帧之间唯一的事情时,其他页面则不必重新呈现,并且会带来非常明显的速度优势。在这里指的一提的是,只有3D变换会转移到它自己的层,2D的则不会。

CSS 动画、变换和过渡并不是GPU自动加速,也不是从浏览器较慢的软件所提供的引擎进行执行。然而, 很多浏览器通过某些特定的属性进行硬件加速,从而提供更好的渲染性能。例如,透明度属性是CSS为数不多的属性之一,由于GPU较为易于进行操作,透明度属性能适当得以加速。基本上,任何你想通过CSS过渡或动画消除透明度的层,浏览器实际上是足够聪明地将它抛到GPU上,并且在那进行操作,而且,这将非常快。在CSS的所有属性中,透明度是最高性能之一,在使用它时,你不会有任何问题。另一个常见的硬件加速操作是CSS 3D变换。


旧的:translateZ()(或者translate3d())黑客

相当一段时间以来,我们一直在使用被称为translateZ()或者(translate3d())的黑客(有时也被称为空变换黑客)去欺骗浏览器,从而推动我们的动画和变换形成硬件加速。我们一直在通过向一个元素中加入简单的3D转型来实现硬件加速,而且这不会在3D空间中进行变换。例如,一个元素在二维空间的动画可以通过添加这个简单的规则得到硬件加速。

transformtranslate3d(0, 0, 0)

硬件加速操作创建了众所周知的文字编排层,它是由GPU上传并合成的。然而,黑客图层的创建并不能总是解决某些页面上的性能瓶颈。图层创建技术可以提升网页速度,但他们却有成本:它们在系统和GPU(特别是在移动设备上)都占据内存,并且它们大多数都会产生较坏的影响(特别是在移动设备上),所以,它们必须被明智地使用,你需要确保硬件加速操作能够真正改善页面的性能,而且该性能瓶颈不会因你网页上的其他操作而引起。

为了避免图层创建黑客,CSS的一个新性能被引进,它能允许我们提前通知浏览器我们可能会对一个元素做出何种改变,从而使它得以优化知道如何提前处理该元素,为像动画这样的操作执行潜在的昂贵工作准备,例如在动画真正开始之前。该性能是一项新的“可变属性”。


新属性:卓越“可变属性”

该“可变属性”可以允许我们提前通知浏览器我们可能会对一个元素做出何种改变,以便在它们需要前设置适当的优化措施,因此也避免的不菲的启动成本,该成本对页面的响应速度有一定的消极影响。我们可以改变元素并加快速度,网页则可以得到明显,最终会让用户有更顺畅的体验。

例如,当我们在一个元素上使用CSS 3D变换时,在它们稍后的合成(绘制到屏幕上)之前,该元素及其内容会被提升到一个层面,就像我们之前提到的一样。然而,在新的一层上设置元素是一个相对昂贵的操作,这会在几秒内非常明显的延迟变换动画的启动,并会引起明显的“闪烁灯”。

为避免这种延迟的发生,你可以在他们真正发生前提前通知浏览器会有这些变化。这样,它就会有时间做好准备以应对这些变化,所以,当这些变化发生时,元素层则会做好准备,变换动画也能得到执行,元素也可以得以呈现,最终网页则能更快地得到更新。

使用“可变属性”, 暗示浏览器即将到来的转型可以跟向元素添加这条规则一样简单,而该元素正是你所期望进行变换的:

will-changetransform

你也可以向浏览器说明你想改变元素滚动位置(元素的位置在可见的滚动窗口,它有多少在那个窗口是可以看到的)的意图,它的内容,或者是一个或更多个CSS属性值,而这些时通过指定那些你期望改变的属性名称而实现的。如果你期望或者是计划去改变元素的多种数值/方面,你可以提供一个以逗号来分隔数值的列表。例如,如果你想让这个元素进行动画和移动(改变它的位置),你可以像这样跟浏览器进行说明:

will-change transform, opacity

指定你明确想要改变的部分可以使浏览器对优化设置作出更好的选择,该优化需要为特定的变化而服务。很明显的是,这是一种无需通过黑客而实现提速的好方法,并且能迫使浏览器进入图层创建,这些可能是有用或者有必要的,但也可能是没用或者没必要的。


“可变属性”会影响除暗示浏览器以外的应用元素吗?

答案是肯定还是否定—这取决于你向浏览器指定和通知的属性。如果属性的任何非初始值在元素上创建堆叠环境,请指明“可变属性”将会在该元素上创建的堆叠环境。

例如,当它们使用数值而不是初始数值时,夹路径属性和透明度属性都会导致元素上堆叠环境的建立。因此,“可变属性”使用这些性能中的一个(或两个)作为数值,都会创建元素上的堆叠环境,甚至在这个改变真正发生之前。这同样适用于其他属性,那会创建一个元素上的堆叠环境。

同样地,一些属性会导致固定位置元素的包含块的建立。例如,转化的元素会为它所有子代创建一个包含块,包含块即使对那些已定位的子代元素亦有效。这样一来,如果某个元素属性触发建立包含块,为这个不定属性赋值也会导致针对已定位元素的包含块的创建。

再者,元素可变属性对元素本身无直接影响,只是会提示浏览器对元素可能发生的变化进行优化。上述情况下,如果元素在创建的堆栈上下文及包含块之外,元素也不会受到直接影响。


使用“可变属性:应该和不该做的事

了解了可变属性”能做什么,很容易会认为:仅仅拥有浏览器就可以优化一切!。我这样说是有意义的,对吗?谁不希望他们所有的更改部分都得以优化并做好紧随需求的准备呢?

与其他类型的能量相比,它并不是完全不同的, 它和“可变属性一样强大和伟大,所以,正如其他能量的来源一样---源于责任。我们应该明智地使用”可变属性”,否则将会导致页面性能冲击,实际上更有可能会使页面崩溃。

与任何性能提示一样,“可变属性有其副效应,并不能被直接检测(毕竟,这只是一种在幕后跟浏览器对话的方式),所以它在使用过程中可能会很棘手。当你使用这个属性时,这里有一些事情要记住,以确保你得到最好服务的同时避免因为使用不当带来的损失。


切忌使用 “可变属性” 进行过多的属性及元素更改

正如我前面所述,要求浏览器来优化所有元素所有属性的更改将非常有意思;因此将下列规则添加至我们的样式表首先会有意义:

*,

*::before,

*::after {

  “will-change”all

}

看起来很好(我也知道看起来很好,且对我有意义),实际上有害,而且还无效。不仅是关键字“可变属性”的数值无效(我们将在稍后的条款中讲述有效值与无效值列表),总括规则也无效。您可以看到,浏览器已尽力对所有元件进行优化(记住不透明或3D变换?),因此明确地告诉它这样做根本无法更改任何事情,也不会奏效。事实上,如此操作会对容量造成严重损坏。因为这些优化可能通过机器终止“可变属性” ,过度使用时,会使页面的翻转速度下降,甚至破坏。

换言之,提高浏览器对这些可能出现或不出现的变化的警惕是一个坏的想法,且会对产品造成更大的损坏。请不要这么做。


为浏览器留出足够的运行时间

“可变属性”之所以以此命名,原因很明显:告诉浏览器将会发生的变化,而不是利用“可变属性”改变那些正在发生的变化。利用“可变属性”功能,针对我们已经做出的声明(对某个元素做出的改变),我们要求浏览器对这些改变做出确切的优化方案,并且,为了让这种情况顺利进行,浏览器需要一些时间针对不同情况去做相应的优化,以便当变化发生时,这些优化方案可以没有任何延迟的被应用。

在改变前将可变性直接设置到某个元素上几乎没有影响。 (甚至可能比不设置它还要糟糕。你可以增加一个新图层的成本当你的原有动画无法胜任新出现图层),举例来说,如果悬停将要发生某些改变,那么是这样的:

.element:hover {

  will-change transform

  transition transform 2s

  transformrotate(30deg) scale(1.5)

}

让浏览器对于已经做出的更新进行优化,这不仅没用,而且有点忽略“可变属性”背后的整体概念。相反,你应该找到至少一种方法来稍微提前预测一下将会发生变化的属性,并设置“可变属性”。

例如,单击时,如果一种元素发生变化,然后当这一元素悬停时,给浏览器足够的时间去优化这一更改,再设置“可变属性”。悬停元素和使用者实际点击之间的时间范围足够让浏览器去优化设置。因为人类的反应时间相对较慢,所以这会给周围200毫秒时窗范围内的浏览器,在实际发生变化之前,有足够的时间来优化设置。

.element {

  /* style rules */

  transition transform 1s ease-out

}

.element:hover {

  will-change transform

}

.element:active {

  transformrotateY(180deg)

}

但是,如果你希望改变发生在悬停过程而不是点击过程呢?我们上面提到的声明将是无用的。这种情况下,在改变动作发生之前,仍然可以找到一些方法来预测。例如,通过悬停上级变化元素可能提供足够的前置时间:

 

.element {

  transition opacity .3s linear

}

/* declare changes on the element when the mouseenters / hovers its ancestor */

.ancestor:hover .element {

  will-change opacity

}

/* apply change when element is hovered */

.element:hover {

  opacity .5

}

然而,悬停上级元素的变化并不总是代表元素可以按照理想的方式运行,所以,在您的应用程序中出现一个活跃的视图或者元素处于窗口的可见部分时,你可以设置一下“可变属性”,其中元素处于窗口的可见部分增加了元素之间相互关联的机会。


更改完成后删除所有“可变属性”

对于即将发生的变化,对浏览器的优化通常是昂贵的。正如我们前面所提到的,可能占用大量的计算机资源。通常,对于浏览器设置的优化是删除这些优化设置,并尽快恢复到之前的正常设置。但是,“可变属性”为了维持比浏览器更长时间的优化设置,会覆盖这一设置。

因此,我们应该记住,在元素更改完成之后删除“可变属性”。这样浏览器就可以恢复对资源的任何优化设置。

如果格式表中已声明“可变属性”,那么是不可能删除的。这就是为什么它通常建议你在使用JavaScript语言时,设置和复原。通过脚本,你可以宣布对浏览器作出的更改,然后在得知这些更改完成后,删除“可变属性”。比方说,就像我们在上一节中的样式规则一样,你可以通过收听元素(或其上级元素)何时悬停,然后在鼠标移入上设置“可变属性”。如果是动画元素,你可以通过倾听动画何时结束,来使用DOM事件结束动画。一旦动画结束完成后,即可删除“可变属性”。

// Rough generic example

// Get the element that is going to be animated onclick, for example

var el = document.getElementById('element'):

 

// Set will-change when the element is hovered

el.addEventListener('mouseenter', hintBrowser):

el.addEventListener('animationEnd', removeHint):

 

functionhintBrowser(){

  // The optimizable properties that are going to change

  // in the animation's keyframes block

  this.style.willChange = 'transform, opacity'

}

 

functionremoveHint(){

  this.style.willChange = 'auto'

}

Craig Buckler曾写过一篇文章,是关于捕捉JavaScript的CSS动画的事件。如果你不是很熟悉的话,应当去看看这篇文章。还有关于控制CSS动画和转变技巧的文章,也是值得一读的。


在样式表中合理使用“可变属性”

正如前一章节所讲,“可变属性”可以提示浏览器某个元素在几毫秒内将要发生的变化。这是其中一个实例,说明在样式表中可以使用“可变属性”。尽管有人推荐使用JavaScript设置和取消“可变属性”,也有一些例子表明,在样式表中设置(保留)“可变属性”也是可以的。

其中一个例子是,在一部分可能与用户进行重复地相互作用的元素上设置“可变属性”,它们应很迅速的响应用户的交互。这部分元素是指,浏览器所作的优化不会被过度使用,因此,也不会被严重损坏。例如,当用户发出请求时,通过滑动来改变一个侧边栏。下面的规则就印证了这一事例。

.sidebar {

  will-change transform

}

另一个例子是,在不断变化的元素上使用“可变属性”。比如某个响应用户鼠标移动的元素,会随着鼠标的移动而在屏幕上移动。这种情况仅仅说明可变数值在样式表中是合适的,因为他精确地描述了该元素会不断发生变化,而且也应该保持优化。

.annoying-element-stuck-to-the-mouse-cursor {

  will-change left, top

}


“可变属性”值

“可变属性”,有以下四种可能的值:自动、滚动位置、内容和自定义。

自定义值是用来详细说明你想要改变的一个或多个属性,多重属性需用逗号分开。下面几个例子,都是有效的“可变属性”,并有详细的属性名称。

will-change transform

will-change opacity

will-change top, left, bottom, right

除关键字外,其他<自定义的标识符>的值都会变化,无、全部、自动、滚动位置和内容等等,除了从<自定义的标识符>正常排除的关键字上述都可改变。因此,正如我们在文章开头提到的,可变性:所有的声明是无效的,因此将被浏览器忽略。

该值的自动指示没有特别的意图,即,浏览器将不会对其通常体现的用途或者特征进行其他任何特殊优化。

正如其名称所暗示的,滚动位置值表示,在任何时间,您都可以改变一个元素的滚动位置。这个值是非常有用的,这是因为,当使用时,浏览器可将超出的部分用这个位置值准备和呈现在滚动窗口的一个可滚动元件中。浏览器通常只呈现在滚动窗口中的内容,以及一些经过(漂浮在)窗口的内容,对制作的滚动效果好看与跳过呈现所需的内存和时间节省进行平衡。使用可变性:滚动位置,它可以作进一步的呈现优化,使得较长的和/或更快的内容滚动可顺利地进行。

内容值表示该元素的含量,也是可以按照预期改变。浏览器通常“缓存”渲染元素,因为大多数情况通常不改变,或者只是改变原来的位置。这个值将仅被浏览器理解为一个信号,同时针对该元素较少的缓存,或完全避免该元素缓存,这是因为如果元素的内容经常变化,那么保持内容的缓存将是无用的,也是一种时间的浪费。因此浏览器将只需暂停缓存,并且只要元素内容出现变化就继续从头开始进行渲染或者显示。

正如之前提到的,指定可变时,某些属性就会失效,因为在大多数属性的变更上,浏览器并不体现任何的专门优化。它只是失效了--尽管指定这些属性仍然是安全的。其它属性可能会导致堆叠环境(模糊、剪切路径等)及/或包含组件的创建。


支持浏览器

撰写本文时,只有Chrome Canary36+,Opera Developer23+及Firefox Nightly支持“可变属性”。将其运送至稳定河槽是有意为之。可以说,过不了多久,所有现代浏览器都会支持该属性。


结语

“可变属性”是一种力量,可辅助我们编写无黑客、可优化运行代码,强调CSS操作速度及性能的重要性。不过,像其他事物一样,力量越大,责任越重,“可变属性”是不可小觑的属性之一,应合理利用。谈到这点,我想引用“可变属性”说明编辑Tab Atkins Jr的话:

将属性赋予可变性,你会在那些变化中的元素上发生实质的改变,并在其停止时转移。

感谢您的阅读!

非常感谢Paul Lewis的评论与反馈、Tab Atkins的支持与回应、BruceLawson及Mathias Bynens对本文的评论。


在JavaScript中修改CSS样式通常会触发浏览器的重绘过程,因为页面渲染需要更新DOM和CSSOM树。这是浏览器为了保持UI一致性而采取的一种标准机制。然而,有一些技术可以帮助减少不必要的重绘次数,提高性能: 1. **使用`requestAnimationFrame`**: 当你想对CSS进行频繁更新时,推荐使用`requestAnimationFrame`而不是直接修改样式。这会在下一次浏览器重绘之前执行回调,减少了不必要的重绘。 ```javascript function updateStyle() { // 修改CSS requestAnimationFrame(updateStyle); } updateStyle(); ``` 2. **`will-change`属性**: 可以预设一个元素将要改变的状态,例如颜色、位置等,可以让浏览器预先准备并优化渲染,降低重绘频率。 ```css .element { will-change: transform; } ``` 3. **渐变或动画优化**: 避免在短时间内大量改变CSS的`transform`或`opacity`,可以使用CSS3的`transition`或`animation`来平滑过渡,减少突变造成的重绘。 4. **避免直接修改`style`属性**: 相对于设置内联样式(`element.style.property = value`),更推荐使用`classList`或`style.setProperty()`方法,它们可能导致更少的重绘。 5. **使用CSS OM API**: 如果可能,尽量利用如`MutationObserver`来监听并批量处理CSS变化,而不是逐条修改。 虽然上述策略能够帮助减少重绘,但在某些场景下,如响应式设计或者实时数据交互,重绘可能是不可避免的。理解何时以及为何发生重绘很重要,以便权衡性能与用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值