掌握聚合最新动态了解行业最新趋势
API接口,开发服务,免费咨询服务

前端开发者应知必会:浏览器是如何渲染网页的

今天我们讨论的话题将专注于网页渲染以及它在Web开发中至关重要的作用。其实网上已经有许多谈论这个主题的文章了,但大多数文章提供的都是比较碎片化的信息,我需要查阅相当多的资料,才能完整地了解网页渲染。所以我决定写下这篇有一定综合性的文章。相信一方面能够帮助初学者了解网页渲染的原理,另一方面也能帮助有经验的同学细化巩固相关的知识结构。

不同的浏览器引擎运行起来会有些许差异,针对特定浏览器的具体内容会更加复杂。本文并不会涉及某个浏览器的底层原理,而是讨论一些通共的原则。

浏览器如何渲染网页

我们先来了解一下浏览器是如何对网页进行渲染的:

  1. 浏览器将从服务器获取的HTML文档构建成文档对象模型DOM(Document Object Model).

  2. 样式将被载入和解析,构成层叠样式表模型CSSOM(CSS Object Model).

  3. 在DOM和CSSOM之上,渲染树(rendering tree)将会被创建,代表一系列将被渲染的对象(这在Webkit内核中被称为renderer或者渲染对象render object,在Gecko内核中被称为框架frame)。渲染树映射除了不可见元素(例如<head>或者含有display:none;的标签)外的所有DOM结构。每一段文本字符串都将划分在不同的渲染对象中,每一个渲染对象都包含了它相应的DOM对象以及计算后的样式。换句话讲,渲染树是DOM的直观表示。

  4. 渲染树的每个元素包含的内容都是计算过的,它被称之为布局layout.浏览器使用一种流式处理的方法,只需要一次pass绘制操作就可以布局所有的元素(tables需要多次pass绘制,pass表示像素处理和顶点处理)。

  5. 最后布局完成,渲染树将转化为屏幕上的实际内容,这一步被称为绘制painting.

重绘Repaint

当页面元素样式的改变不影响元素在文档流中的位置时(例如background-color, border-color,visibility),浏览器只会将新样式赋予元素并进行重绘操作。

回流Reflow

当改变影响文档内容或者结构,或者元素位置时,回流操作就会被触发,一般有以下几种情况:

  • DOM操作(对元素的增删改,顺序变化等);

  • 内容变化,包括表单区域内的文本改变;

  • CSS属性的更改或重新计算;

  • 增删样式表内容;

  • 修改class属性;

  • 浏览器窗口变化(滚动或缩放);

  • 伪类样式激活(:hover等)。

浏览器如何优化渲染

浏览器本身会尽可能地减少其重绘或回流的次数,只更改必要的元素。例如一个position设置为absolute/fixed的元素的更改只会影响其本身和其子元素,而static的元素变化则会影响其之后的所有页面元素。

另外一项优化的技术则是在JavaScript代码运行时,浏览器会缓存所有的变化,然后只通过一次pass绘制操作来应用这些更改。例如下面这段代码只会触发一次重绘和回流:

var $body = $('body');
$body.css('padding', '1px'); // reflow, repaint
$body.css('color', 'red'); // repaint
$body.css('margin', '2px'); // reflow, repaint
// only 1 reflow and repaint will actually happen

然而,根据我们之前提到过的,获取某个元素的属性将会触发强制回流。比如我们在刚才的代码中加上一句读取元素属性的操作:

var $body = $('body');
$body.css('padding', '1px');
$body.css('padding'); // reading a property, a forced reflow
$body.css('color', 'red');
$body.css('margin', '2px');

结果就会有两次回流发生。因此,我们应该尽量合并读取元素属性的操作来优化性能。

当然也有我们不得不触发强制回流的情况。比如说对同一个元素的margin-left属性进行两次操作——开始的时候赋值100px的距离,之后为了实现动画效果,再加上transition属性将距离改变到50px.

我们先定义一个CSS类:

.has-transition {
   -webkit-transition: margin-left 1s ease-out;
      -moz-transition: margin-left 1s ease-out;
        -o-transition: margin-left 1s ease-out;
           transition: margin-left 1s ease-out;
}

之后再对页面元素进行操作:

// our element that has a "has-transition" class by default
var $targetElem = $('#targetElemId');

// remove the transition class
$targetElem.removeClass('has-transition');

// change the property expecting the transition to be off, as the class is not there
// anymore
$targetElem.css('margin-left', 100);

// put the transition class back
$targetElem.addClass('has-transition');

// change the property
$targetElem.css('margin-left', 50);

但事实上这段代码并不会像注释描述的那样运作,每条语句的操作将被缓存,只有结果会在页面上显示,所以我们就需要手动进行一次强制回流:

// remove the transition class
$(this).removeClass('has-transition');

// change the property
$(this).css('margin-left', 100);

// trigger a forced reflow, so that changes in a class/property get applied immediately
$(this)[0].offsetHeight; // an example, other properties would work, too

// put the transition class back
$(this).addClass('has-transition');

// change the property
$(this).css('margin-left', 50);

你可以在JSBin预览这个例子。

优化渲染效率的几条最佳实践

根据我查阅的一些资料,总结出以下几条优化建议:

  • 合法地书写HTML和CSS,不要忘了文档编码类型。样式文件应当在 <head> 标签中,脚本文件在 <body> 结束前。

  • 简化并优化你的CSS选择器(有些人可能CSS预处理器用习惯了从来不关注这一点)。将嵌套层减少到最小。CSS选择器根据其优先级具有不同的运行效率(从快到慢):

    • ID选择器: #id

    • 类选择器: .class

    • 标签选择器: div

    • 相邻选择器: a + i

    • 子元素选择器: ul > li

    • 通用选择器: *

    • 属性选择器: input[type="text"]

    • 伪类选择器: a:hover

浏览器中CSS选择器是从右到左进行匹配的(为什么浏览器要从右到左匹配样式选择器),这也是为什么越短的选择器运行越快的原因(别提通用选择器,它会遍历所有元素):

div * {...} // bad
.list li {...} // bad
.list-item {...} // good
#list .list-item {...} // good
  • 在你的脚本代码中,尽量减少DOM操作。缓存所有的内容,包括属性和对象(如果他们需要被复用的话)。尽量将元素缓存到本地之后再进行操作,最后再添加到DOM当中。

  • 如果你使用jQuery进行DOM操作的话,最好遵循jQuery最佳实践

  • 修改元素样式时,更改其class属性是性能最高的方法。你的选择器越有针对性越好(这同样也有助于分离页面样式和逻辑)。

  • 尽量只对 position 为 absolute/fixed 的元素设置动画。

  • 在页面滚动时禁用 :hover 样式效果:

.disable-hover {
  pointer-events: none;
}
var body = document.body,
    timer;

window.addEventListener('scroll', function() {
  clearTimeout(timer);
  if(!body.classList.contains('disable-hover')) {
    body.classList.add('disable-hover')
  }
  
  timer = setTimeout(function(){
    body.classList.remove('disable-hover')
  },500);
}, false);

如果你想对此话题进行更深入的了解,可以查阅:

  1. How browsers work 中文版

  2. 【译】浏览器渲染:repaint,reflow/relayout,restyle

有任何好的意见或者是建议以及心得体会欢迎留言。

原文来自:FreeCodeCamp

声明:所有来源为“聚合数据”的内容信息,未经本网许可,不得转载!如对内容有异议或投诉,请与我们联系。邮箱:marketing@think-land.com

  • 营运车判定查询

    输入车牌号码或车架号,判定是否属于营运车辆。

    输入车牌号码或车架号,判定是否属于营运车辆。

  • 名下车辆数量查询

    根据身份证号码/统一社会信用代码查询名下车辆数量。

    根据身份证号码/统一社会信用代码查询名下车辆数量。

  • 车辆理赔情况查询

    根据身份证号码/社会统一信用代码/车架号/车牌号,查询车辆是否有理赔情况。

    根据身份证号码/社会统一信用代码/车架号/车牌号,查询车辆是否有理赔情况。

  • 车辆过户次数查询

    根据身份证号码/社会统一信用代码/车牌号/车架号,查询车辆的过户次数信息。

    根据身份证号码/社会统一信用代码/车牌号/车架号,查询车辆的过户次数信息。

  • 风险人员分值

    根据姓名和身份证查询风险人员分值。

    根据姓名和身份证查询风险人员分值。

0512-88869195
数 据 驱 动 未 来
Data Drives The Future