前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浏览器渲染流程

浏览器渲染流程

作者头像
河马嘴不大
发布2022-12-24 12:12:18
4580
发布2022-12-24 12:12:18
举报

页面的设计与实现之后,前端工程师就需要关注性能优化了。其中浏览器渲染机制是前端性能优化的关键,弄浏览器在背后做了什么,才能在明白如何优化。

浏览器解析
  • DOM DOM对象是浏览器解析HTML脚本生成的,最终输出一个树状结构的对象。
  • CSSOM CSSOM对象是浏览器解析CSS脚本生成的,最终也是输出类似DOM的树状结构。
  • RenderTree DOM 与 CSSOM 融合成一棵RenderTree(渲染树),随后计算每个可见元素的布局,并输出给绘制过程,在屏幕上渲染像素。优化这里的每一步对实现最佳渲染性能至关重要。

构建的过程如下:

alt text
alt text
布局

有了渲染树,就进入布局阶段。根据渲染树种确定的每个DOM元素的样式规则,浏览器就能具体计算每个DOM元素最终在屏幕上显示的大小位置,宽高等等几何属性。由于文档流中的布局是相对的,因此每个元素的布局发生变化,会联动引发其他元素的布局变化。

绘制

绘制就是在已确定了几何属性的元素上填充像素,比如绘制文字,颜色,图像,边框,阴影等等可视元素。这期间会产生多个图层

合并渲染层

来到这里,浏览器的渲染过程就接近尾声。每个图层绘制完,浏览器会将其按照合理的顺序合并到同一图层,并显示在浏览器上。

总结
  1. 浏览器解析(包括HTML,SVG,XHTML,CSS,JavaScript等等)
    • 解析HTML代码,构建Document Object Model (DOM)
    • 解析CSS代码,构建CSS Object Model (CSSOM)
    • JavaSript通过API操作DOM和CSSOM, 构建渲染树
  2. 布局阶段 在屏幕上绘制渲染树中的所有节点的几何属性,比如: 位置,宽高,大小等等
  3. 绘制元素 绘制所有节点的可视属性,比如:颜色,背景,边框,背景图等,这期间可能会产生多个图层(堆叠上下文)。
  4. 合并渲染层 把以上绘制的所有图层(类似于PhotoShop中的“图层”)合并,最终输出一张图片。重要的事 这里重要要说两个概念,一个是Reflow,另一个是Repaint。这两个不是一回事。
    • Repaint——屏幕的一部分要重画,比如某个CSS的背景色变了。但是元素的几何尺寸没有变。
    • Reflow——意味着元件的几何尺寸变了,我们需要重新验证并计算Render Tree。是Render Tree的一部分或全部发生了变化。这就是Reflow,或是Layout。(HTML使用的是flow based layout,也就是流式布局,所以,如果某元件的几何尺寸发生了变化,需要重新布局,也就叫reflow)reflow 会从这个root frame开始递归往下,依次计算所有的结点几何尺寸和位置,在reflow过程中,可能会增加一些frame,比如一个文本字符串必需被包装起来。

Reflow的成本比Repaint的成本高得多的多。DOM Tree里的每个结点都会有reflow方法,一个结点的reflow很有可能导致子结点,甚至父点以及同级结点的reflow。在一些高性能的电脑上也许还没什么,但是如果reflow发生在手机上,那么这个过程是非常痛苦和耗电的。

所以,下面这些动作有很大可能会是成本比较高的。

  • 当你增加、删除、修改DOM结点时,会导致Reflow或Repaint。
  • 当你移动DOM的位置,或是搞个动画的时候。
  • 当你修改CSS样式的时候。
  • 当你Resize窗口的时候(移动端没有这个问题),或是滚动的时候。
  • 当你修改网页的默认字体时。

注:display:none会触发reflow,而visibility:hidden只会触发repaint,因为没有发现位置变化。

多说两句关于滚屏的事,通常来说,如果在滚屏的时候,我们的页面上的所有的像素都会跟着滚动,那么性能上没什么问题,因为我们的显卡对于这种把全屏像素往上往下移的算法是很快。但是如果你有一个fixed的背景图,或是有些Element不跟着滚动,有些Elment是动画,那么这个滚动的动作对于浏览器来说会是相当相当痛苦的一个过程。你可以看到很多这样的网页在滚动的时候性能有多差。因为滚屏也有可能会造成reflow。

基本上来说,reflow有如下的几个原因:

  • Initial。网页初始化的时候。
  • Incremental。一些Javascript在操作DOM Tree时。
  • Resize。其些元件的尺寸变了。
  • StyleChange。如果CSS的属性发生变化了。
  • Dirty。几个Incremental的reflow发生在同一个frame的子树上。

好了,我们来看一个示例吧:

代码语言:javascript
复制
var bstyle = document.body.style; // cache


bstyle.padding = "20px"; // reflow, repaint
bstyle.border = "10px solid red"; //  再一次的 reflow 和 repaint


bstyle.color = "blue"; // repaint
bstyle.backgroundColor = "#fad"; // repaint


bstyle.fontSize = "2em"; // reflow, repaint


// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('dude!'));

当然,我们的浏览器是聪明的,它不会像上面那样,你每改一次样式,它就reflow或repaint一次。一般来说,浏览器会把这样的操作积攒一批,然后做一次reflow,这又叫异步reflow或增量异步reflow。但是有些情况浏览器是不会这么做的,比如:resize窗口,改变了页面默认的字体,等。对于这些操作,浏览器会马上进行reflow。

但是有些时候,我们的脚本会阻止浏览器这么干,比如:如果我们请求下面的一些DOM值:

  • offsetTop, offsetLeft, offsetWidth, offsetHeight
  • scrollTop/Left/Width/Height
  • clientTop/Left/Width/Height
  • IE中的 getComputedStyle(), 或 currentStyle

因为,如果我们的程序需要这些值,那么浏览器需要返回最新的值,而这样一样会flush出去一些样式的改变,从而造成频繁的reflow/repaint。

减少reflow/repaint

下面是一些Best Practices:

1)不要一条一条地修改DOM的样式。与其这样,还不如预先定义好css的class,然后修改DOM的className。

代码语言:javascript
复制
// bad
var left = 10,
top = 10;
el.style.left = left + "px";
el.style.top  = top  + "px";


// Good
el.className += " theclassname";


// Good
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";

2)把DOM离线后修改。如:

  • 使用documentFragment 对象在内存里操作DOM
  • 先把DOM给display:none(有一次reflow),然后你想怎么改就怎么改。比如修改100次,然后再把他显示出来。
  • clone一个DOM结点到内存里,然后想怎么改就怎么改,改完后,和在线的那个的交换一下。

3)不要把DOM结点的属性值放在一个循环里当成循环里的变量。不然这会导致大量地读写这个结点的属性。

4)尽可能的修改层级比较低的DOM。当然,改变层级比较底的DOM有可能会造成大面积的reflow,但是也可能影响范围很小。

5)为动画的HTML元件使用fixed或absoult的position,那么修改他们的CSS是不会reflow的。

6)千万不要使用table布局。因为可能很小的一个小改动会造成整个table的重新布局。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017-10-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 浏览器解析
  • 布局
  • 绘制
  • 合并渲染层
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档