当前位置: 首页 > news >正文

回流、重绘及其优化

回流、重绘及其优化

渲染过程

渲染引擎通过通过网络请求接收渲染内容

  1. 解析HTML抽象DOM tree
  2. 抽象出Render tree
  3. 布局(layout)render tree
  4. 绘画render tree

抽象DOM tree

渲染引擎的第一步是解析html文档并将解析的元素转换为dom树中的实际dom节点。
image

抽象CSSOM tree

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

构建渲染树

HTML中的可视指令与来自cssom树的样式数据结合使用来创建渲染树。
image
为了构建渲染树,浏览器大致如下:
  • 从dom树的根开始,遍历每个可见节点。某些节点不可见(例如,脚本标记,元标记等),并且由于它们未反映在呈现的输出中而被省略。display:none 也会使节点省略
  • 对于每个可见节点,浏览器找到适当的匹配cssom规则并应用它们
  • 它会发布带有内容和计算样式的可见节点
  • 每个渲染器代表一个矩形区域,通常对应于一个节点的CSS框。

它包括几何信息,如宽度,高度和位置

渲染树的布局

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

html使用基于流的布局模型,这意味着大多数时候它可以一次性计算几何。坐标系相对于根渲染器。使用顶部和左侧坐标。

布局是一个递归过程,从根元素开始,也就是html,每个渲染器都会去计算他自己的位置和大小

绘制渲染树

在这个阶段,遍历渲染器树,调用渲染器的paint()方法在屏幕上显示内容。

渲染分为全局渲染和增量渲染

处理脚本和样式表的顺序

当解析器到达script标记时,脚本将被立即解析并执行。
文档的解析将暂停,直到脚本执行完毕。
这意味着该过程是同步的

这也是为什么把script标签放在body结束之前

html5添加了一个选项,将脚本标记为异步,以便它可以被其他线程解析和执行。

回流和重绘(reflow和repaint)

  • 回流: 意味着元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树;
  • 重绘:意味着元素发生的改变只影响了节点的一些样式(背景色,边框颜色,文字颜色等),只需要应用新样式绘制这个元素就可以了;

何时触发回流和重绘

  • repaint重绘:

    1. reflow回流必定引起repaint重绘,重绘可以单独触发
    2. 背景色、颜色、字体改变(注意:字体大小发生变化时,会触发回流)
  • reflow回流:

    1. 页面第一次渲染(初始化)
    2. DOM树变化(如:增删节点)
    3. Render树变化(如:padding改变)
    4. 浏览器窗口resize
    5. 当你查询布局信息,包括offsetLeft、offsetTop、offsetWidth、offsetHeight、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height、调用了getComputedStyle()或者IE的currentStyle时,浏览器为了返回最新值,会触发回流。

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

减少reflow和repaint

  • 尽量避免改变布局属性。如width, height, left, top。
  • 除了transforms 或者 opacity属性都会引起重绘,做动画的时候要注意,尽量使用这两个属性;
  • 使用Flexbox。
  • 避免多次读取部分布局属性(同上)
  • 将复杂的节点元素脱离文档流,降低回流成本

javascript

  1. 避免使用setTimeout setInterval 来更新视图,这会在render之后提交修改需求
  2. 在micro-tasks中修改dom。这会在render之前提交修改需求
  3. 把script标签放在body结束之前,或者使用异步script(defer, async)
  4. 把计算量大的js放在workers执行,例如解析一个大的json文件

CSS

  • 减少选择器的复杂性。
  • 避免逐个修改节点样式,尽量一次性修改,减少style修改所影响元素的数量,使用cssText来替代要多次修改的style属性
// 设置单个属性
elt.style.color = "blue"; 

// 在单个语句中设置多个样式
elt.style.cssText = "color: blue; border: 1px solid black"; 

// 在单个语句中设置多个样式
elt.setAttribute("style", "color:red; border: 1px solid blue;");
  • 通过改变类名来修改样式

DOM

  1. 使元素脱离文档流
  2. 对其应用多重修改
  3. 把元素带回文档中
这个过程会触发两次回流,第一步和第三步。把会触发多次回流的步骤放在第二步

三种基本方法:

  • display:none,然后修改样式,然后在恢复
  • 使用文档片段(document fragment)在当前dom树之外构建一个子树,再把他拷贝回文档。
var fragment = document.createDocumentFragment()
... 在这里进行dom操作,可以减少回流和重绘的次数
document.getElementById('#app').appendChild(fragment)
  • 将原始元素拷贝到一个脱离文档的节点中,修改副本,完成后替换原始元素
var old = document.getElementById('#app')
var clone = old.cloneNode(true)
... 在这里进行dom操作,可以减少回流和重绘的次数
old.parentNode.replaceChild(clone, old)

缓存布局信息

前面提到在查询布局信息(offsetLeft...)的时候也会引起回流,我们在使用的时候可以把布局信息缓存起来,减少回流次数

这里贴上<<高性能javascript>>中的例子:把myElement元素沿对角线移动,每次移动一个像素,从100100的位置开始,到500500的位置结束。在timeout循环体中你可以使用下面的方法

// 低效的
myElement.style.left = 1 + myElement.offsetLeft + 'px'
myElement.style.top = 1 + myElement.offsetTop + 'px'
if (myElement.offsetTop >= 500) {
  stopAnimation();
}
// 优化
// 在循环外层获取初始值
var current = myElement.offsetLeft
.
.
.
// 直接使用current变量,不再查询偏移量
current++
myElement.style.left = current + 'px'
myElement.style.top = current + 'px'
if (current >= 500) {
  stopAnimation();
}

使元素进行动画效果的时候脱离文档流
在元素发生动画效果的时候,会引起底部元素的回流,这个影响可能很大,也可能很小,取决于元素在文档流的位置

  1. 动画元素使用绝对定位,使其脱离文档流
  2. 这里再进行旋转,跳跃,都不会影响到整个页面的回流
  3. 在动画结束时恢复定位,从而只会下移一次文档的其他元素。

参考DOM操作成本到底高在哪儿
参考高性能javascript
参考How JavaScript works: the rendering engine and tips to optimize its performance

相关文章:

  • JMeter学习参数化User Defined Variables与User Parameters
  • LAMP架构介绍、MySQL和MariaDB介绍、MySQL安装
  • PL/SQL之--变量
  • 面向Android的Tesseract工具
  • java性能优化读书笔记(1)
  • 栈大小和内存分部问题
  • linux系统部署mongodb数据库
  • CentOS6.5菜鸟之旅:安装SUN JDK1.7和Tomcat7
  • Strut2中的标签
  • 区块链应用 | 最全区块链生态图谱发布,一张图看清2400个典型项目
  • Holding Bin-Laden Captive!(母函数)
  • 页面中引入mui 地址选择,点击页面中其他input时页面回到顶部
  • [转载]MFC一个文档不同视图
  • apache2.2 虚拟主机配置
  • 【机器视觉与图像处理】基于MATLAB的角度计算
  • 《Java8实战》-第四章读书笔记(引入流Stream)
  • 【391天】每日项目总结系列128(2018.03.03)
  • laravel with 查询列表限制条数
  • PHP CLI应用的调试原理
  • 计算机在识别图像时“看到”了什么?
  • 排序算法学习笔记
  • 前端学习笔记之观察者模式
  • 微信小程序上拉加载:onReachBottom详解+设置触发距离
  • 小程序测试方案初探
  • mysql面试题分组并合并列
  • ​iOS安全加固方法及实现
  • ​MPV,汽车产品里一个特殊品类的进化过程
  • #pragma multi_compile #pragma shader_feature
  • #在 README.md 中生成项目目录结构
  • (12)Hive调优——count distinct去重优化
  • (2)STL算法之元素计数
  • (二)正点原子I.MX6ULL u-boot移植
  • (附源码)spring boot球鞋文化交流论坛 毕业设计 141436
  • (六)激光线扫描-三维重建
  • (转载)hibernate缓存
  • **python多态
  • .NET CF命令行调试器MDbg入门(二) 设备模拟器
  • .net core使用ef 6
  • .Net Framework 4.x 程序到底运行在哪个 CLR 版本之上
  • .net 简单实现MD5
  • .NET设计模式(11):组合模式(Composite Pattern)
  • @GetMapping和@RequestMapping的区别
  • []sim300 GPRS数据收发程序
  • [C#] 我的log4net使用手册
  • [C++] 默认构造函数、参数化构造函数、拷贝构造函数、移动构造函数及其使用案例
  • [C++]模板与STL简介
  • [CF482B]Interesting Array
  • [CVPR 2023:3D Gaussian Splatting:实时的神经场渲染]
  • [EFI]Dell Latitude-7400电脑 Hackintosh 黑苹果efi引导文件
  • [EFI]MSI GF63 Thin 9SCXR电脑 Hackintosh 黑苹果efi引导文件
  • [Flutter]设置应用包名、名称、版本号、最低支持版本、Icon、启动页以及环境判断、平台判断和打包
  • [LeetCode]Multiply Strings
  • [Lucas定理]【学习笔记】
  • [NAND Flash 6.1] 怎么看时序图 | 从时序理解嵌入式 NAND Read 源码实现
  • [Oh My C++ Diary]内联函数