DOM渲染机制与常见性能优化

参考资料

零、前言

本人垃圾,毁人无数。之前在 JavaScript客户端开篇 一文提及JavaScript客户端的时间线,本篇将做进一步详细的叙述。主要包括渲染的路径,以及渲染性能,并介绍一些常见的渲染优化方法,及其他前端相关优化要点。

一、主要渲染原理

1.0 前情提要

时间线

1.1 构建对象模型

浏览器处理过程
  • 转换:浏览器从磁盘或网络读取 HTML 的原始字节,然后根据指定的文件编码格式(例如 UTF-8 )将其转换为相应字符
  • 符号化:浏览器将字符串转换为 W3C HTML5 标准 指定的各种符号
  • 词法分析:发射的符号转换为 对象 ,定义它们的属性与规则
  • DOM 构建:因为 HTML 标记定义不同标签间的相互关系(某些标签嵌套在其他标签中),所以创建的对象在树状数据结构中互相链接,树状数据结构还捕获原始标记中定义的父子关系:比如 HTML 对象是 body 对象的父对象, body 是 paragraph 对象的父对象等等
CSS 对象模型
  • CSSOM 采用树状结构的原因:在给页面上的一切对象计算最终的样式集时,浏览器会先从应用给该节点的最通用规则开始(例如,如果节点是 body 元素的子元素,则应用所有 body 样式),然后,通过应用更具体的规则递归细化计算的样式,即 规则向下层叠
  • 上面的树不是完整的 CSSOM 树,它只是显示了我们决定在样式表中覆盖的样式。每个浏览器都会提供一套默认的样式,也称为 用户代理样式,即我们不提供任何自定义样式时看到的样式
  • CSS 规则转换的过程跟 HTML 相似,如下图:
CSSOM 转换过程

1.2 渲染树构建、布局和绘制

Render Tree
  • DOM 树与 CSSOM 树融合成渲染树,其中渲染树只包括渲染页面需要的节点(不包括 <script><style>display: none; 等)
  • 布局(也称为重排reflows))计算每个对象的精确位置及尺寸
  • 最后一步的绘制,输入确定的渲染树,在屏幕上渲染像素

1.3 CSS/JS阻塞

  • CSS相关:
  • 默认情况下,CSS 被视为阻塞渲染的资源
  • 媒体类型与媒体查询允许我们将一些 CSS 资源标记为不阻塞渲染
  • 所有 CSS 资源,无论阻塞或不阻塞,浏览器都会下载。
  • JavaScript相关:
  • JavaScript 可以查询、修改 DOM 与 CSSOM
  • JavaScript 的执行因 CSSOM 而阻塞
  • 除非明确声明 DOM 构建为异步,否则 JavaScript 会阻塞这一流程
  • 实例分析 CSS 和 JavaScript 默认与异步情况下浏览器的执行流程
// demo 1
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div>![](awesome-photo.jpg)</div>
    <script src="app.js"></script>
  </body>
</html>
demo 1 执行流程
// demo 2
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div>![](awesome-photo.jpg)</div>
    <script src="app.js" async></script>
  </body>
</html>
demo 2 执行流程
// demo 3
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet" media="print">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div>![](awesome-photo.jpg)</div>
    <script src="app.js" async></script>
  </body>
</html>
demo 3 执行流程

1.4 通过异步编程减少关键路径阻塞

  • 通过以上的不同执行路径分析,可以得出以下通过异步编程减少关键路径阻塞的结论:
  • 首屏的 DOM 结构尽量简单
  • 若 JavaScript 代码不影响 DOM 构建,使用 async (放在 <body> 底部不一定不会影响最初的 DOM 构建,主要看具体浏览器的执行机制)
  • 若 HTML 语义化较好,CSS 文件可使用媒体类型 media= "print" 使其不阻塞渲染

1.5 前端自动化打包的优化方式

  • 为减少 http 请求次数,现在的自动化打包工具都会把 JavaScript 文件打包到不超过 4 个 JavaScript 文件中(浏览器同时请求上限为 4 个),因此加载速度会较慢,故 Web App 经常采用首屏页面加载
  • 首屏页面渲染完毕后,再加载 bundle.js ,加载完毕后进行页面的重排(reflows)和重绘(repaint),进入单页面应用(Single Page Application)

二、性能优化

2.0 前情提要

  • 本节性能优化内容基于第一节的浏览器渲染原理,根据各个流程给予一定的性能优化指南。内容跟第一节有部分重复。

2.1 加载优化(最耗时)

  • 减少 HTTP 请求:浏览器一般同时响应请求为4个请求(PC 一般为4个,Android 支持4个,IOS 5后可支持6个),所以尽量减少页面的请求数,首次加载同时请求数不能超过4个。(Webpack打包等)
  • 合并 CSS、 JavaScript;
  • 合并小图片、 使用 CSS sprite,base-64;
  • 缓存:使用缓存可以减少向服务器的请求数,节省加载时间,所以所有静态资源都要在服务器端设置缓存,并且尽量使用长 Cache (长 Cache 资源的更新可使用时间戳)
  • 缓存原理参考
  • 缓存一切可缓存的资源;
  • 使用长 Cache (使用时间戳更新 Cache);
  • 使用外联式引用 CSS、JavaScript(可使用 localstorage 缓存图片);
  • 压缩 HTML、CSS、JavaScript:减少资源大小可以加快网页显示速度,所以要对HTML、CSS、JavaScript 等进行代码压缩,并在服务端设置 GZip
  • 压缩(例如多余的空格、换行符和缩进),自动化工具或在线压缩工具;
  • 启用 GZip
  • 使用首屏加载:首屏的快速显示,可以大大提升用户对页面速度的感知,因此应尽量针对首屏的快速显示做优化;
  • 按需加载:将不影响首屏的资源和当前屏幕资源不用的资源放到用户需要时才加载,可以大大提升重要资源的显示速度和降低总体流量。但是这也会导致大量重绘,影响渲染性能
  • LazyLoad
  • 滚屏加载
  • 通过 Media Query 加载
  • 预加载:大型重资源页面(如游戏)可使用增加 Loading 的方法,资源加载完成后再显示页面。但 Loading 时间过长,会造成用户流失。
  • 对用户行为分析,可以在当前页加载下一页资源,提升速度
  • 图片压缩:图片是最占流量的资源,因此尽量避免使用它,使用时选择最合适的格式(实现需求的前提下,以大小判断),合适的大小。
  • 使用 智图 压缩;
  • 使用其他方式代替图片(CSS3,SVG,IconFont);
  • 使用 Srcset (主要移动端);
  • 选择合适的图片(webP优于JPG,PNG8优于GIF);
  • 选择合适的大小(首次加载不大于1014KB,不宽于640(基于手机的一般宽度));

2.2 脚本执行优化

  • CSS 写在头部(阻塞 DOM 渲染,不阻塞加载,内联会阻塞加载),JavaScript 写在尾部或异步(默认阻塞加载和渲染)
  • 避免图片和 iFrame 等的空 Src:空 Src会重新加载当前页面,影响速度和效率
  • 尽量避免重设图片大小:指通过 CSS、JavaScript 等中多次重置图片大小,多次重设图片大小会引发图片的多次重绘,影响性能
  • 图片尽量避免使用 DataURL ,前面提到 DataURL可以减少加载时间,但是 DataURL 没有使用图片的压缩算法文件会变大,并且要解码后再渲染,耗时长,综上应尽量避免

2.3 CSS优化

  • 尽量避免在 HTML 标签中写 Style 属性
  • 避免 CSS 表达式:CSS 表达式的执行需跳出 CSS 书的渲染,因此请避免 CSS 表达式
  • 移除空的 CSS 规则:空的 CSS 规则增加了 CSS 文件的大小,且影响 CSS 树的执行,所以需移除空的 CSS 规则
  • 使用 flexbox 代替传统的布局模型
  • 正确使用 display 属性:
  • display:inline 后边不应再使用 widthheightmarginpadding 以及 float
  • display:inline-block 后不应该使用 float
  • display:block 后不应该再使用 vertical-align
  • display:table 后不应该再使用 marginfloat
  • 不滥用 floatfloat 在渲染时的计算量比较大,尽量减少使用
  • 不滥用 Web 字体:Web字体需要下载,解析,重绘当前页面,尽量减少使用
  • 不声明过多的 font-size: 尽量使用语义化标签的默认字体大小,提高 CSS 树的效率
  • 值为 0 时不需要任何单位
  • 标准化各种浏览器前缀
  • 没前缀应放在最后
  • CSS 动画只用(-webkit- 无前缀 两种即可)
  • 其他前缀为 -webkit--moz--ms-无前缀 四种
  • 避免让选择器看起来像正则表达式:高级选择器执行耗时长且不易读懂,避免使用

2.4 JavaScript执行优化

  • 减少重绘和回流
  • 避免不必要的 DOM 操作;
  • 尽量改变 Class 而不是 Style ,使用 classList 代替 className ;
  • 避免使用 document.write()
  • 减少 drawImage
  • 缓存 DOM 选择与计算:每次 DOM 选择都要计算,用一个变量保存这个值;
  • 尽量使用事件代理,避免批量绑定事件;
  • 尽量使用 ID 选择器:ID选择器是最快的;
  • Touch 事件优化:使用 touchstarttouchend 代替 click,但注意 Touch 响应过快,易引发误操作;

2.5 渲染优化

  • HTML 使用 viewportviewport 可以加速页面的渲染;
  • 减少 DOM 节点:DOM 节点太多影响页面的渲染,应尽量减少 DOM 节点
  • 动画优化:
  • 尽量使用 CSS3 动画
  • 合理使用 requestAnimationFrame 动画代替 setTimeout (跑在主线程上,一般一秒刷新 60 次,提高动画帧的利用效率),参考文章 requestAnimationFrame & CSS3 animation
  • 适当使用 Canvas 动画, 5 个元素以内使用 CSS 动画(IOS8可使用webGL);
  • 高频事件优化:TouchmoveScroll 事件可导致多次渲染
  • 使用 requestAnimationFrame 监听帧变化,使得在正确的时间进行渲染;
  • 增加响应变化的时间间隔,减少重绘次数
  • GPU 加速:CSS中以下属性(CSS3 transitions、CSS3 3D transforms、Opacity、Canvas、webGL、Video)来触发 GPU 渲染,但过度使用会引发手机耗电增加。

END:初步整理,望各位留下评论共同探讨。不胜感激!

推荐阅读更多精彩内容

  • 大家都知道万维网的应用层使用了HTTP协议,并且用浏览器作为入口访问网络上的资源。用户在使用浏览器访问一个网站时需...
    SylvanasSun阅读 1,563评论 1 11
  • 围绕前端的性能多如牛毛,涉及到方方面面,以我我们将围绕PC浏览器和移动端浏览器的优化策略进行罗列注意,是罗列不是展...
    流动码文阅读 345评论 0 0
  • 前端性能优化指南 AJAX优化 缓存AJAX: 请求使用GET:当使用XMLHttpRequest时,而URL长度...
    kangkk阅读 1,228评论 1 30
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 137,960评论 20 590
  • 她就这样等待着,漫漫地等待某一天。她的意中人会身披金甲战衣,脚踩七色的云彩来娶她。沉浸在幻想中的她是幸福的,拥抱着...
    文艺小贩阅读 26评论 0 0