前言

前端代码离不开浏览器环境,理解 js、css 代码如何在浏览器中工作是非常重要的。

如何优化渲染过程中的回流,重绘?script 脚本在页面中是怎么个加载顺序?了解这些对前端性能优化起着非常大的作用。

借着这篇文章,让自己对这块知识的理解更深一步。

渲染

渲染树(Render Tree)

浏览器通过解析 HTML 和 CSS 后,形成对应的 DOM 树和 CSSOM 树。

从根节点开始解析 DOM 树节点并匹配对应的 CSSOM 样式规则,选择可见的的节点,最终结合成一颗渲染树

从上图能看到渲染树的特点:

  • 渲染树中不包含 head、script、link、meta 之类不可见的节点
  • CSS 定义的样式规则将和实际的 DOM 匹配,并且被 display:none 修饰的节点最终不会出现在渲染树中

渲染阶段

根据上图,整个渲染阶段分为三部分:

  • 渲染树的形成:通过 DOM 和 CSSOM 形成渲染树
  • 布局 Layout(自动重排 Reflow):基于页面的流式布局,遍历渲染树节点,不断计算节点最终的位置,几何信息,样式等属性后,输出一个“盒模型”
  • 绘制 Paint(栅格化):将节点位置,大小根据屏幕的窗口大小换算成真实的像素,同颜色等属性一同“画到”页面上

回流和重绘

基本概念

  • 回流 Reflow:某些元素位置、几何形状的更改需要浏览器重新计算相关元素。
  • 重绘 Repaint:将回流重排好的元素绘制到页面上,但也因某些 js、css 的修改导致渲染树发生变化,浏览器需要再次绘制页面。

两者的关系:触发回流一定会触发重绘, 而触发重绘却不一定会触发回流

下图很形象的展示了 Mozilla 页面的渲染过程。

触发回流条件

  • 首次布局渲染页面
  • 改变浏览器窗口大小
  • 改变字体
  • 网页内容变化
  • 触发 CSS 伪类
  • 操作 DOM
  • style 样式表发生变化
  • 调用 DOM 元素的 offsetXX, clientXX,scrollXX,getClientRects 等属性方法,获取元素当前的位置度量信息(参见)

如何测试网页性能

都知道频繁的渲染过程会影响网页性能,但怎么知道网页开始渲染内容了呢?

我们可以通过 Chrome 的 F12,选择 Rendering 来查看网页的性能。

  • Paint flashing: 以绿色高亮重绘区域
  • Layout Shift Regions: 以蓝色高亮布局发生变化的区域

结合上面的方法,用 一个简单的 Demo 来示意:

能从图中看到,这些操作 触发了浏览器的重绘

  • 鼠标移至按钮上,触发了默认的 hover 效果(出现绿框)
  • 改变元素 color 属性(出现绿框)
  • 修改元素 top 属性,不断改变元素位置影响布局(出现绿框,蓝框)

提升渲染性能

布局/回流绘制/重绘 是页面渲染必须会经过的两个过程,不断触发它们肯定会增加性能的消耗。

浏览器会对这些操作做优化(把它们放到一个队列,批量进行操作),但如果我们调用上面提到的 offsetXX, clientXX,scrollXX,getClientRects 等属性方法就会强制刷新这个队列,导致这些队列批量优化无效。

下面列举一些简单优化方式:

  • 不要使用 table 布局 table 布局会破坏 HTML 流式解析过程,甚至内部元素改动会触发整个 table 重绘
  • 将需更改的 class 放到最里层 明确元素位置,减少父类元素不必要渲染判断
  • 使用 fixed、absolute 属性修饰复杂多变的处理(动画) 将改变范围降到最低程度,避免影响到父级元素
  • 合并,减少 DOM 操作;通过虚拟 DOM 来代替

脚本的加载

link 和 script 加载文件的差异

注:均放在 head 标签内。

考个问题:CSS 定义在 head 中,其需加载 5 秒,请问页面加载后内容会先优先展示吗?

          
我被渲染出来了

我原先以为页面内容会优先渲染,CSS 加载完成后才改变内容样式。其实这是错的。

从上图看到,页面加载后,body 内元素就已经解析好了,只是没有渲染到页面上。随后 CSS 文件加载后,带有样色的内容才被渲染到页面上。

延迟的 link 的加载阻断了页面渲染,但并没有影响 HTML 的解析,当 CSS 加载后,DOM 完成解析,CSSOM 和 DOM 形成渲染树,最后将内容渲染到页面上。

反问,将 link 替换成 script 效果也一样吗?

与 link 不同,script 的加载会阻断页面 HTML 的解析,浏览器解析完 script 后,会等待 js 文件加载完后,页面才开始后续的解析,body 内容才出现。

head 和 body 中的 script 标签

学前端时相信都听过这样的名言:

CSS 写在 head 里,js 写在 body 结束标签前

知道了上面 link 和 script 的区别后,应该明白前半句的含义,下面来解释下后半句。

下面 script 均在 body 中

页面渲染 和 script 加载

先看下脚本在 body 中的一般情况:

在 body 内部的首位分别加载两个 js 文件,前者延迟 3 秒,后者延迟 5 秒,为了清楚他们的“工作”情况,在 head 中添加了定时器示意。

                      
我被渲染了

能看到 body 中定义的内联脚本首先工作,初始化 foo 变量。

随后加载 addTen.js,并阻断页面渲染。3 秒后,输出 js 内容(foo 赋值为 10),页面并重新开始解析,展示 div 内容。

最后加载 addOne.js ,继续等待 2 秒后,输出 js 内容(foo 赋值为 11)。

多个 script 文件的加载

如果前一个 js 文件加载慢于后一个,会有怎么个效果?

我被渲染了

两个 script 标签并行加载,1 秒后 addOne.js 首先加载完毕,等待 4s 秒后,addTen.js 加载完后,页面直接渲染(因为 script 已经全部完成)。

简单总结下

  1. 无论在 head 还是 body 中,浏览器会等待 script 文件的加载(阻断页面解析渲染)
  2. 多个 script 的文件加载是异步的,不存在互相影响(后一个文件不需要等待前一个加载完后才下载),执行顺序同定义顺序

所以建议 script 放在 body 结束标签之前,确保页面内容全部解析完成并开始渲染。

DOM 的 DOMContentLoaded 事件

DOMContentLoaded 事件可以来确定整个 DOM 是否全部加载完成,下面我们简单测试下:

我被渲染了

最终输出:

addTen.jsfoo 10addOne.jsfoo 11[ready] document

DOMContentLoaded 事件的定义是异步回调方式,当 DOM 加载完成后触发,即使写在最前面,也会等待后面的 script 加载完成后才触发。

这里顺便提个 window.onload

window.onloadDOMContentLoaded 不同,前者会等待页面中所有的资源加载完毕后再调用执行(比如:img 标签),后者在 DOM 加载完毕后即触发。

“真正的异步脚本”——动态脚本

能看到无论 script 放在那个位置,浏览器都会等待他们直至 body 内的文件全部加载完。

那有什么 真正的异步 脚本加载吗?(不会阻断页面解析)

那就是 动态脚本

如果你接触过第三方网页统计脚本,那将比较了解,下面给段示例代码:

我被渲染了

最终输出:

addTen.jsafoo 10addOne.jsfoo 11[ready] document已加载  5  秒已加载  6  秒已加载  7  秒已加载  8  秒dynamicScript.js is runningdynamicScript.js loaded已加载  9  秒已加载  10  秒

定义了需要加载 8 秒的 dynamicScript.js 文件,所有的 script 加载方式依旧异步,但 dynamicScript.js 在 DOMContentLoaded 触发后,最后才执行,浏览器并没有等待它的加载完成后才渲染页面。

我们也可以将它放在 head 中。这种通过脚本来动态修改 DOM 结构的加载方式是 无阻塞式 的,不受其他脚本加载的影响。

defer 和 async

我们可以在 script 定义 deferasync ,使整个脚本加载方式更加友好。比如:被修饰的脚本在 head 中,将不会阻断 body 内容的展示

注意: defer 修饰的脚本将延迟到 body 中所有定义的脚本之后,DOM(页面内容)加载完之前触发async 不会像 defer 一样等待 body 中的脚本,而是当前脚本一加载完毕就触发。

                  
我被渲染了

加载顺序:

已加载  1  秒已加载  2  秒scriptAsync.js已加载  3  秒已加载  4  秒addTen.jsfoo 10addOne.jsfoo 11scriptDefer.js[ready] document已加载  5  秒已加载  6  秒已加载  7  秒已加载  8  秒dynamicScript.js is runningdynamicScript.js loaded已加载  9  秒已加载  10  秒

本文使用 mdnice 排版

关闭浏览器网页触发事件_浅析浏览器渲染和 script 加载相关推荐

  1. 关闭浏览器网页触发事件_浏览器是如何工作的?

    作者:zhangwang 原文链接:https://zhuanlan.zhihu.com/p/47407398 可能每一个前端工程师都想要理解浏览器的工作原理. 我们希望知道从在浏览器地址栏中输入 u ...

  2. ie浏览器网页版进入_荟萃浏览器v2.10.2清爽版 网页秒开/装机必备

    荟萃浏览器v2.10.2清爽版 网页秒开/装机必备 https://www.lanzoux.com/iEq2ge0s3ej ·简约风格 界面简洁布局清爽,感受纯净简约之美. ·极速浏览 精心优化加载速 ...

  3. 计算机如何快速关闭网络页面,关闭浏览器快捷键是什么_电脑浏览器快捷键怎么关闭-win7之家...

    对于电脑中经常使用的程序,无疑就是属于浏览器了,大部分用户都会通过浏览器来打开一些网页,然而由于打开的窗口过多,每次想要关闭时,都需要进行确认操作,很是麻烦,那么电脑浏览器快捷键怎么关闭呢?接下来小编 ...

  4. java弹窗 触发事件_关于ElementUI中MessageBox弹框的取消键盘触发事件(enter,esc)关闭弹窗(执行事件)的解决方法...

    好久没见了 在项目中遇到一个小小的需求,总结了一下! 详细我就不介绍了,相信大家用过的话,很了解.详见文档-----------> http://element-cn.eleme.io/#/zh ...

  5. js hover 触发事件_为什么说JS的DOM操作很耗性能

    想问这样的问题,其实是自己心中没有个谱,一直用 js 计算性能来衡量 浏览器dom 操作性能.js性能和浏览器性能其实是两码事. 这个问题很抽象,它里面涉及挺多个小的知识点. 重申一点,js 操作 D ...

  6. JS关闭窗口时触发事件方法

    JS监听关闭浏览器事件 1.Onunload与Onbeforeunload区别: ①Onunload,onbeforeunload都是在刷新或关闭时调用,可以在 ②Onbeforeunload也是在页 ...

  7. ie浏览器开发比谷歌浏览器_跨浏览器开发:处理IE

    ie浏览器开发比谷歌浏览器 I compare developing for Internet Explorer to growing up in a mental asylum. If you we ...

  8. JS在即将离开当前页面(刷新或关闭)时触发事件

    // onbeforeunload 事件在即将离开当前页面(刷新或关闭)时触发window.onbeforeunload = function () {return /^\#\/ipinfo/.tes ...

  9. 监听浏览器的返回事件,禁止浏览器返回

    //禁止页面后退 history.pushState(null,null,document.URL); window.addEventListener('popstate',function(){// ...

最新文章

  1. Paddle 网络中的Tensor 数据结构
  2. vue计算属性computed与监听属性watch的基本使用
  3. 判断系统是大端还是小段
  4. power bi函数_在Power BI中的行上使用聚合函数
  5. 编写第一个Java程序:helloworld
  6. 20155335俞昆《java程序设计》第十周总结
  7. 详解nullable、firstpos、lastpos和followpos的计算规则
  8. 程序员给小姐姐修电脑的最佳地方和姿势
  9. *第六周*数据结构实践项目三【括号的配对】
  10. 直线电机原理动画_直线振动筛工作原理结构图以及结构解析
  11. 一段关于中国人口老龄化的评论
  12. 做Meta分析要用哪些软件?Meta分析软件盘点,含软件安装包!
  13. VLD(Visual LeakDetector)内存泄露库的使用
  14. 操作系统介绍,为什么使用虚拟机,虚拟机使用原理透彻解释
  15. 新世纪大学英语(第二版)综合教程第一册 Unit 1 (中英翻译和重点单词)
  16. AutoCAD中如何截取清楚的图像
  17. 行式数据和列式数据对比 存储压缩性能
  18. OneNote 与 OneNote 2016 有什么区别?
  19. ScyllaDB 1.2 国内安装更新源发布
  20. 基于基站定位数据的商圈分析

热门文章

  1. Java与C#个人之比较
  2. 公司管理项目管理中的技巧
  3. 跨域资源共享 CORS 详解
  4. 解决:java.lang.IllegalStateException: ApplicationEventMulticaster not initialized
  5. Spring之JDBCTemplate
  6. dependency 中的 classifier属性
  7. 关于RESTful一些注意事项,接口开发规范
  8. jrebel、JavaRebel
  9. java.util.UnknownFormatConversionException: Conversion = ‘,‘ 解决
  10. 2019-03-28 SQL Server Pivot