整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

「浏览器」HTML、CSS和JS如何变成页面的?

们经常写 HTML 、 CSS 和 JavaScript ,写好这些之后,我们就会在浏览器中看到页面,那浏览器究竟在这背后做了一些什么事情呢?本篇文章将揭晓答案!

了解浏览器的渲染原理是我们在通往更深层次的前端开发中不可缺少的,它可以让我们从更深层次、角度去考虑性能优化等~

下面进入正文~

进程、线程

浏览器会分配一个线程“自上而下,从左到右”依次解析和渲染代码,那么进程和线程是什么,它们之间有着怎样的关系呢?

进程

一个进程就是一个程序运行的实例。当启动一个程序的时候,操作系统会为该程序创建一块内存,用来存放代码,运行中的数据和一个执行任务的主线程,这样的一个运行环境就叫进程

线程

线程不能单独存在,它是由进程来启动和管理的。线程依附于进程,进程中使用多线程并行处理能提升运算效率

两者之间的关系

1、进程中的任意一线程执行出错,都会导致整个进程的崩溃

2、线程之间可以共享数据

3、当一个进程关闭后,操作系统会回收进程所占用的内存

4、进程之间的内容相互隔离

渲染机制

从HTML、CSS和JavaScript开始

了解浏览器的渲染原理,我们就要从理解 HTML 、 CSS 和 JavaScrip 开始,我们先来看一张图

HTML (超文本标记语言),顾名思义,由标记(标签)和文本组成,每个标签都有自己的语意,浏览器会根据标签和文本展示对应的内容。

CSS (层叠样式表),由选择器和属性组成,它可以改变 HTML 的样式,比如上图中,我们改变了 span 的颜色由蓝色为绿色。

JavaScript ,我们可以通过 JS 完成很多事情,例如上图中修改样式。

下面开始分析渲染的原理

渲染流水线

渲染模块由于渲染的机制的复杂,被划分为了很多子阶段,输入的 HTML 经过这些子阶段,最后会输出为像素。这样的处理流程就叫做 渲染流水线

按照渲染的时间顺序,流水线可分为几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成

构建DOM树

由于浏览器无法直接理解和使用 HTML ,所以需要将 HTML 转换为浏览器能够理解的结构( DOM 树)

树结构示意图

DOM树的构建过程

我们来分析一下下面这段代码会构建出一棵什么样的 DOM 树

我们先将上面的代码运行,然后在浏览器控制台输入 document ,看看会有什么效果

我们一层级一层级的打开就会看到如上图的效果,我们可以根据这每一层级展开的效果,绘制出一棵 DOM 树结构,如下:

接下来,我们试一下利用 JS 修改一下内容,看有什么改变:

我们可以看到“浏览器”的文字变成了“chrome”

再来看一下 DOM 树是否有改变

我们看到在“浏览器”的位置换成了“chrome”,那么如何让 DOM 节点拥有样式?

样式计算

样式计算,顾名思义,就是 计算出 DOM 节点中每个元素的具体样式 ,这个阶段会分为三部分:

  • 把 CSS 转换为浏览器能够理解的结构
  • 转换样式表中的属性值,使其标准化
  • 计算出 DOM 树中每个节点的样式

CSS样式来源

  • link 导入外部样式资源

浏览器会新开辟一个线程,去服务器获取对应的资源文件(不阻碍主线程的渲染)

  • style 内嵌样式

从上到下解析,解析完继续解析 DOM 结构。在真实项目中,如果 css 代码不是很多,或是移动端项目,我们应该使用内嵌式,以此来减少 http 资源的请求,提高页面渲染速度

  • 行内样式
  • @import 导入

它是同步的,不会开辟新线程去加载资源文件,而是让主线程去获取,这阻碍 DOM 结构的继续渲染;只有把外部样式导入进来,并且解析后,才会继续渲染 DOM 结构

把CSS转换为浏览器能够理解的结构

浏览器就像不能理解 HTML 一样,不理解 CSS ,所以当渲染引擎接收到 CSS 文件时,会执行转换操作,将 CSS 文本转换为浏览器可以理解的 styleSheets 结构。

在 HTML 中,在浏览器中输入 document 可以查看 html 的结构。在 css 中,可以输入 document.styleSheets 看到 css 的结构

现在的结构是空的,我们来加一些样式,看看效果

转换样式表中的属性值,使其标准化

属性值标准化就是将所有值转换为渲染引擎容易理解的、标准化的计算值。我们大致看一下效果:

  • 标准化前
body {
    font-size: 2em;
    color: black;
    font-weight: bold;
    ...
}
复制代码
  • 标准化后
body {
    font-size: 16px;
    color: rgb(0, 0, 0);
    font-weight: 700;
    ...
}
复制代码

计算出DOM树中每个节点的具体样式

样式计算有两个CSS的规则:继承规则和层叠规则

  • CSS继承规则

CSS 继承就是每个 DOM 节点都包含有父节点的样式。我们来看一下下面这段代码中如何应用到 DOM 节点上

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        h1 {
            color: red;
        }

        div {
            color: blue;
        }

        span {
            font-size: 16px;
        }
    </style>
</head>
<body>
    <h1>掘金</h1>
    <div>
        <span>浏览器</span>
        <span>渲染原理</span>
        构建DOM树
    </div>
</body>
</html>
复制代码

子节点会拥有父节点的样式,由此我们可以画出这样一张图

我们还可以打开控制台,看一下选中 span 标签,都会看到哪些内容

通过上图,我们可看到一个元素的样式、继承过程等, userAgent 样式是浏览器默认的内置样式,如果我们不提供任何样式,就会使用此样式。

  • 样式层叠规则

层叠在 CSS 处于核心地位,它是 CSS 的一个基本特征,它定义了如何合并来自多个源的属性值的算法。

样式计算阶段最终输出的内容是每个 DOM 节点的样式,并且保存在了 ComputedStyle 中。我们可以通过控制台看到某个 DOM 元素最终的计算样式

布局阶段

现在我们不知道 DOM 元素的几何位置信息,所以现在我们需要计算出 DOM 树中可见元素的几何位置,这个计算过程就叫做布局。布局阶段有两个过程:

  • 创建布局树
  • 布局计算

创建布局树

创建布局树的意思就是创建一棵只包含可见元素的树。我们来看下面一段代码创建布局树的过程

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        h1 {
            color: red;
        }

        div {
            color: blue;
        }

        div span {
            font-size: 16px;
        }

        div span:last-child {
            display: none;
        }
    </style>
</head>
<body>
    <h1>掘金</h1>
    <div>
        <span>浏览器</span>
        <span>渲染原理</span>
        构建DOM树
    </div>
</body>
</html>
复制代码

构建布局树的过程中, DOM 树中所有不可见的节点都不会包含在这棵树中。浏览器会遍历 DOM 树中所有能看见的节点,然后把这些节点加入到布局中;不可见的节点就会被忽略, head 标签下面的内容、 div 下最后一个 span 节点都不会在布局树中,我们看一下这个过程图感受一下~

布局计算

布局计算就是计算布局树节点的坐标位置。这个计算过程极为复杂。

分层

渲染引擎会为特定的节点生成专用的图层,并生成一棵对应的图层树。这样做是因为页面中可能含有很多复杂的效果,我们可以打开控制台看一下页面的分层情况

我们可以看到,渲染引擎给页面分了很多图层,这些图层会按照一定顺序叠加在一起,形成最终的页面

那么图层的来源有哪些?

1、拥有层叠上下文属性的元素会被提升为单独的一层

层叠上下文可以使能够使 HTML 元素具有三维的概念,这些 HTML 元素按照自身属性的优先级分布在垂直于这个二维平面的 z 轴上。哪些元素具有层叠上下文属性?

2、需要剪裁的地方会被创建为图层

当我们创建一个有宽度和高度的 div 时,里面的文字内容可能会超出这个区域,这时候渲染引擎会把裁剪文字内容的一部分用于显示在 div 区域,例如

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        div {
            width: 100px;
            height: 100px;
            background: yellow;
            overflow: auto;
        }
    </style>
</head>
<body>
    <div>
        我们经常写`HTML`、`CSS`和`JavaScript`,写好这些之后,我们就会在浏览器中看到页面,那浏览器究竟在这背后做了一些什么事情呢?本篇文章将揭晓答案!

        了解浏览器的渲染原理是我们在通往更深层次的前端开发中不可缺少的,它可以让我们从更深层次、角度去考虑性能优化等~
    </div>
</body>
</html>
复制代码

运行结果

我们再打开控制台的 layers 看一下效果

可以看到渲染引擎为文字部分单独创建了一个图层。

在布局树中的节点如果拥有对应的图层,这个节点就是一个图层,如果没有,这个节点就属于父节点的图层,如下图:

图层绘制

创建好图层树后,渲染引擎会绘制图层树中的每个图层。渲染引擎会将图层绘制分解为很多小的绘制指令,然后将这些指令按照顺序组成待绘制列表,我们可以打开控制台的 layers ,选择 document 层,看一下效果

栅格化操作

栅格化就是将图块转换位位图,图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有图块的栅格化都是在线程池内执行的。

图层绘制列表准备好之后,主线程会把这个绘制列表提交给合成线程,绘制操作由渲染引擎中的合成线程来完成。

合成线程将图层划分为图块,然后合成线程会按照视口(可见区域)附近的图块优先生成位图。

合成与显示

所有的图块都被光栅化后,合成线程会生成一个绘制图块的命令( DrawQuad ),然后将该命令提交给浏览器进程。浏览器进程里面 viz 组件用来接收 DrawQuad 命令,将其页面内容绘制到内存中,最后将内存显示到屏幕。这个时候,我们就看到了页面

完善渲染流水线示意图

根据上文中描述,我们可以画出这样一张图

我还在网上找到了另外一张图

这两张图都是描述浏览器的渲染流程的。

家好,我是皮皮。

前言

对于前端来说,HTML 都是最基础的内容。

今天,我们来了解一下 HTML 和网页有什么关系,以及与 DOM 有什么不同。通过本讲内容,你将掌握浏览器是怎么处理 HTML 内容的,以及在这个过程中我们可以进行怎样的处理来提升网页的性能,从而提升用户的体验。


一、浏览器页面加载过程

不知你是否有过这样的体验:当打开某个浏览器的时候,发现一直在转圈,或者等了好长时间才打开页面……

此时的你,会选择关掉页面还是耐心等待呢?

这一现象,除了网络不稳定、网速过慢等原因,大多数都是由于页面设计不合理导致加载时间过长导致的。

我们都知道,页面是用 HTML/CSS/JavaScript 来编写的。

  • HTML 的职责在于告知浏览器如何组织页面,以及搭建页面的基本结构;
  • CSS 用来装饰 HTML,让我们的页面更好看;
  • JavaScript 则可以丰富页面功能,使静态页面动起来。

HTML由一系列的元素组成,通常称为HTML元素。HTML 元素通常被用来定义一个网页结构,基本上所有网页都是这样的 HTML 结构:

<html>
    <head></head>
    <body></body>
</html>

其中:

  • html元素是页面的根元素,它描述完整的网页;
  • head元素包含了我们想包含在 HTML 页面中,但不希望显示在网页里的内容;
  • body元素包含了我们访问页面时所有显示在页面上的内容,是用户最终能看到的内容;


HTML 中的元素特别多,其中还包括可用于 Web Components 的自定义元素。

前面我们提到页面 HTML 结构不合理可能会导致页面响应慢,这个过程很多时候体现在<script><style>元素的设计上,它们会影响页面加载过程中对 Javascript 和 CSS 代码的处理。

因此,如果想要提升页面的加载速度,就需要了解浏览器页面的加载过程是怎样的,从根本上来解决问题。

浏览器在加载页面的时候会用到 GUI 渲染线程和 JavaScript 引擎线程(更详细的浏览器加载和渲染机制将在第 7 讲中介绍)。其中,GUI 渲染线程负责渲染浏览器界面 HTML 元素,JavaScript 引擎线程主要负责处理 JavaScript 脚本程序。

由于 JavaScript 在执行过程中还可能会改动界面结构和样式,因此它们之间被设计为互斥的关系。也就是说,当 JavaScript 引擎执行时,GUI 线程会被挂起。

以网易云课堂官网为例,我们来看看网页加载流程。

(1)当我们打开官网的时候,浏览器会从服务器中获取到 HTML 内容。

(2)浏览器获取到 HTML 内容后,就开始从上到下解析 HTML 的元素。

(3)<head>元素内容会先被解析,此时浏览器还没开始渲染页面。

我们看到<head>元素里有用于描述页面元数据的<meta>元素,还有一些<link>元素涉及外部资源(如图片、CSS 样式等),此时浏览器会去获取这些外部资源。除此之外,我们还能看到<head>元素中还包含着不少的<script>元素,这些<script>元素通过src属性指向外部资源。

(4)当浏览器解析到这里时(步骤 3),会暂停解析并下载 JavaScript 脚本。

(5)当 JavaScript 脚本下载完成后,浏览器的控制权转交给 JavaScript 引擎。当脚本执行完成后,控制权会交回给渲染引擎,渲染引擎继续往下解析 HTML 页面。

(6)此时<body>元素内容开始被解析,浏览器开始渲染页面。

在这个过程中,我们看到<head>中放置的<script>元素会阻塞页面的渲染过程:把 JavaScript 放在<head>里,意味着必须把所有 JavaScript 代码都下载、解析和解释完成后,才能开始渲染页面。

到这里,我们就明白了:如果外部脚本加载时间很长(比如一直无法完成下载),就会造成网页长时间失去响应,浏览器就会呈现“假死”状态,用户体验会变得很糟糕。

因此,对于对性能要求较高、需要快速将内容呈现给用户的网页,常常会将 JavaScript 脚本放在<body>的最后面。这样可以避免资源阻塞,页面得以迅速展示。我们还可以使用defer/async/preload等属性来标记<script>标签,来控制 JavaScript 的加载顺序。

百度首页

三、DOM 解析

对于百度这样的搜索引擎来说,必须要在最短的时间内提供到可用的服务给用户,其中就包括搜索框的显示及可交互,除此之外的内容优先级会相对较低。

浏览器在渲染页面的过程需要解析 HTML、CSS 以得到 DOM 树和 CSS 规则树,它们结合后才生成最终的渲染树并渲染。因此,我们还常常将 CSS 放在<head>里,可用来避免浏览器渲染的重复计算。


二、HTML 与 DOM 有什么不同

我们知道<p>是 HTML 元素,但又常常将<p>这样一个元素称为 DOM 节点,那么 HTML 和 DOM 到底有什么不一样呢?

根据 MDN 官方描述:文档对象模型(DOM)是 HTML 和 XML 文档的编程接口。

也就是说,DOM 是用来操作和描述 HTML 文档的接口。如果说浏览器用 HTML 来描述网页的结构并渲染,那么使用 DOM 则可以获取网页的结构并进行操作。一般来说,我们使用 JavaScript 来操作 DOM 接口,从而实现页面的动态变化,以及用户的交互操作。

在开发过程中,常常用对象的方式来描述某一类事物,用特定的结构集合来描述某些事物的集合。DOM 也一样,它将 HTML 文档解析成一个由 DOM 节点以及包含属性和方法的相关对象组成的结构集合。


三、DOM 解析

我们常见的 HTML 元素,在浏览器中会被解析成节点。比如下面这样的 HTML 内容:

<html>
    <head>
        <title>标题</title>
    </head>
    <body>
        <a href='xx.com'>我的超链接</a>
        <h1>页面第一标题</h1>
    </body>
</html>

打开控制台 Elements 面板,可以看到这样的 HTML 结构,如下图所示:

在浏览器中,上面的 HTML 会被解析成这样的 DOM 树,如下图所示:


我们都知道,对于树状结构来说,常常使用parent/child/sibling等方式来描述各个节点之间的关系,对于 DOM 树也不例外。

举个例子,我们常常会对页面功能进行抽象,并封装成组件。但不管怎么进行整理,页面最终依然是基于 DOM 的树状结构,因此组件也是呈树状结构,组件间的关系也同样可以使用parent/child/sibling这样的方式来描述。同时,现在大多数应用程序同样以root为根节点展开,我们进行状态管理、数据管理也常常会呈现出树状结构。


四、事件委托

我们知道,浏览器中各个元素从页面中接收事件的顺序包括事件捕获阶段、目标阶段、事件冒泡阶段。其中,基于事件冒泡机制,我们可以实现将子元素的事件委托给父级元素来进行处理,这便是事件委托。

如果我们在每个元素上都进行监听的话,则需要绑定三个事件;(假设页面上有a,b,c三个兄弟节点)

function clickEventFunction(e) {
  console.log(e.target === this); // logs `true`
  // 这里可以用 this 获取当前元素
}
// 元素a,b,c绑定
element2.addEventListener("click", clickEventFunction, false);
element5.addEventListener("click", clickEventFunction, false);
element8.addEventListener("click", clickEventFunction, false);

使用事件委托,可以通过将事件添加到它们的父节点,而将事件委托给父节点来触发处理函数:

function clickEventFunction(event) {
  console.log(e.target === this); // logs `false`
  // 获取被点击的元素
  const eventTarget = event.target;
  // 检查源元素`event.target`是否符合预期
  // 此处控制广告面板的展示内容
}
// 元素1绑定
element1.addEventListener("click", clickEventFunction, false);

这样能解决什么问题呢?

  • 绑定子元素会绑定很多次的事件,而绑定父元素只需要一次绑定。
  • 将事件委托给父节点,这样我们对子元素的增加和删除、移动等,都不需要重新进行事件绑定。

常见的使用方式主要是上述这种列表结构,每个选项都可以进行编辑、删除、添加标签等功能,而把事件委托给父元素,不管我们新增、删除、更新选项,都不需要手动去绑定和移除事件。

如果在列表数量内容较大的时候,对成千上万节点进行事件监听,也是不小的性能消耗。使用事件委托的方式,我们可以大量减少浏览器对元素的监听,也是在前端性能优化中比较简单和基础的一个做法。

注意:

  1. 如果我们直接在document.body上进行事件委托,可能会带来额外的问题;
  2. 由于浏览器在进行页面渲染的时候会有合成的步骤,合成的过程会先将页面分成不同的合成层,而用户与浏览器进行交互的时候需要接收事件。此时,浏览器会将页面上具有事件处理程序的区域进行标记,被标记的区域会与主线程进行通信。
  3. 如果我们document.body上被绑定了事件,这时候整个页面都会被标记;
  4. 即使我们的页面不关心某些部分的用户交互,合成器线程也必须与主线程进行通信,并在每次事件发生时进行等待。这种情况,我们可以使用passive: true选项来解决


五、总结

我们了解了 HTML 的作用,以及它是如何影响浏览器中页面的加载过程的,同时还介绍了使用 DOM 接口来控制 HTML 的展示和功能逻辑。我们了解了DOM解析事件委托等相关概念。

面制作技术详解


演示视频在文章底部

1.页面内容居中显示方法

将这段代码<div style="width:50%;margin:auto;">放置在<body>标签之下。

将</div>放置在</body>之上。

将全部内容包裹在这个div中,就可以实现整个页面居中。

内容显示宽度为浏览器视窗宽度的50%。

margin(外边距)是在CSS布局中经常用到的属性,它指定了该div元素距离四周的距离。使用“auto”值,可以实现居中。

2.导航栏悬停顶端方法

把四个a标签装到一个div中。

将<div style="position:fixed; top:0px;">添加到<a style="margin: 0px 30px 0px 10px;" href="#chapter1">试飞进程</a>之上。

将</div>添加到<a style="margin: 0px 30px 0px 0px;"href="#chapter4">总体评价</a>之下。

position是css布局中指定位置的属性,“fixed”值是让该div悬停于固定位置。

默认下,该div距离视窗顶端有10px左右的距离,因此为了让它与视窗顶部对齐,添加top:0px。

3.鼠标滑过导航标题或链接时改变背景色提示

这就要介绍关于css的写法了。

简单来说,就是在<head></head>标签中添加

<style>

a:hover

{

background-color:#ffff00;

}

</style>

学过HTML页面中head标签有啥用?——零基础自学网页制作的小伙伴应该知道,CSS脚本是可以添加在head元素中的。

其中,a:hover中的a指的是所有<a></a>标签。

hover指的是:当鼠标悬停在a上面时的状态

使用:连接。

这个状态下要执行的内容在{}中。

background-color:#ffff00;即背景色为黄色。

3.隐藏滚动条方法

首先,我们要明确一点,就是,滚动条是在内容长度超过视窗高度时产生的。

如果要取消视窗最右侧滚动条,就要控制内容高度。

把<p></p>和<img/><map></map>全部装进<div></div>中,控制该div的高度可以实现。

在<p>标签色上面添加<div>。

在</map>标签下面添加</div>。

下面,为div规定尺寸,添加style="width:610px; height:530px;"。

这样,就不会超出视窗。但是代码写完后发现并不是,如图:

多出的文字内容超出div范围,右侧滚动条依然存在。

这就要在div的style中再增加一条语句"overflow-y:scroll;"

这句话的意思是“overflow-y”(超出最大高度)就显示滚动条(scroll)。而不是让内容超出div的边框。

<div style="width:610px; height:530px; overflow-y:scroll;" >

如图:

因为图片宽度的问题,下方的x轴的scroll也出现了,我们不想看到它,影响美观。

添加“overflow-x:hidden”即可,hidden(隐藏)。

<div style="width:610px; height:530px; overflow-y:scroll; overflow-x:hidden;" >

如图:hidden之后,将无法滚动或拖动画面。

最后,我们要把右侧的scroll也隐藏掉,因为点击鼠标,滚动滚轮就够了,滚动条实在碍眼。

从前面的例子可知,hidden是不行的,有没有别的办法?

那就是在盖div的外部再添加一个div,让这个div的宽度略小于里面div的宽度,小到刚刚挡住滚动条既可以。如图:

这个div这样写即可

<div style="margin:30px 0px 0px 0px;width:600px;overflow:hidden;">

</div>

同时还要给里面的div添加margin来让它们对齐

<div style="margin:30px 0px 0px 0px;width:600px;overflow:hidden;">

<div style="margin:0px 0px 0px 8px;width:610px; height:530px; overflow-y:scroll;overflow-x:hidden;" >

<!--省略了p img map 请自行脑补或参考源码-->

</div>

</div>

完整代码:用HTML制作一个简单页面(代码阅读练习)——零基础自学网页制作