探寻浏览器渲染的秘密
(给前端大全加星标,提升前端技能)
作者:前端桃园 公号 / 桃翁
前言
起因是这样,有运营小姐姐跟我反馈某个页面卡顿的厉害。心中突然一想,妈耶不会有bug吧,心慌慌的。然后自己打开页面,不卡呀,流畅的一xx,肯定是她弄错了。带着去教她如何正确的使用电脑的想法我自信的下了楼,然后自信的在她电脑上打开了页面,我滑,我滑,我再滑。woc,页面咋不动啊,woc,电脑都卡死了。???什么情况,然后有其他运营反馈 air 上并不卡顿。页面下滑为何卡顿?在mbp和mba上的表现为何不同?这一切的问题究竟是从何而起?请老板们带着这两个问题往下看,我将一步一步揭开浏览器渲染的面纱。
先上张图让大家感受一下被支配的恐惧。注意,那个 GPU 进程内存空间占用 10.9 GB。
知识储备
要搞懂我下面说的,首先你需要先知道现代浏览器的架构以及显卡、GPU 和屏幕分辨率的关系。当然了,就算这些不了解,也是可以接着往下看的,我会简单的讲一下,嘻嘻嘻。
现代浏览器的架构
因为这里并没有什么规范,各大浏览器厂商的各自的架构设计也并不相同(不过都是大同小异),我就以 chrome 浏览器为例说一下 chrome 的设计。
chrome 浏览器从最初的单进程发展到现在的多进程架构。我们可以从上面我发的图看到浏览器包括:一个浏览器进程、一个 GPU 进程、一个网络进程、多个渲染进程和多个插件进程。
渲染进程
了解了上面的浏览器的架构,下面我们说说今天的主角渲染进程,关于浏览器多进程之间是如何配合最后在屏幕上展示内容的,这个后面会写文章记录。现在我们说说渲染进程的事儿。
按照渲染的时间顺序可以分成以下几个子阶段:构建 DOM 树、样式计算、布局、分层、绘制、分块、光栅化、合成。东西有点多,为了快速记忆和理解,需要重点关注每个子阶段的输入和输出以及做了哪些处理。
还需要了解渲染进程中的几个线程。包括主线程(main thread)、工作线程(work thread)、合成线程(compositor thread)以及光栅化线程(raster thread)。后面会总结这些线程的具体功能,我们先看一下整体的渲染流程。
构建 DOM 树
DOM 树是什么相信大家都知道,我就不多 BB 了。因为浏览器无法直接理解和使用 html 文件,所以需要将 html 文件转为浏览器能够理解的结构 DOM 树。
网页中常常包含图片、css、js 等资源文件,这些资源浏览器会去各种渠道获取(缓存、网络下载等)。在构建 DOM 树的时候主线程会去请求他们,相关资源会通过进程之间的通信(IPC)通知网络进程去下载这些资源。在遇到 <script>
标签的时候,解析 DOM 树的工作会暂停,等 js 代码执行完毕之后在去重新解析 DOM 树。
总结一下构建 DOM 树子阶段的输入、输出以及操作过程:
输入:html 文件
输出:DOM 树
操作过程:解析 html 结构为浏览器可以理解的 DOM 树结构,期间会去下载次级资源以及执行 js 代码。
样式计算
样式计算是为了获取每个节点的样式,其主要分为三步来完成。
首先和解析 DOM 树一样,浏览器是无法理解 css 代码的,需要将 css 文件转成浏览器可以理解的数据结构 styleSheets。具体 styleSheets 是什么样的结构这里我们就不去重点了解了,只需要了解到主进程会将 css 代码转成浏览器可以理解的结构,这个结构支持查询和修改。可以在开发者工具上通过 document.styleSheets 打印出来。
为了适配多端样式,我们可能使用的是 rem、vh 等 css 代码。这些属性值不容被渲染引擎理解,所以需要将这些不是标准化的样式转为标准样式。比如 rem 转成 px、bule 转成 rgba 等。
我们获取到标准化后的样式表,最后就是计算每个节点的样式了。这一步骤涉及到 css 的继承规则和层叠规则。有些属性是可以被子元素继承的,有些属性是会覆盖前面的样式。这一块也不多做讨论了。
总结一下样式计算子阶段的输入、输出和操作过程:
输入:css 样式文件
输出:对应每个 DOM 的样式
操作过程:进行了三个操作,包括:转成浏览器可以理解的 styleSheets、将 css 转成标准化的样式、最后是计算每个节点的样式。
布局阶段
想要渲染一个完整的页面,仅知道 DOM 树和 DOM 树元素的样式还是不够的,我们还需要知道 DOM 树中元素的位置。
同样的布局这个子阶段也分为两个过程操作,分别是合成布局树和计算节点位置。
布局树和 DOM 树类似,不过布局树上只包含会显示的节点内容,不包含如 等元素。也不包含 display: none 样式的元素。只包含可见节点。有了一颗完成的布局树,主线程会计算出每个元素的位置信息以及盒子大小。
总结一下布局阶段子阶段的输入、输出和操作过程:
输入:css 样式表、DOM 树
输出:布局树
操作过程:合成布局树、计算节点位置
分层
有了布局树,计算出了每个节点的位置。那么下面是不是进行绘制了呢?答案是否定的,因为页面有很多复杂的效果,比如滑动、z-idnex 等。为了更好的实现这些效果,渲染引擎主线程还需要为特定的阶段生成专用的图层,并生成一颗对应的图层树。
分层这一步其实没什么好解释了,唯一需要了解的是哪些元素会被单独分层。布局树和图层树并不是一一对应的关系,不是每个布局树的节点都会生成一个单独的图层树节点。如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。那么哪些操作会让节点生成一个单独的图层呢?接着往下面看。
1)拥有层叠上下文属性的元素会单独生成一个图层。
浏览器是一个二维的概念,但是层叠上下文可以让元素具有三维的概念。比如 css 属性中的 z-index、position、css 滤镜等。
3D 或透视变换的 css 属性
使用加速视频解码的 video 元素
canvas 元素
opacity 属性
2)需要裁剪的地方也会单独生成一个图层
裁剪就是需要滚动的地方,里面内容会单独生成一个图层。如果有滚动条,滚动条也会单独生成一个图层。(所以想一想我那个性能很差的页面有多少个图层?手动狗头)
总结一下布局阶段子阶段的输入、输出和操作过程:
输入:布局树
输出:图层树
操作过程:为特定的节点生成单独的图层、并将这些图层合成图层树
图层绘制
在完成图层树的构建之后,渲染引擎主线程会对每个图层进行绘制。这里说的绘制不是真正的绘制画面,而是生成一个绘制指令列表。
如果我们要在白纸上绘制一些东西,比如黄底、白圆、黑字的一个图案。通常我们会把操作分解成几步来完成:
我们会先在白纸上涂上黄色的底。
然后我们会在黄底上画一个白色的圆。
最后我们会在白色圆上画出黑色的字。
渲染引擎的图层绘制和这个类似,会把每一个图层的绘制拆分成很多的绘制指令。
总结一下布局阶段子阶段的输入、输出和操作过程:
输入:图层树
输出:每个图层的绘制指令
操作过程:将每个图层的绘制拆分成多个绘制指令,传给合成线程。
栅格化
绘制列表只是用来生成记录绘制指令的列表,实际的绘制操作是有渲染进程的合成线程来执行的。
绘制指令生成之后,渲染进程主线程会将绘制指令发送给合成线程,由合成线程来完成最后的绘制工作。合成线程会将图层划分为图块。简单解释下图块是什么,浏览器的视口内容是有限的,有些图层可能非常大。渲染进程不会把该图层的所有内容都渲染出来,而是会将这些图层划分为一个一个小的图块。栅格化子进程会将视口区域内的图块转化为位图(磁贴),并将这位存入 GPU 显存中。GPU 操作是在 GPU 进程中,所以渲染进程会通过 IPC 通信协议来通知 GPU 进程来进行操作。
总结一下布局阶段子阶段的输入、输出和操作过程:
输入:绘制指令列表、图层树。
输出:位图
操作过程:将图层划分为图块,将图块转换成位图。
合成和显示
等所有图块都被栅格化,合成线程会收集位图信息来创建合成帧。合成帧随后会通过 IPC 协议将消息传给浏览器主进程。浏览器主进程收到消息后,会将页面内容绘制到内存中,最后再将内存显示在屏幕上。
总结
到这里,我们整个浏览器的渲染进程也就讲完了。下面我们通过一张图来总结一下渲染过程中,浏览器各进程各线程是如何工作的。
主线程将 html 文件转化为浏览器能够读懂的 DOM 树结构。其中会通过网络进程加载次级资源,遇到 js 会停止构建 DOM 树,并执行 js。
主线程将 css 文件转化为浏览器能够读懂的 styleSheets 结构,并将其中的属性标准化,最后计算每个节点的样式。
主线程通过得到的 DOM 树和 styleSheets 样式表合成一颗布局树并计算每个节点的具体位置。
主线程通过得到的布局树进行图层分层并得到一个图层树。
主线程通过分层树对每一个图层分解绘制指令,得到一个绘制指令列表。
合成线程对图层进行分块处理,并对视口区域内的图块进行位图转换,将得到的结果通过 GPU 进程存入到 GPU 显存中。
合成线程收集位图信息创建合成帧,并将消息通过 IPC 协议传给浏览器主进程,主进程收到消息后,会将页面内容绘制到内存中,最后再将内存显示在屏幕上。
上面已经讲完了浏览器整个渲染流程,我们来讲讲产生这个例子中产生卡顿的原因。通常情况下图层是有助于性能的,但是创建的每一层都需要内存和管理,而这些并不是免费的。事实上,在内存有限的设备上,对性能的影响可能远远超过创建层带来的任何好处。每一层的纹理都需要上传到 GPU,使 CPU 与 GPU 之间的带宽、GPU 上可用于纹理处理的内存都受到进一步限制。
屏幕分辨率、显卡等关系
讲完了渲染流程,也找到了页面卡顿的原因。但是我们还是不知道为何页面在 mbp 和 mba 上有差异。这就是接下来我们要讲的内容了。
我们需要了解几个概念:屏幕尺寸、分辨率、屏幕像素密度。
屏幕尺寸,单位通常是英寸,其大小是显示器的对角线长度。
分辨率也就是屏幕上由多少个像素组成,mbp 的屏幕分辨率是 2560 * 1600,也就是在横向的宽度上有 2560 个像素,竖向的高度上有 1600 个像素。
屏幕像素密度(ppi ),每英寸屏幕有多少个像素。
mbp 的屏幕分辨率是 2560 * 1600,mba 的屏幕分辨率是 1440 * 900。这样算下来 mbp 有 409600 个像素,mba 有1296000 个像素。显卡压力会小很多,内存占用也会更少。再有因为整个布局是 table 布局,每次滑动都会导致整个 table 表格回流,导致整个 GPU 内存飙升。
总结
至此整个问题就全部解决、全部了解清楚了。其实刚开始就把这个问题解决了,但是其中很多东西一直都不怎么了解,趁着这次机会把整个过程都了解清楚。其实像我们这种做开发的人,就是要有一种死钻牛角的精神,不能把问题解决了就行了,更要了解其中的原理,为什么会这样。期间我也有想放弃不整了,还是在小伙伴的帮助下完成这次的探寻之旅。在毕业初期能够遇到一个和自己讲的来话的学长真的能给自己很大的帮助。
共勉。
最后放一张解决了问题后的图。
参考链接
极客时间《浏览器工作原理与实践》第5、6讲
https://zhuanlan.zhihu.com/p/47407398
https://developers.google.com/web/fundamentals/performance/rendering/stick-to-compositor-only-properties-and-manage-layer-count
https://my.oschina.net/u/2282680/blog/805130
https://www.zhihu.com/question/268016229
https://www.jianshu.com/p/c3387bcc4f6e
推荐阅读
(点击标题可跳转阅读)
烧脑!JS+Canvas 带你体验「偶消奇不消」的智商挑战
Google 员工吐槽 TypeScript :类型检查不太好
如何让你的 JS 写得更漂亮
觉得本文对你有帮助?请分享给更多人
关注「前端大全」加星标,提升前端技能
好文章,我在看❤️
探寻浏览器渲染的秘密相关推荐
- android 重绘如何能不闪一下屏幕_浏览器渲染机制——重绘重排
性能优化中,减少重绘重排应该是一种很好的优化方式,我们具体看一下什么情况下会造成重绘重排,为什么减少重绘重排可以做到优化,怎么样减少重绘重排. 浏览器渲染过程 我们先看看当浏览器拿到服务端返回的资源时 ...
- 从敲入 URL 到浏览器渲染完成、对HTTP协议的理解
1. 大致过程 当你这样子回答的时候: 用户输入 url 地址,浏览器查询 DNS 查找对应的请求 IP 地址 建立 TCP 连接 浏览器向服务器发送 http 请求,如果服务器段返回以 301 之类 ...
- 深入浅出浏览器渲染原理
前言 浏览器的内核是指支持浏览器运行的最核心的程序,分为两个部分的,一是渲染引擎,另一个是JS引擎.渲染引擎在不同的浏览器中也不是都相同的.比如在 Firefox 中叫做 Gecko,在 Chrome ...
- 浏览器渲染引擎学习总结
简单介绍浏览器渲染引擎情况 很多人就只会用浏览器,不知道浏览器的工作原理或者说浏览器最核心的东西,浏览器的内核是最核 心的东西,也叫做渲染引擎,那这个东西到底是干嘛的呢,下面本教程就为大家好好介绍一下 ...
- 浏览器渲染机制面试_浏览器渲染原理
本文目录结构 问题 浏览器渲染原理 渲染过程 1. 浏览器接收到 HTML ⽂件并转换为 DOM 树 当我们打开⼀个⽹⻚时,浏览器都会去请求对应的 HTML ⽂件.虽然平时我 们写代码时都会分为 JS ...
- div置于页面底部_浏览器渲染页面的原理及流程
浏览器渲染页面的原理及流程 浏览器将域名通过网络通信从服务器拿到html文件后,如何渲染页面呢? 1.根据html文件构建DOM树和CSSOM树.构建DOM树期间,如果遇到JS,阻塞DOM树及CSSO ...
- 浏览器渲染阻塞与优化-详解推迟加载、异步加载。
我认为一个前端工程师是否优秀,很大程度上取决于对前端性能上优化的功力.所以性能优化对前端真的很重要!!! 本文介绍了什么是阻塞.为什么会阻塞?阻塞优化常用的5种方式以及他们的注意事项. 浏览器渲染阻塞 ...
- 浏览器渲染机制面试_面试官不讲码德,问我Chrome浏览器的渲染原理(6000字长文)...
前言 对于HTML,css和JavaScript是如何变成页面的,这个问题你了解过吗?浏览器究竟在背后都做了些什么事情呢?让我们去了解浏览器的渲染原理,是通往更深层次的开发必不可少的事情,能让我们更深 ...
- 前端后分离深入分析 ——浏览器渲染和服务器渲染区别
1.为什么会有服务器渲染与客户端渲染? 首先理解服务器和浏览器客户端之间传递的是什么--HTML,CSS,JavaScript的文件以及数据载体json(xml)等文件,而文件都是静态,之所以动态是应 ...
最新文章
- matplotlib命令与格式:图像(figure)与子区域(axes)布局与规划
- 在Salesforce中处理Email的发送
- python语言培训班-学python培训班需要多久?深圳Python培训
- hive 在kettle 作业return code 2 的问题 Unexpected exception: Unexpected exception: Unexpected exception:
- PHP获取字符串的所有子集,PHP Regexp(PCRE)-查找所有子字符串的集合
- java get提交中文乱码_java get方法提交中文乱码问题
- APP版本更新通知流程测试要点
- 从环境搭建探讨做事的方法
- astgo-官方功能更新日志
- 那些我们卖掉的二手iPhone到底去哪了?
- DAX基础1:快速熟悉DAX的使用环境之Power BI
- Linux中的计划任务—Crontab调度一次性执行的任务at/batch
- 用pycharm创建数据库sqlites3表格,但是打开并不显示
- PDF图标异常的解决方法
- 【接口篇 / Lan】(5.6) ❀ 05. 与思科交换机三层链路聚合连接 ❀ FortiGate 防火墙
- Object-Oriented Programming Summary Ⅰ
- 仿hao123的导航网站纯静态版|html导航网站源码,115le仿hao123网址导航整站静态html...
- 两篇word文档行间距设置的一样但是显示的不一样的解决方法
- 阿里云ET城市大脑全面升级 新增城市规划、应急调度等七领域
- 科研试剂Acrylamide-PEG-Acrylamide,丙烯酰胺-聚乙二醇-丙烯酰胺
热门文章
- nginx location配置详解
- 深入理解客户的需求至关重要!
- Dev-DXperience12.2版的新产品介绍:DXTREME
- C#系列五《程序的分支》
- flink读mysql速度怎么样_[DB] Flink 读 MySQL
- json java的set函数,JsonConfig的jsonConfig.setExcludes的用法
- mysql还书过程_记一次安装 MySQL 的过程
- 语言舒尔特方格程序_有效提升孩子注意力的方法,舒尔特方格训练法,简单有效...
- P2802 回家(dfs+三维数组标记+剪枝)
- AdaBoost算法详解与python实现