详解浏览器渲染原理及性能优化

988 阅读26分钟

写在前面

本文将详细的讲述浏览器渲染原理及性能优化的方法总结。

浏览器功能与组成

浏览器应该具备的功能

  1. 网络服务

    浏览器通过网络模块来下载各式各样的资源,如html文本;javascript代码;样式表;图片;音视频文件等。

    网络部分本质上十分重要,因为它耗时长,而且需要安全访问互联网上的资源。

  2. 资源管理

    从网络下载,或者本地获取到的资源需要有高效的机制来管理它们。例如如何避免重复下载,资源如何缓存等

  3. 网页浏览

    这是浏览器的核心也是最基本的功能,最重要的功能。如何将资源转变为可视化的结果。

    多页面管理 插件与管理 账户和同步 安全机制 开发者工具 ... ...

浏览器的主要功能总结起来就是一句话:将用户输入的url转变成可视化的图像。

  • 从url到DOM树

  • 从DOM树到可视化图像

这两个过程之间的关系并没有那么明确,我们可以统称这两个过程为页面的渲染

浏览器的内核(渲染引擎)

在浏览器中有一个最重要的模块,它主要的作用是将页面转变为可视化的图像结果。

这个模块就是浏览器内核,通常它也被称为渲染引擎。

IE---------->Trident

Safari------>WebKit
	WebKit本身主要是由两个引擎构成的,
		一个正是渲染引擎“WebCore”,
		另一个则是javascript解释引擎“JSCore”,
		它们均是从KDE的渲染引擎KHTML及javascript解释引擎KJS衍生而来。
		
Chrome------>WebKit的分支引擎----->Blink
	 在13年发布的Chrome 28.0.1469.0版本开始,Chrome放弃Chromium引擎转
	 而使用最新的Blink引擎(基于WebKit2——苹果公司于2010年推出的新的WebKit引擎),
	 Blink对比上一代的引擎精简了代码、改善了DOM框架,也提升了安全性。
	 
Opera
	旧版Opera 46版本 :Elektra排版引擎
	Opera7.0	:Presto渲染引擎
	Opera在20132月宣布放弃Presto:
				采用Chromium引擎;
				又转为Blink引擎;
				
Firefox------>Gecko

现代浏览器:多进程、多线程模型

不堪回首的过去:

当你通过浏览器打开很多页面的时候,如果其中一个页面不响应了或者崩溃了,那么随之而来的将会是更不幸的事情,你开打的所有页面都会得不到响应,最让人不能忍受的是,其中的一些页面可能还包含了未保存或者未发送的信息。

浏览器产商如何解决:

1. 采用多进程模型,该模型可以带来的好处
2. 避免因单个页面的不响应或者崩溃影响整个浏览器的稳定性
3. 当第三方插件崩溃时,也不会影响整个浏览器的稳定性
4. 安全

进程与线程

进程

程序的一次执行, 它占有一片独有的内存空间.是操作系统执行的基本单元。

其中:

  1. 一个进程中至少有一个运行的线程: 主线程, 进程启动后自动创建

  2. 一个进程中也可以同时运行多个线程, 我们会说程序是多线程运行的

  3. 一个进程内的数据可以供其中的多个线程直接共享,多个进程之间的数据是不能直接共享的

线程

是进程内的一个独立执行单元,是CPU调度的最小单元,是程序运行的基本单元。

线程池(thread pool): 保存多个线程对象的容器, 实现线程对象的反复利用。

js引擎是单线程运行的!(回忆事件轮询机制)

浏览器中的进程

  1. Browser进程: 浏览器的主进程,负责浏览器界面的显示,和各个页面的管理, 浏览器中所有其他类型进程的祖先,负责其他进程的的创建和销毁 它有且只有一个!!!!!
  2. Renderer进程: 网页渲染进程,负责页面的渲染,可以有多个 当然渲染进程的数量不一定等于你开打网页的个数
  3. 各种插件进程
  4. GPU进程

每个进程内部又有很多线程,多线程的目的主要是保持用户界面的高度响应

例如:

  1. 为了不让Browser进程的UI线程被其他耗时的操作(数据库读写,本地文件读写)所阻塞,那么我们就把这些操作放到分线程中去处理

  2. 在Renderer进程中,为了不让其他操作阻止渲染线程的高速执行,我们通常会将渲染过程管线化,利用计算机的多核优势,让渲染的不同阶段在不同的线程中执行

管线化解释:

CPU的工作方式不是先来先处理(这样你使用一个程序,另一个就不能用了),而是时间片轮转法(每个程序执行一段时间,由于速度快,基本看不出来),同时配置优先级算法。

管线化,就是本来只需要一个Renderer进程,可以浏览器为了抢占资源,开出了几十个或者更多,为了防止别的进程把它挤掉。

移动设备的浏览器可能不太一样:

  1. Android不支持插件,所以就没有插件进程

  2. GPU演化成了Browser进程的一个线程

  3. Renderer进程演化成了操作系统的一个服务进程,它仍然是独立的

浏览器的渲染

主要模块

  • 一个渲染引擎主要包括:HTML解析器,CSS解析器,javascript引擎,布局layout模块,绘图模块

    • HTML解析器:解释HTML文档的解析器,主要作用是将HTML文本解释成DOM树。

    • CSS解析器:它的作用是为DOM中的各个元素对象计算出样式信息,为布局提供基础设施

    • Javascript引擎:使用Javascript代码可以修改网页的内容,也能修改css的信息,javascript引擎能够解释javascript代码,并通过DOM接口和CSS树接口来修改网页内容和样式信息,从而改变渲染的结果。

    • 布局(layout):在DOM创建之后,Webkit需要将其中的元素对象同样式信息结合起来,计算他们的大小位置等布局信息,形成一个能表达这所有信息的内部表示模型

    • 绘图模块(paint):使用图形库将布局计算后的各个网页的节点绘制成图像结果

    Hint:文档对象模型(Document Object Model,简称DOM)

渲染规则

浏览器渲染页面的整个过程:浏览器会从上到下解析文档。

  1. 遇见 HTML 标记,调用HTML解析器解析为对应的 token (一个token就是一个标签文本的序列化)并构建 DOM 树(就是一块内存,保存着tokens,建立它们之间的关系)。
  2. 遇见 style/link 标记 调用解析器 处理 CSS 标记并构建 CSS样式树。
  3. 遇见 script 标记 调用 javascript解析器 处理script标记,绑定事件、修改DOM树/CSS树 等
  4. 将 DOM树 与 CSS树 合并成一个渲染树。
  5. 根据渲染树来渲染,以计算每个节点的几何信息(这一过程需要依赖图形库)。
  6. 将各个节点绘制到屏幕上。

以上这些模块依赖很多其他的基础模块, 包括要使用到网络 存储 2D/3D图像 音频视频解码器 和 图片解码器。

所以渲染引擎中还会包括如何使用这些依赖模块的部分。

渲染过程

02.png

browserRendering.png

浏览器收到html文档后,开始逐行解析,其中遇到style、img等需要引入外部资源的时候,有以下规则:

  1. 浏览器加载资源是异步的,加载一点解析一点
  2. 页面style标签写的内部样式是异步解析的,style标签的内容是由html解析器解析的

但是对于link进来的外部css样式:

  1. link进来的样式,是由css解析器去解析,并且是同步解析的
  2. css解析器会阻塞页面的渲染。(也可以说link进来的外部样式会阻塞页面渲染,利用他避免闪屏)
  3. 推荐使用link方式去引入样式,一来方便管理,二来避免闪屏

EventLoop

JavaScript中,任务被分为两种,一种宏任务(MacroTask)也叫Task,一种叫微任务(MicroTask)。

MacroTask(宏任务)

  • script全部代码、setTimeoutsetIntervalsetImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN)、I/OUI Rendering

MicroTask(微任务)

  • Process.nextTick(Node独有)PromiseObject.observe(废弃)MutationObserver(具体使用方式查看这里

浏览器中的Event Loop

Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。

JS调用栈

JS调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。

同步任务和异步任务

Javascript单线程任务被分为同步任务异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。

v.gif

阻塞渲染

css 阻塞

声明:只有link引入的外部css才能够产生阻塞。

  1. style标签中的样式:

    • html进行解析
    • 不阻塞浏览器渲染(可能会产生“闪屏现象”)
    • 不阻塞DOM解析
  2. link引入的外部css样式(推荐使用的方式)

    • 由CSS解析器进行解析

    • 阻塞浏览器渲染(可以利用这种阻塞避免“闪屏现象”)

    • 阻塞其后面的js语句的执行

      原因: 如果后面js的内容是获取元素的样式,例如宽高等CSS控制的属性, 如果不等样式解析完毕,后面的js就获得了错误的信息。由于浏览器也不知道后续js的具体内容,所以只好等前面所有的样式解析完毕后,再执行js。

      例如:Firefox在样式表加载和解析的过程中,会禁止掉所有的脚本。

      现代浏览越发注重用户体验,对于WebKit内核的浏览器而言,仅当脚本尝试访问的样式属性、或可能受尚未加载的样式表影响时,它才会禁止该脚本。

    • 不阻塞DOM的解析

      原因: DOM解析和CSS解析是两个并行的进程,浏览器解析DOM生成DOM Tree, 解析cSS生成CSS Tree, 最终组成render Tree,再渲染页面

      即: DOM的解析,ICSS的解析是 并行执行的,即:不阻塞DOM的解析。

  3. 优化核心理念:尽可能快的提高外部css加载速度

    • 使用CDN节点进行外部资源加速
    • 对css进行压缩(利用打包工具,比如webpack,gulp等)
    • 减少http请求数,将多个css文件合并
    • 优化样式表的代码

关于js阻塞

  1. 阻塞DOM解析

    原因:浏览器不知道后续脚本的内容,如果先去解析了下面的DOM,而随后的js删除了后面所有的DOM,那么浏览器就做了无用功,浏览器无法预估脚本里面具体做了什么操作,例如像document.write这种操作,索性全部停住,等脚本执行完了,浏览器再继续向下解析DOM。

  2. 阻塞页面渲染

    原因:js中也可以给DOM设置样式,浏览器同样等该脚本执行完毕,再继续干活,避免做无用功。

  3. 阻塞后续js的执行

    原因:维护依赖关系,例如:必须先引入jQuery再引入bootstrap

写在后面

  1. css的解析和js的执行是互斥的(互相排斥),css解析的时候js停止执行,js执行的时候css停止解析。

  2. 无论css阻塞,还是js阻塞,都不会阻塞浏览器加载外部资源(图片、视频、样式、脚本等)

    原因:浏览器始终处于一种:“先把请求发出去”的工作模式,只要是涉及到网络请求的内容,无论是:图片、

    样式、脚本,都会先发送请求去获取资源,至于资源到本地之后什么时候用,由浏览器自己协调。这种做法效率很高

  3. WebKit 和 Firefox 都进行了【预解析】这项优化。在执行js脚本时,浏览器的其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源。通过这种方式,资源可以在并行连接上加载,从而提高总体速度。请注意,预解析器不会修改 DOM 树。

在上述的过程中,网页在加载和渲染过程中会触发“DOMContentloaded”和“onload”事件分别是在DOM树构建(解析)完成之后,以及DOM树构建完并且网页所依赖的资源都加载完之后。

上面介绍的是一个完整的渲染过程,但现代网页很多都是动态的,这意味着在渲染完成之后,由于网页的动画或者用户的交互,浏览器其实一直在不停地重复执行渲染过程(重绘重排),以上的数字表示的是基本顺序,这不是严格一致的,这个过程可能重复也可能交叉。

图层与重绘重排

css图层与浏览器绘制

浏览器在渲染一个页面时,会将页面分为很多个图层,图层有大有小,每个图层上有一个或多个节点。

在渲染DOM的时候,浏览器所做的工作实际上是:

  1. 获取DOM后分割为多个图层

  2. 对每个图层的节点计算样式结果 (Recalculate style--样式重计算)

  3. 为每个节点生成图形和位置 (Layout--重排,回流)

  4. 将每个节点绘制填充到图层位图中 (Paint--重绘)

  5. 图层作为纹理上传至GPU

  6. 组合多个图层到页面上生成最终屏幕图像 (Composite Layers--图层重组)

03.jpg

图层创建的条件

Chrome浏览器满足以下任意情况就会创建图层:

  1. 拥有具有3D变换的CSS属性
  2. 使用加速视频解码的video节点
  3. canvas节点
  4. CSS3动画的节点
  5. 拥有CSS加速属性的元素(will-change)

重绘(Repaint)

重绘是一个元素外观的改变所触发的浏览器行为,例如改变outline、背景色等属性。

浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。重绘不会带来重新布局,所以并不一定伴随重排。

需要注意的是:

重绘是以图层为单位,如果图层中某个元素需要重绘,那么整个图层都需要重绘。比如一个图层包含很多节点,其中有个gif图,gif图的每一帧,都会重回整个图层的其他节点,然后生成最终的图层位图。

所以这需要通过特殊的方式来强制gif图属于自己一个图层(translateZ(0)或者translate3d(0,0,0)

CSS3的动画也是一样(好在绝大部分情况浏览器自己会为CSS3动画的节点创建图层)

重排(Reflow 回流)

渲染对象在创建完成并添加到渲染树时,并不包含位置和大小信息,计算这些值的过程称为布局或重排

"重绘"不一定需要"重排",比如改变某个网页元素的颜色,就只会触发"重绘",不会触发"重排",因为布局没有改变。

但是,"重排"必然导致"重绘",比如改变一个网页元素的位置,就会同时触发"重排"和"重绘",因为布局改变了。

触发重绘的属性

    * color								
    * background								
    * outline-color
    * border-style						
    * background-image								
    * outline
    * border-radius						
    * background-position							
    * outline-style
    * visibility						
    * background-repeat								
    * outline-width
    * text-decoration					
    * background-size								
    * box-shadow

触发重排(回流)的属性

盒子模型相关属性会触发重布局,定位属性及浮动也会触发重布局,改变节点内部文字结构也会触发重布局:
		* width							* top											* text-align
		* height						* bottom										* overflow-y
		* padding						* left											* font-weight
		* margin						* right											* overflow
		* display						* position										* font-family
		* border-width					* float											* line-height
		* border							* clear											* vertival-align
		* min-height																		* white-space

常见的触发重排的操作

Reflow 的成本比 Repaint 的成本高得多的多。

DOM Tree 里的每个结点都会有 reflow 方法,一个结点的 reflow 很有可能导致子结点,甚至父点以及同级结点的 reflow。

在一些高性能的电脑上也许还没什么,但是如果 reflow 发生在手机上,那么这个过程是非常痛苦和耗电的。

所以,下面这些动作有很大可能会是成本比较高的:

1. 当你增加、删除、修改 DOM 结点时,会导致 Reflow , Repaint。
2. 当你 Resize 窗口的时候(移动端没有这个问题,因为移动端的缩放没有影响布局视口)
3. 当你修改网页的默认字体时。	
4. 获取某些属性时(width,height...),注:display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发生位置变化。
5. 当你移动 DOM 的位置
6. 当你修改 CSS 样式的时候。

优化方案

如果我们需要使得动画或其他节点渲染的性能提高,需要做的就是减少浏览器在运行时所需要做的工作(尽量减少1234步):

1. 计算需要被加载到节点上的样式结果(Recalculate style--样式重计算)
2. 为每个节点生成图形和位置(Layout--回流和重布局)
3. 将每个节点填充到图层中(Paint Setup和Paint--重绘)
4. 组合图层到页面上(Composite Layers--图层重组)

一些解决方法:

  1. 元素位置移动变换时尽量使用CSS3的transform来代替对top left等的操作, 可以减少重绘重排的次数

    变换(transform)和透明度(opacity)的改变仅仅影响图层的组合

  2. 使用opacity来代替visibility (1) 使用visibility不触发重排,但是依然重绘。 (2) 直接使用opacity即触发重绘,又触发重排(GPU底层设计如此!)。 (3) opacity配合图层使用,即不触发重绘也不触发重排。 原因: 透明度的改变时,GPU在绘画时只是简单的降低之前已经画好的纹理的alpha值来达到效果,并不需要整体的重绘。 不过这个前提是这个被修改opacity本身必须是一个图层。

  3. 不要使用table布局 table-cell

  4. 将多次改变样式属性的操作合并成一次操作 不要一条一条地修改DOM的样式,预先定义好class,然后修改DOM的className

  5. 将DOM离线后再修改 由于display属性为none的元素不在渲染树中,对隐藏的元素操作不会引发其他元素的重排。 如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发2次重排。

  6. 利用文档碎片(documentFragment)------vue使用了该种方式提升性能。

  7. 不要把获取某些DOM节点的属性值放在一个循环里当成循环的变量

    当你请求向浏览器请求一些 style信息的时候,就会让浏览器flush队列,比如:

    • offsetTop, offsetLeft, offsetWidth, offsetHeight

    • scrollTop/Left/Width/Height

    • clientTop/Left/Width/Height

    • width,height

    当你请求上面的一些属性的时候,浏览器为了给你最精确的值,需要刷新内部队列,因为队列中可能会有影响到这些值的操作。即使你获取元素的布局和样式信息跟最近发生或改变的布局信息无关,浏览器都会强行刷新渲染队列。

  8. 动画实现过程中,启用GPU硬件加速:transform: tranlateZ(0)

  9. 为动画元素新建图层,提高动画元素的z-index,z-index会提升图层位置(注意,脱离文档流并不代表开启了一个图层)

请求动画帧

window.requestAnimationFrame()
 
window.requestAnimationFrame() 
// 说明:该方法会告诉浏览器在重绘之前调用你所指定的函数
// 1.参数:该方法使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。
// 回调函数会被自动传入一个参数,DOMHighResTimeStamp,标识requestAnimationFrame()开始触发回调函数的当前时间
// 2.返回值:
// 一个long整数,也成为请求 ID,是个非零值 ,是回调列表中唯一的标识,没别的意义。
 
window.cancelAnimationFrame(requestID)


// 用此方法代替定时器实现移动
let i = 0;

let id = window.requestAnimationFrame(move)

function move() {
    i++;
    document.getElementById('test').style.transform = `tanslateX(${i}px)`
    id = window.requestAnimationFrame(move)
}

setTimeout( () => {
    window.cancelAnimationFrame(id)
}, 2000)

取消一个先前通过调用window.requestAnimationFrame()方法添加到计划中的动画帧请求。

requestID是先前调用window.requestAnimationFrame()方法时返回的ID

硬件加速

硬件加速意味着Graphics Processing Unit(GPU)会通过代替Central Processing Unit (CPU)做一些负荷比较大的事情,来协助浏览器快速渲染页面,当CSS操作使用硬件加速的时候,通常会使页面渲染速度加快

顾名思义,CPU和GPU都是计算机处理单元。CPU在电脑主板,几乎处理电脑的一切操作,有电脑大脑之称;GPU在显卡上,负责处理和渲染图形。此外GPU通过特殊的设计,使其擅长于渲染图形所需的数学和几何运算。因此把操作转嫁到GPU可以获得显著的性能提升,同时也可以减少移动设备CPU的争用。

硬件加速(或者说GPU加速)依赖于浏览器渲染页面使用的layering model,当特定的操作(CSS 3D变形)作用于页面上的一个元素,元素移动到它自己的layer,在这个layer中元素合一不受页面其他元素的干扰独立渲染,然后复合到页面中去。在这种隔离内容渲染的工作方式下,如果页面的变化仅仅是该元素的变形,其余部分不必被重新渲染,这会带来显著的速度优势。值得注意的是只有3D变形会有自己的layer,2D变形不会。

CSS的动画、变形、渐变并不会自动的触发GPU加速,而是使用浏览器稍慢的软件渲染引擎。然而一些浏览器提供了hardware acceleration by means of certain properties来获取更高的渲染性能。 举个例子,opacity属性是几个能够加速的属性之一,因为GPU可以方便的处理。基本上任何层的透明度渐变浏览器都会交给GPU处理来加速。除了opacity能够使用GPU处理的就是CSS 3D变形了

translateZ() (or translate3d()) Hack

很长一段时间内我们都通过translateZ()或者translate3d() hack来骗取浏览器触发硬件加速,具体做法就是为元素添加没有变化的3D变形,比如元素在2维空间可以通过添加以下CSS来硬件加速

transform: translate3d(0, 0, 0);

所谓硬件加速就是创建了一个被传递到GPU处理的层的操作,然而强制使用hack方式创建layer并不是长久之计,创建layer的技术可以使页面加速,但是也有代价:它们占用RAM和GPU存储空间(考虑到移动设备的存储容量有限),所以必须呗小心使用,确保这么做真的对页面渲染有所帮助

为了避免创建layer的hacks,一个允许我们提前通知浏览器我们将对元素做何种变化的CSS属性被引入,这样浏览器可以优化处理元素渲染的方式,为元素提前准备昂贵的动画处理操作,这就是wiil-change属性

will-change属性

will-change属性可以提前通知浏览器我们要对元素做什么动画,这样浏览器可以提前准备合适的优化设置。这样可以避免对页面响应速度有重要影响的昂贵成本。元素可以更快的被改变,渲染的也更快,这样页面可以快速更新,表现的更加流畅。

举个例子,当对于素使用 CSS 3D变形时,元素及其内容可以在合成到页面之前被创建到我们之前说的layer。然而把元素放到layer中是个昂贵的操作,这将会导致变形动画延迟一个课件的瞬间,也就是flicker

为了避免这种延时,我们可以在发生之前通知浏览器,这样浏览器会有一定的时间去准备这些变化,当发生的时候layer已经准备好了,这样动画酒会很流畅,不会闪屏

使用will-change提示浏览器关于即将发生的变形十分简单,添加个CSS属性就行

will-change: transform;

也可以告诉浏览器要改变元素的滚动条位置,或者多个要变化的属性,写下属性的名字就行,也可以写多个,逗号隔开

will-change: transform, opacity;

声明了元素即将进行的变化会让浏览器在渲染页面时做更好的决定,这明显比之前说的3D hacks要好。

合理使用

了解了will-change的行为,为浏览器上一切元素设置will-change是不是效率会变高?答案是否定的,will-change如果被滥用会使页面崩溃。

will-change也有副作用,虽然并不直接可见,毕竟它只是在背后和浏览器说悄悄话,为了合理使用will-change,

不要声明太多属性或为太多元素声明

::before,
::after {
    will-change: all;
}

虽然看起来很屌,但其实对页面渲染伤害很大,这样的规则设了和没设没什么区别,浏览器本来就尝试最优的渲染所有元素,就等于你让老师重点照顾班里每个同学一样,就是废话!

其实这甚至是有害的,因为一些操作会占用太多的资源,甚至会导致页面奔溃,就等于强制要求老师为每个学生补课,累死了。。。

给浏览器足够的时间工作

will-change顾名思义,通知浏览器即将发生的变化,而不是正在发生的变化。使用will-change,我们要求浏览器重点照顾我们声明的元素,为了这个浏览器需要一定的时间来组织优化操作,这样当变化发生的时候,优化才能没有延迟的作用到元素

在变化前立即为元素添加will-change几乎没有作用,可能还不如不设置,因为会导致新的layer创建

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

这样的设置就没什么用,我们需要给浏览器足够的时间,下面这样就是有用的,感受一下

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

如果一定要hover的时候,也有技巧

.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会导致该优化迟迟不能释放,这就要求我们用完了就释放

这时候我们需要借助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';
}

当然对于用户会反复触发的操作放在style中不移除也可以

will-change属性的值

  1. auto 表示没有明确的意图; 无论是启发式和最优化,用户代理应该应用都和正常情况相同

  2. scroll-position 表示开发者期望去在接下来去改变或者有动画应用元素的滚动位置

  3. contents 表示开发者期望去在接下来去改变或者有动画应用元素的内容

  4. 用来排除关键字 will-change, none, all, auto, scroll-position, and contents, 从之外增加一些通用的关键字

    will-change: transform: will-change: opacity: will-change: top, left, bottom, right:

如果一个属性无最初的值,在这个元素上这个属性将创建一个堆栈的内容, 明确规定在will-change的属性必须在这个元素上创建一个堆栈的内容.

如果一个属性无最初的值, 这个属性将造成这个元素产生一个包含区块的固定定位的元素, 明确规定在 will-change的属性必须造成这个元素产生一个包含区块的固定定位的元素。

总结

看到这里,想必你已经对浏览器渲染原理及性能优化有了自己的理解和感悟。

前端之路不易,请带着坚持与热情继续前进吧!

PS:如果您觉得我的文章对您有帮助,请多多点赞支持!同时非常欢迎您关注我的 Github!