加载、执行与渲染

关键渲染路径 Critical rendering path

CRP是浏览器将 HTML,CSS 和 JavaScript 转换为屏幕上的像素所经历的步骤序列。

  • 浏览器请求指定的 HTML 文件 ,服务器返回 指定的HTML文件。
  • 浏览器按HTML文件中的代码顺序进行解析,依次转换为 DOM 树。
  • 当浏览器发现有外部资源(如外联的样式、脚本或者图片、字体等资源,就会再发起一个HTTP请求。
    • 如果是阻塞类型的资源,意味着浏览器将停下来等待,直到该资源被载入和处理完毕,才会解析后面的 HTML 。
  • 浏览器接着解析 HTML,发送请求和构造 DOM 直到文件结尾,这时开始构造 CSS 对象模型。
  • 等到 DOM 和 CSSOM 完成之后,浏览器构造渲染树,计算所有可见内容的样式。
  • 一旦渲染树完成布局开始,定义所有渲染树元素的位置和大小。
  • 完成之后,页面被渲染完成,或者说是绘制到屏幕上。

浏览器的解析过程

  • 构建DOM树
  • 预加载扫描器:优化相关资源的加载
  • 构建CSSOM树
  • Javascript编译
    • 被解析、编译和解释。被解析为抽象语法树;有些浏览器引擎会进一步编译为字节码;然后在主线程上解释执行(也有例外,例如在 web worker 中运行的代码)
  • 构建无障碍树:构建对应于无障碍对象模型(AOM) 的无障碍树。

用户体验与阻塞 blocking

考虑如下场景:

  • html文档中全体资源总尺寸过大,加载时间太长,将导致白屏,即用户迟迟看不到任何有吸引力的资源
  • html文档中包含大量装饰性图片。暂时不显示这些图片不会显著影响内容的呈现
  • html文档中包含大量图片,但这些图片大多不在第一屏的可见视窗之内。

从用户角度:

  • 阻塞式体验:用户需要等待html文件上所有的资源都被加载和执行完成后才能得到渲染树,即看到最终的页面。
  • 非阻塞式体验:用户首先看到关键内容(如html文本和基本样式),再逐步看到非关键内容(美化的样式、各类图片等)

等待页面加载的时间越长,用户在页面加载完成之前离开的概率就越大.

渲染阻塞 render blocking

HTML文件中存在不同类型的资源,如html元素、CSS样式、JS代码、图片、Web字体等,并且从渲染的角度,它们之间存在着相互等待或依赖的关系。

  • CSSOM的构建必须等待相应的HTML元素被加载和构建。
  • 由于级联的特性,多段CSS代码之间也存在依赖关系,即必须等待所有样式加载后,才能得到最终的样式树。
  • JavaScript中如果包含DOM读取操作,则必须等待相应的HTML元素被加载和构建;反过来,JavaScript中如果包含DOM更新操作,则最终的DOM树必须等待JavaScript被加载和执行。
  • 多段JavaScript代码之间同样可能存在依赖关系,比如函数调用顺序,因此,调用方(caller)同样需要等待被调方(callee)被加载和执行。

资源的的加载顺序决定了页面的正确性,但同时制约了对渲染顺序的优化。

一个简单的优化方法是:将所有js、css代码置于页面底部

网络阻塞 IO blocking

大部分情况下,浏览器是单线程执行的。主线程负责html文件的解析和渲染。 由于CSS样式、JS代码、图片、Web字体等资源还存在外联的载入方式。当主线程在html文件中解析到外联资源时,有两种情况:

  • 在主线程中发起新的HTTP请求,并且等载资源加载完成后,再继续后续的解析。称之阻塞式操作。
  • 开启一个新的线程,采用异步方式发起新的HTTP请求,并且不等待响应就继续后续文档的解析,称之非阻塞式操作。
    • 见后续关于异步的讨论

由于网络连接的不可靠性,阻塞式网络IO极易出现卡死等现象;而另一方面,非阻塞式IO可以快速渲染主文档或关键内容,改善用户体验。但使得解析顺序变得不可预测,需要处理好资源之间的依赖性。

预加载扫描器

浏览器一般内置有预加载扫描器,它用来优化各类资源的加载,减少阻塞操作,即不必等到解析器遇到外部资源的引用后再对其进行请求和加载。它将在后台预先检索HTML文件中的各类资源,当主 HTML 解析器遇到相应资源时,这些资源可能已经被下载或执行。

<link rel="stylesheet" href="styles.css" />
<script src="myscript.js" async></script>
<img src="myimage.jpg" alt="图像描述" />
<script src="anotherscript.js" async></script>

本例中,图片和脚本都会被预先加载

从渲染阻塞的角度, CSS 加载不会阻塞 HTML 的加载,但是它确实会阻塞 JavaScript,因为 JavaScript 经常需要查询元素的 CSS 属性。

脚本

  1. 阻塞模式 <script src="app.js" ></script> 阻塞主线程,立即加载、立即执行

  2. 预加载模式 <script src="app.js" async></script> 通知预加载扫描器,该脚本可预加载(异步方式),但其执行时点仍然是<script>在html文件中的代码位置

  3. 延迟执行模式 <script src="app.js" defer></script>

预加载该脚本,并且在整个html文件完成解析后再执行。前端开发者习惯于将脚本放置在<head>中,但这要么会拖慢页面加载,要么会报dom元素不存在的错误,从而不得不放置在页面底端。defer属性可以很好的解决这个问题。

async、defer等属性仅限外联资源

图片

<img> 默认是阻塞式网络操作。显示一幅高精度图片会使得用户持久等待。可编写脚本实现延迟加载或懒加载:

//本脚本置于页面最底端或window.onload中
let imagesToLoad = document.querySelectorAll("img[data-src]");
const loadImages = (image) => {
  image.setAttribute("src", image.getAttribute("data-src"));
  image.onload = () => {
    image.removeAttribute("data-src");
  };
};

为了避免图片延迟加载导致的突然撑大文本,可以给每个图片预加载一个轻量级的占位图片。

其它方案还有:按需加载、渐进式加载等

CSS

默认情况下,CSS 被视为渲染阻塞资源,因此,在 CSSOM 构造完成之前,浏览器不会渲染任何已处理的内容。 所以CSS 的尺寸应当尽量小,利用尽快送达,建议使用媒体类型和查询(media query)等实现非阻塞渲染。

字体

默认情况下,对字体的请求会延迟到构造渲染树之前,这可能会导致文本渲染被延迟。

可以使用 <link rel="preload">、CSS font-display 属性和字体加载 API 来覆盖默认行为并预加载网络字体资源。

优化 CRP

提升页面加载速度需要通过被加载资源的优先级、控制它们加载的顺序和减小这些资源的体积。优化方法包括

  1. 通过异步、延迟加载或者消除非关键资源来减少关键资源的请求数量,
  2. 通过区分关键资源的优先级来优化被加载关键资源的顺序,来缩短关键路径长度。
  3. 优化必须的请求数量和每个请求的文件体积
    • 比如css精灵图或定制的icon font

参考

results matching ""

    No results matching ""