JS的回流和重绘

JavaScript06

JS的回流和重绘,第1张

一、首先我们简单了解下浏览器的渲染过程:

1.解析HTML抽象DOM Tree

2.抽象出Render Tree

3.布局(layout)render tree

4.绘画render tree

HTML解析成DOM,抽象DOM Tree

解析html文档并将解析的元素转换为dom树中的实际dom节点。

把CSS解析成CSSOM,抽象CSSOM Tree

当浏览器解析dom的时候,遇到link标签,引用外部的css样式表,引擎会将css抽象成cssom

构建渲染树(Render Tree)

DOM和CSSOM合并就产生了Render Tree。构建渲染树大致步骤:

①从dom树的根开始,遍历每个可见节点。

②对于每个可见节点,浏览器找到适当的匹配cssom规则并应用它们。

③它会发布带有内容和计算样式的可见节点。

④每个渲染器代表一个矩形区域,通常对应于一个节点的CSS框。

渲染树的布局

①当渲染器被创建并添加到树中时,它没有位置和大小。计算这些值称为布局。

②布局是一个递归过程,从根元素开始,也就是html,每个渲染器都会去计算他自己的位置和大小,由于浏览器使用流式布局,对Render Tree的计算通常只需要遍历一次就可以完成,但table及其内部元素除外,他们可能需要多次计算,通常要花3倍于同等元素的时间,这也是为什么要避免使用table布局的原因之一。

绘制渲染树

遍历渲染器树,调用渲染器的paint()方法,然后展示。

二、回流和重绘(reflow和repaint)

1. 理解回流和重绘

回流: 当元素的结构、位置、尺寸或某些属性发生改变时,浏览器需要重新计算样式和渲染树;

重绘:当元素发生的改变只影响了节点的一些样式(背景色,边框颜色,文字颜色等),浏览器只需要将新样式赋予给这个元素并重绘它。

需要注意的是, 回流必将引起重绘,重绘不一定会引起回流。

2.如何触发回流

(1)页面首次渲染

(2)DOM树变化(如:增删可见dom节点)

(3)浏览器窗口resize

(4)元素尺寸或位置发生改变

(5)查询布局信息,包括offsetLeft、offsetTop、offsetWidth、offsetHeight、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height、调用了getComputedStyle()或者IE的currentStyle时,浏览器为了返回最新值,会触发回流。

3. 如何触发重绘

背景色、颜色、字体改变,(例如:color、background-color、visibility等,注意:字体大小发生变化时,会触发回流)

三、可减少回流和重绘,优化渲染性能

1.尽量避免改变和多次读取布局属性。如width, height, left, top。

2.除了transforms 或者 opacity属性都会引起重绘,做动画的时候要注意,尽量使用这两个属性;

3.将复杂的节点元素脱离文档流,降低回流成本。

4.缓存布局信息

5.使元素进行动画效果的时候脱离文档流,可先绝对定位,设置好后再恢复。

部分参考:https://segmentfault.com/a/1190000014474575?from=singlemessage&isappinstalled=0

一个页面渲染完毕后,随着用户的操作或请求数据的变化,都会导致网页的重新渲染。根据不同的触发条件,重新渲染分为重排(reflow)和重绘(repaint)两种情况。

重绘 :当一个元素视觉表现属性改变时,会触发重绘。例如元素背景颜色的改变、字体颜色的改变、边框颜色的改变、透明度的改变等。

重排 :当渲染树的一部分或全部更新而导致网页结构或节点尺寸发生改变时,都会导致重排。例如可见元素节点的添加和删除、改变元素的尺寸(元素宽、高、内边距大小、边框大小)、浏览器窗口大小发生改变等。

减少页面重绘与重排的方法:

1、尽可能将css属性的读、写分开进行。

2、尽量不要对style或class中的属性进行一条一条的更改,尽量使用 class或csstext 进行批量更改。

3、不要使用table布局。因为可能一个很小的改变而导致整个table重新布局。

重绘和重排的注意点 :

1、当页面发生重排时一定会导致重绘,而重绘不一定导致重排。例如: 透明度发生改变时会导致重绘而不会导致重排。

2、不可见的元素不会影响重绘与重排。

移动互联网时代,用户对于网页的打开速度要求越来越高。首屏作为直面用户的第一屏,其重要性不言而喻。优化用户体验更是我们前端开发非常需要 focus 的东西之一。

从用户的角度而言,当打开一个网页,往往关心的是从输入完网页地址后到最后展现完整页面这个过程需要的时间,这个时间越短,用户体验越好。所以作为网页的开发者,就从输入url到页面渲染呈现这个过程中去提升网页的性能。

所以输入URL后发生了什么呢?在浏览器中输入url会经历域名解析、建立TCP连接、发送http请求、资源解析等步骤。

http缓存优化是网页性能优化的重要一环,这一部分我会在后续笔记中做一个详细总结,所以本文暂不多做详细整理。本文主要从网页渲染过程、网页交互以及Vue应用优化三个角度对性能优化做一个小结。

首先谈谈拿到服务端资源后浏览器渲染的流程:

关键渲染路径是浏览器将 HTML、CSS、JavaScript 转换为在屏幕上呈现的像素内容所经历的一系列步骤。也就是我们刚刚提到的的的浏览器渲染流程。

为尽快完成首次渲染,我们需要最大限度减小以下三种可变因素:

首先,DOM 和 CSSOM 通常是并行构建的,所以 CSS 加载不会阻塞 DOM 的解析。

然而,由于 Render Tree 是依赖于 DOM Tree 和 CSSOM Tree 的,

所以他必须等待到 CSSOM Tree 构建完成,也就是 CSS 资源加载完成(或者 CSS 资源加载失败)后,才能开始渲染。因此,CSS 加载会阻塞 Dom 的渲染。

由此可见,对于 CSSOM 缩小、压缩以及缓存同样重要,我们可以从这方面考虑去优化。

当浏览器遇到 script 标记时,会阻止解析器继续操作,直到 CSSOM 构建完毕,JavaScript 才会运行并继续完成 DOM 构建过程。

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility 等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

回流(Reflow)

当 Render Tree 中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。

有时即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流。现代浏览器会对频繁的回流或重绘操作进行优化:浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。

当你访问以下属性或方法时,浏览器会立刻清空队列:

因为队列中可能会有影响到这些属性或方法返回值的操作,即使你希望获取的信息与队列中操作引发的改变无关,浏览器也会强行清空队列,确保你拿到的值是最精确的。

避免频繁操作样式,最好一次性重写 style 属性,或者将样式列表定义为 class 并一次性更改 class 属性。

避免频繁操作 DOM,创建一个 documentFragment,在它上面应用所有 DOM 操作,最后再把它添加到文档中。

也可以先为元素设置 display: none,操作结束后再把它显示出来。因为在 display 属性为 none 的元素上进行的 DOM 操作不会引发回流和重绘。

避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。

对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。

图片懒加载在一些图片密集型的网站中运用比较多,通过图片懒加载可以让一些不可视的图片不去加载,避免一次性加载过多的图片导致请求阻塞(浏览器一般对同一域名下的并发请求的连接数有限制),这样就可以提高网站的加载速度,提高用户体验。

将页面中的img标签src指向一张小图片或者src为空,然后定义data-src(这个属性可以自定义命名,我才用data-src)属性指向真实的图片。src指向一张默认的图片,否则当src为空时也会向服务器发送一次请求。可以指向loading的地址。注意,图片要指定宽高。

当载入页面时,先把可视区域内的img标签的data-src属性值负给src,然后监听滚动事件,把用户即将看到的图片加载。这样便实现了懒加载。

事件委托其实就是利用JS事件冒泡机制把原本需要绑定在子元素的响应事件(click、keydown……)委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。

优点:

例如有一个列表需要绑定点击事件,每一个列表项的点击都需要返回不同的结果。

传统写法:

传统方法会利用for循环遍历列表为每一个列表元素绑定点击事件,当列表中元素数量非常庞大时,需要绑定大量的点击事件,这种方式就会产生性能问题。这种情况下利用事件委托就能很好的解决这个问题。

改用事件委托:

输入搜索时,可以用防抖debounce等优化方式,减少http请求;

这里以滚动条事件举例:防抖函数 onscroll 结束时触发一次,延迟执行

节流函数:只允许一个函数在N秒内执行一次。滚动条调用接口时,可以用节流throttle等优化方式,减少http请求;

下面还是一个简单的滚动条事件节流函数:节流函数 onscroll 时,每隔一段时间触发一次,像水滴一样

参考链接: https://zhuanlan.zhihu.com/p/113864878?from_voters_page=true