浏览器内核
基于标记语言显示内容的程序或模块
渲染引擎(浏览器引擎)和 JS 引擎
渲染引擎
在浏览器窗口中显示所请求的内容,显示 html、xml 文档及图片,它也可以借助插件(一种浏览器扩展)显示其他类型数据
- Trident:
IE浏览器 - Gecko:
Firefox消耗很多的资源 - Presto:
Opera兼容性 - Webkit:
Safari前身是KDE小组的KHTML引擎,可以说WebKit是KHTML的一个开源的分支 - Blink:谷歌
Webkit的一个分支
渲染进程是如何工作的
- 主线程 Main thread 处理您发送给用户的大部分代码
- 工作线程 Worker thread 处理 WebWorker 或 ServiceWorker
- 排版线程 Compositor thread
- 光栅线程 Raster thread
解析
构建 DOM
当渲染进程接收提交的导航消息和HTML数据,主线程开始解析文本串(HTML),使之成为一个DOM。解析中遇到 html 能优雅容错。
-
字节数据 => 字符串 => Token => Node => DOM
-
字节数据 => 字符串
浏览器都会去请求对应的 HTML 文件
-
字符串 => Token
浏览器会先将这些字符串通过词法分析转换为标记(token)
代码分拆成一块块,并给这些内容打上标记
-
Token => Node
标记会紧接着转换为 Node
-
子资源加载
网站通常使用图像,CSS 和 JavaScript 等外部资源,需要从网络或缓存加载。在解析构建DOM时,主线程可以逐个请求它们。为了加快速度预加载同时运行。
将 CSS 文件转换为 CSSOM 树
主线程解析 CSS 并确定每个 DOM 节点的计算样式,再根据 CSS 选择器将哪种样式应用于哪个元素。
字节数据 => 字符串 => Token => Node => CSSOM
- 这一过程其实是很消耗资源,浏览器得递归
CSSOM树,然后确定具体的元素到底是什么样式 - 尽可能的避免写过于具体的
CSS选择器 HTML来说也尽量少的添加无意义标签
布局 - layout(回流 reflow)
渲染进程知道每个节点的文档结构和样式。布局是查找元素几何的过程。
布局过程主线程 遍历 DOM 并计算样式,并创建布局树(layout tree, 包含坐标和边界框大小等信息)。
- 渲染树只会包括需要显示的节点和这些节点的样式信息
- 渲染树中的元素(
WebKit中称为「renderers」,Firefox下为「frames」)与 DOM 元素相对应 - 对于每个
DOM元素,必须在所有Style Rules中找到符合的 selector 并将对应的规则进行合并
布局树的特殊情况
- 不包括
display: none的节点 - 包括
visibility: hidden的元素 - 包括伪类元素,如
::before
改变 window 大小,改变字体,添加或删除样式,文字改变,定位或者浮动,用户在 input 框中输入文字,盒模型,激活 CSS 伪类
-
改变父节点里的子节点很可能会导致父节点的一系列回流
-
IE 中的 getComputedStyle(), 或 currentStyle
-
getBoundingClientRect()
-
Eventloop
- 当 Eventloop 执行完 Microtasks 后,会判断 document 是否需要更新,因为浏览器是 60Hz 的刷新率,每 16.6ms 才会更新一次。
- 然后判断是否有 resize 或者 scroll 事件,有的话会去触发事件,所以 resize 和 scroll 事件也是至少 16ms 才会触发一次,并且自带节流功能。
- 判断是否触发了 media query
- 更新动画并且发送事件
- 判断是否有全屏操作事件
- 执行 requestAnimationFrame 回调
- 执行 IntersectionObserver 回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好
- 更新界面
- 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 requestIdleCallback 回调
绘制 - Paint(重绘 repainting)
知道元素的大小,形状和位置,但是不知道绘制的顺序。主线程遍历布局树以创建绘制记录,绘制记录就是一个绘画过程。
特点:
-
布局树变化
在每个步骤中,前一个操作的结果会影响创建绘制顺序。
-
动画
在每个帧之间运行这些操作。如果出现帧丢失或者帧之间运行 JS 时,则页面将出现卡顿。
使用 setTimeout() 可能由于期间隔时间和屏幕刷新时间不同步,可能导致某些帧的操作被跨跃,直接更新下一帧的图像。
将JS操作划分成小块,并安排在每个帧上运行
window.requestAnimationFrame(renderFunc)requestAnimationFrame 优点:
-
省CPU时间:页面隐藏最小化时停止渲染,setTimeout持续运行
-
函数节流:高频率事件(resize/scroll)为防止刷新间隔内多次执行函数,只执行一次更流畅
-
将非dom操作的运行任务放在webWorker中运行,避免阻塞主线程
// 任务文件 demo_workers.js var i = 0; function timedCount() { i = i + 1; postMessage(i); // 向页面发送数据 } setInterval(timedCount, 500); // 包含dom的页面 index.html let w = new window.Worker("demo_workers.js"); // 开始 w.onmessage = function (event) { document.getElementById("result").innerHTML = event.data; }; w.terminate(); // 停止
-
合成
光栅化
浏览器知道文档的结构,每个元素的样式,页面的几何形状和绘制顺序,需将信息转换为屏幕上的像素,称为光栅化。
合成
合成是一种将页面的各个部分分层,分别栅格化,并在合成器线程的单独线程中合成为页面的技术。
如果发生滚动,图层已经被栅格化需要合成一个新帧。通过移动图层和合成新帧,可以以相同的方式实现动画。
分层
为了找出哪些元素需要在哪些层中,主线程遍历布局树以创建层树。如果页面的某些部分应该是单独的图层(如滑入式侧面菜单)但没有得到单独图层,可以使用CSS属性 will-change 提示浏览器。
过多的图层:过多图层合成可能会导致比页面小部分每帧光栅化更慢。
光栅和复合关闭主线程
- 提交合成:创建层树并确定绘制顺序,主线程就会将该信息提交给合成器线程
- 栅格化:合成器线程然后栅格化每个层,合成器线程将它们分成多个图块并将图块发送到栅格线程。
- 栅格存储:栅格线程栅格化每个图块并将它们存储在GPU内存中。
- 绘制四边形:一旦图块被光栅化,绘制四边形的图块信息(图块在内存中的位置、绘制图块页面中的位置)
- 合成框架:合成器线程可以优先考虑视口(或附近)内的删格线程,以便优先被光栅化。图层还有不同分辨率的倾斜度,可以处理放大操作。
- 创建合成器帧:收集绘制四边形的图块信息,通过IPC将合成器框架提交给浏览器进程
- 浏览器UI合成:UI线程添加另一个合成器框架以用于浏览器UI更改,或者从其他渲染器进程添加扩展。
- GPU展示:合成器帧被发送到GPU以在屏幕上显示。
- 滚动事件:合成器线程会创建另一个合成器帧以发送到GPU
优点
- 不涉及主线程的情况下完成
- 合成线程不需要等待样式计算或JavaScript执行