美文网首页
浅谈web特性~

浅谈web特性~

作者: LORI地盘 | 来源:发表于2017-03-31 22:28 被阅读0次

    导语:

    文中多处引用牛人博客观点,重在分享知识和自己的一些见解~

    一,浏览器渲染页面流程

    1.浏览器解析html源码,然后创建一个 DOM树。
    在DOM树中,每一个HTML标签都有一个对应的节点,并且每一个文本也都会有一个对应的文本节点。DOM树的根节点就是 documentElement,对应的是html标签。

    2.浏览器解析CSS代码,计算出最终的样式数据。
    对CSS代码中非法的语法她会直接忽略掉。解析CSS的时候会按照如下顺序来定义优先级:浏览器默认设置 < 用户设置 < 外链样式 < 内联样式 < html中的style。

    3.构建出DOM树,并且计算出样式数据后,下一步就是构建一个 渲染树(rendering tree)。渲染树和DOM树有点像,但是是有区别的。DOM树完全和html标签一一对应,但是渲染树会忽略掉不需要渲染的元素,比如head、display:none的元素等。而且一大段文本中的每一个行在渲染树中都是独立的一个节点。渲染树中的每一个节点都存储有对应的css属性。

    4.一旦渲染树创建好了,浏览器就可以根据渲染树直接把页面绘制到屏幕上。

    重绘和重排(repaints and reflows)

    每个页面至少在初始化的时候会有一次重排操作。任何对渲染树的修改都有可能会导致下面两种操作:

    1.重排就是渲染树的一部分必须要更新 并且节点的尺寸发生了变化。这就会触发重排操作。

    2.重绘部分节点需要更新,但是没有改变他的集合形状,比如改变了背景颜色,这就会触发重绘。什么情况下会触发重绘或重排

    下面任何操作都会触发重绘或者重排:

    • 增加或删除DOM节点设置 display: none;(重排并重绘) 或visibility: hidden(只有重排)
    • 移动页面中的元素
    • 增加或者修改样式用户
    • 改变窗口大小
    • 滚动页面等

    减少重绘和重排

    1.不要一个一个地单独修改属性,最好通过一个classname来定义这些修改

    // badvar left = 10, top = 10;
    el.style.left = left + "px";
    el.style.top = top + "px";
    // better el.className += " theclassname";

    2.把对节点的大量修改操作放在页面之外用 documentFragment来做修改clone 节点,在clone之后的节点中做修改,然后直接替换掉以前的节点通过 display: none 来隐藏节点(直接导致一次重排和重绘),做大量的修改,然后显示节点(又一次重排和重绘),总共只会有两次重排。

    3.不要频繁获取计算后的样式。如果你需要使用计算后的样式,最好暂存起来而不是直接从DOM上读取。

    4.总的来说,总是考虑到渲染树得存在,考虑到你的一次修改会导致多大的绘制操作。比如绝对定位元素的动画就不会影响其他大部分元素。

    有个小段子:

    1.用户输入网址(假设是个html页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回html文件;

    2.浏览器开始载入html代码,发现<head>标签内有一个<link>标签引用外部CSS文件;

    3.浏览器又发出CSS文件的请求,服务器返回这个CSS文件;

    4.浏览器继续载入html中<body>部分的代码,并且CSS文件已经拿到手了,可以开始渲染页面了;

    5.浏览器在代码中发现一个<img>标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码;

    6.服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码;

    7.浏览器发现了一个包含一行Javascript代码的<script>标签,赶快运行它;

    8.Javascript脚本执行了这条语句,它命令浏览器隐藏掉代码中的某个<div> (style.display=”none”)。杯具啊,突然就少了这么一个元素,浏览器不得不重新渲染这部分代码;

    9.终于等到了</html>的到来,浏览器泪流满面……

    10.等等,还没完,用户点了一下界面中的“换肤”按钮,Javascript让浏览器换了一下<link>标签的CSS路径;

    11.浏览器召集了在座的各位<div><span><ul><li>们,“大伙儿收拾收拾行李,咱得重新来过……”,浏览器向服务器请求了新的CSS文件,重新渲染页面。

    有个小例子:

    以下代码在Chrome 51 Release版的Timeline

    
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title></title>
      <link rel="stylesheet" href="index.css">
    </head>
    <body>
    </body>
    <script>
      document.querySelector('body').innerHTML = 'hello world';
    </script>
    </html>
    
    

    页面的加载过程为:

    0.Event(beforeunload)

    1.Send Request

    2.Receive Response

    3.Receive Data

    4.Event(pagehide)

    5.Event(visibilitychange)

    6.Event(webkitvisibilitychange)

    7.Event(unload)

    8.Event(readystatechange)

    9.Finish loading

    10.Parse HTML

    11.Update Layer Tree

    12.Paint

    13.firstPaint

    14.firstContentfullPaint

    15.composite Layers

    16.Major GC(0 B collected)

    17.Minor GC(123 KB collected)

    注:
    GC,即垃圾回收。

    如果你还不太明白,这个是最后的解释:

    网页渲染必须在很早的阶段进行,可以早到页面布局刚刚定型。因为样式和脚本都会对网页渲染产生关键性的影响。所以专业开发者必须了解一些技巧,从而避免在实践的过程中遇到性能问题。

    渲染引擎的主要目的就是从一个网页的URL开始,经过一系列的复杂处理过程之后,变成一个可视化的结果,这一过程就是这里所说的页面渲染的基本过程。
    所谓的渲染,就是根据描述或者定义构建数学模型,通过模型生成图像的过程。浏览器的渲染引擎就是能够将HTML/CSS/JavaScript转换成图像结果的模块,如下图所示,输入是URL对应的各种资源,输出是可视化的图像。从这里看,非常的简单和容易理解。

    URL对应的各种资源,输出是可视化的图像。从这里看,非常的简单和容易理解。

    模型.png

    渲染模块

    那么渲染引擎提供了哪些功能模块来支持页面渲染的呢?下图是一个渲染引擎所包含的基本功能和它们依赖的一些第三方库。

     引擎 
    
    渲染.png

    从图中大致可以看出,一个渲染引擎大致包括HTML解释器,CSS解释器,布局和JavaScript引擎。下面依次来描述它们:
    HTML解释器:解释HTML语言的解释器,本质是将HTML文本解释成DOM(文档对象模型)树。
    CSS解释器:解释样式表的解释器,其作用是将DOM中的各个元素对象加上样式信息,从而为计算最后结果的布局提供依据。
    布局:DOM之后,需要将其中的元素对象同样式信息结合起来,计算它们的大小位置等布局信息,形成一个能够表示这所有信息的内部表示模型。
    JavaScript引擎:JavaScript可以修改网页的内容,也能修改CSS的信息,JavaScript引擎解释JavaScript代码并把代码的逻辑和对DOM和CSS的改动信息应用到布局中去,从而改变渲染的结果。
    这些模块依赖很多其他的基础模块,这其中包括网络,存储,2D/3D图形,音频视频和图片解码器等。实际上,渲染引擎中还应该包括如何使用这些依赖模块的部分,这部分的工作其实并不少,因为需要使用它们来高效的渲染网页。例如,利用2D/3D图形库来实现高性能的网页绘制和网页的3D渲染,这个实现非常非常的复杂。最后,当然,在最下面,依然少不了操作系统的支持,例如线程支持,文件支持等等。

    基本过程

    浏览器是如何完成网页渲染?

    首先,我们回顾一下网页渲染时,浏览器的动作:

    根据来自服务器端的HTML代码形成文档对象模型(DOM)。
    加载并解析样式,形成CSS对象模型。
    在文档对象模型和CSS对象模型之上,创建一棵由一组待生成渲染的对象组成的渲染树(在Webkit中这些对象被称为渲染器或渲染对象,而在Gecko中称之为“frame”。)渲染树反映了文档对象模型的结构,但是不包含诸如标签或含有display:none属性的不可见元素。在渲染树中,每一段文本字符串都表现为独立的渲染器。每一个渲染对象都包含与之对应的DOM对象,或者文本块,还加上计算过的样式。换言之,渲染树是一个文档对象模型的直观展示。
    对渲染树上的每个元素,计算它的坐标,称之为布局。浏览器采用一种流方法,布局一个元素只需通过一次,但是表格元素需要通过多次。
    最后,渲染树上的元素最终展示在浏览器里,这一过程称为“painting”。
    当用户与网页交互,或者脚本程序改动修改网页时,前文提到的一些操作将会重复执行,因为网页的内在结构已经发生了改变。

    Repaint

    当改变那些不会影响元素在网页中的位置的元素样式时,譬如background-color(背景色), border-color(边框色), visibility(可见性),浏览器只会用新的样式将元素重绘一次(这就是重绘,或者说重新构造样式)。

    Reflow

    当改变影响到文本内容或结构,或者元素位置时,重排或者说重新布局就会发生。这些改变通常由以下事件触发:

    DOM操作(元素添加、删除、修改或者元素顺序的改变);
    内容变化,包括表单域内的文本改变;
    CSS属性的计算或改变;
    添加或删除样式表;
    更改“类”的属性;
    浏览器窗口的操作(缩放,滚动);
    伪类激活(悬停)。
    浏览器如何优化渲染?

    浏览器尽可能将 repaint/reflow 限制在被改变元素的区域内。比如,对于位置固定或绝对的元素,其大小改变只影响元素本身及其子元素,然而,静态定位元素的大小改变会触发后续所有元素的重流。

    另一种优化技巧是,在运行几段JavaScript代码时,浏览器会缓存这些改变,在代码运行完毕后再将这些改变经一次通过加以应用。举个例子,下面这段代码只会触发一个reflow和repaint:

    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’); 
    
    

    其结果就是,重排发生了两次。因此,你应该把访问元素属性的操作都组织在一起,从而优化网页性能。

    有时,你必须触发一个强制性重排。比如,我们必须将同样的属性(比如左边距)两次赋值给同一个元素。起初,它应该设置为100px,且不带动效。接着,它必须通过过渡(transition)动效改变为50px。你现在可以在JSbin上学习这个例子,不过我会在这儿更详细地介绍它。

    首先,我们创建一个带过渡效果的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);
    

    现在代码如预期那样执行了。

    二,性能优化

    创建有效的HTML和CSS文件,不要忘记指明文档的编码方式。样式应该包含在标签内,脚本代码则应该加在标签末端。
    尽量简化和优化CSS选择器(这种优化方式几乎被使用CSS预处理器的开发者统一忽视了)将嵌套程度保持在最低水平。以下是CSS选择器的性能排名(从最快者开始):

    • 识别器:#id
    • 类:.class
    • 标签:div
    • 相邻兄弟选择器:a + i
    • 父类选择器:ul> li
    • 通用选择器:*
    • 属性选择:input[type=”text”]
    • 伪类和伪元素:a:hover

    你应该记住,浏览器在处理选择器时依照从右到左的原则,因此最右端的选择器应该是最快的:#id或则.class

    • div * {…} // bad
    • .list li {…} // bad
    • .list-item {…} // good
    • list .list-item {…} // good

    在你的脚本代码中,尽可能减少DOM操作。缓存所有东西,包括元素属性以及对象(如果它们被重用的话)。当进行复杂的操作时,使用“孤立”元素会更好,之后可以将其加到DOM中(所谓“孤立”元素是与DOM脱离,仅保存在内存中的元素)。
    如果你使用jQuery来选择元素,请遵从jQuery选择器最佳实践方案。
    为了改变元素的样式,修改“类”的属性是奏效的方法之一。执行这一改变时,处在DOM渲染树的位置越深越好(这还有助于将逻辑与表象脱离)。
    尽量只给位置绝对或者固定的元素添加动画效果。
    在使用滚动时禁用复杂的悬停动效(比如,在中添加一个额外的不悬停类)
    了解模块之后,下面就是这些模块如何组织以达成渲染过程的。一般地,一个典型的渲染过程下图所示,这是渲染引擎的核心过程,一切都是围绕着它来的。

    guo
    
    引擎.png

    下面逐个从左至右来解释上图中的这一过程。这一过程的先后关系由图中的实线箭头表示。左上角开始,首先是网页内容,送到HTML解释器。HTML解释器在解释它后形成DOM树,中间如果遇到JavaScript代码则交给JavaScript引擎去处理。如果页面包含CSS,则交给CSS解释器去解析。当DOM建立的时候,接受来自CSS解释的样式信息,构建一个新的内部绘图模型。该模型由布局模块计算模型内部的各个元素的位置和大小信息,最后由绘图模块完成从该模型到图像的绘制。
    最后解释图中虚线箭头的指向含义。它们表示在渲染过程中,每个阶段可能使用到的其他模块。在网页内容的下载中,需要使用到网络和存储,这个是显而易见地。但计算布局和绘图的时候,需要使用2D/3D的图形模块,同时因为要生成最后的可视化结果,这时候需要开始解码音频视频和图片,同其它内容一起绘制到最后的图像中。
    在渲染完成之后,用户可能需要跟渲染的结果进行交互,或者网页自身有动画,一般而言,这会持续的重新渲染过程。这个过程跟上面类似,不再赘述。

    在此特别感谢inyiyi的博客,学习很多~inyiyi的博客

    相关文章

      网友评论

          本文标题:浅谈web特性~

          本文链接:https://www.haomeiwen.com/subject/urwvottx.html