Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

浏览器系列 - 简单读懂渲染Timeline #27

Open
HXWfromDJTU opened this issue Oct 18, 2020 · 0 comments
Open

浏览器系列 - 简单读懂渲染Timeline #27

HXWfromDJTU opened this issue Oct 18, 2020 · 0 comments

Comments

@HXWfromDJTU
Copy link
Owner

HXWfromDJTU commented Oct 18, 2020

我们知道在浏览器渲染中,页面渲染有几个关键的时刻比如说First PaintDOMContentLoadedOnload以及可交互时间

打开我们亲爱的淘宝页面,使用devtools中的Performance面板录制一段从初始加载到完成的过程,可以看出各个资源的下载和执行的过程,也能看到Chrome给我们标出了所需要注意的几个关键时间点。

DOMContentLoaded

直接看字面意思,就是DOM的内容加载(解析)完毕了。而据我们之前所知,页面中脚本(无论是外链还是内联)的执行都会阻碍DOM的解析,也就是说脚本的执行,会延迟DOMContentLoaded事件的到来。

如上图所示,DOM的解析阻塞于脚本的加载,而脚本的加载也受限于脚本前面的css加载完成后才会执行,在任何情况下,DOMContentLoaded的触发不需要等待图片或者其他任何资源的加载完成。

这里插一个题外话,async标明的脚本不知道何时会加载完,而后立即执行,所以DOMContentLoaded事件也不会等它。但type=moduledefer标明的<script>标签脚本一定会先于DOMContentLoaded事件。

以下代码都是我们熟悉的用于监听DCL事件

// jQuery
$(document).ready(function(){......}); // 或者
$(function(){...});
// 原生
document.addEventListener('DOMContentLoaded',function(){......})
Q:我们把script沉到body后面可以让DOMContentLoaded提前吗?

首先回答是不可以的。
因为DCL的定义是整个文档都加载完成,当然也包括body外,HTML内的script标签。
但是我们要是把script标签放到了header中,往细说是阻塞了body的解析,那么body中有啥?当然就是我们页面的主要内容结构啦。

理论上浏览器会等待DOM和CSSOM都解析完生成RenderTree才开始布局和绘制,但是现代的浏览器,为了减少白屏等待的时间,都会进行HTML局部的渲染。

上面的截图同样来自于淘宝首页,我们可以看到在DOMContentLoaded之前,就已经触发了FirstPaint,页面空白的时间的不到30ms。但是DOM远远没有解析完,只是部分完成了。这个过程中,我们发现并没有表示script执行的黄色片段。

下面我们来看看www.hoopchina.com 虎扑网的首页

�所以我们将script标签写在header中则会阻塞body的解析,也就是会阻碍First Paint的到来,也就是说用户看到的白屏时间会更长。

所以呢,js代码沉底,只是提前 First Content Paint 的时间,而不是减少 DOMContentLoaded 的时间。

兼容性(ie滚粗)

看到图中红色的块块了吗?...在ie 6-8下,请做以下兼容

 document.onreadystatechange=function(){
   /*dom加载完成的时候*/
   if(document.readyState=='complete'){
       fn&&fn();//处理事情
     }
   };

�既然上面提到了FirstPaint那么我么就先说说First�Paint相关的知识。

FirstPaint

首先呢,Chrome 的devtools给我们细微的划分了FirstPaint为First ���Contentful Paint(首次有内容的渲染)和First Meaningful Paint(首次有效的渲染)。

FirstPaint && FirstContentfulPaint

使用 window.performace.getEntriesByType 这个api可以检测到这两个阶段的开始时间。

FirstPaint 表示的是页面上第一个像素被绘制上去的时刻,有可能是背景颜色。

FirstContentfulPaint 表示的是浏览器第一个DOM节点渲染到品目上的时间。

从上面的测试结果也可以看出来,二者之间的间距非常非常之小,但这两者共同决定了我们常说的白屏时间。

FisrtMeaningfulPaint

FMP在chrome下的定义是,浏览器计算页面的内容高度或者说是内容多少变化最大的一个时刻,和我们通常意义上将的首屏内容(不包括滚动下滑的内容)意义相近。内容有没有意义,也只是我们网站开发者才能够知道的,所以我们能够根据这一条规则进行优化。

还是来看看淘宝首页的情况吧,FCP的时候出现了顶部的搜索框,FMP的时候基本完成了骨架屏的渲染。

可交互时间

我们知道浏览器中的Javascript是单线程的,浏览器的渲染机制也规定了,UI渲染、�JS执行和用户操作一个时刻只能够执行一种,一定会有一个先后顺序。(宏任务微任务的概念看这里,传送门👉)

既然用户的操作会被JS的执行和UI渲染所阻塞,既然也不能完全避免这样的情况,我们就需要将这样的占用时间尽量缩短,或者说是切割。也就是说将长的JS代码执行任务,切割成小的执行任务。

从人的感受上来说,用户给出的操作最好在100ms内要得到操作反馈,否则就会让用户感觉到卡顿或者说不爽

Time to interactive

其实就是“可交互时间”的英文翻译,我们常简称为TTI。定义上来说,指的是用户看到了页面的大部分内容(近似于FMP)之后,准备进行用户操作,但是此时的主线程又被JS的执行所占用着,想输入,想点击但是都得不到浏览器的响应。

借用一张图来表示,图不是我自己画的....来源在这

Onload Evnet

在文章首部的图中,我们发现DOMContentLoaded之后还有一个Onload Event,它表明的是页面上所有的资源(图片、音频、视屏)都被加载完的时刻,就会触发onload事件,并且它是固定会晚于DCL时刻的。因为onload是指DOM中的所有资源,而影响DCL只有CSS和JS这两种资源的加载与执行。

重要的是,也能确保使用async标记的script被加载并且执行完,假如我们的一些业务代码是依赖于这些异步加载的第三方库的,则不会出现业务代码操作失败。

但因为图片、视屏这一类的资源一般都是加载时间较长,所以onload事件的使用,需要谨慎,否则会大大拖延业务代码的执行。

对应到代码上,就是使用JS去监听window.onload事件

window.onload=function(){
  document.getElementById("bg").style.backgroundColor="#F90"; //DOM操作
  // 或者其他任意业务代码
}

用户的感受

总结一下,其实前端性能优化,就是服务于用户的感受的,说白了就是用户要感觉到,那么我们能不能够从交互的角度来量化一下用户的感受呢?

以下是几个当你打开一个页面后,脑子里会�闪过的几个念头...

闪过的念头 白话描述 内部因素
咦?访问成功了吗? 用户看到了页面切换,看到了白屏?不知道服务器是否有响应? FirstPaint、FirstContentfulPaint
内容加载完了吗? 用户陆续看到了内容,但不知道页面内容加载完了没有? FirstMeaningfulPaint
我可以动鼠标点击了吗? 页面看起来时加载完了,但是用户不知道自己可以操作了吗? Time To Interact
爽不爽? �整个流程下来,页面是否闪烁,内容上下乱跳,让用户有一个直观的感受,爽与不爽? 是否有长任务占用主线程,onload之后是否又频繁造成重排

如何优化

1️⃣ 如何提前FP和FCP

回看Timeline,阻塞FP和FCP的就是head标签中的css和Javascript,但是有些css确实首屏需要的(key-css的概念,看这里👉)则仍需要保留在head中避免频繁重排,Javascript脚本则可以放心地沉到body底部或者deferasync

使用http缓存本地资源,减少请求时间也是很重要的一点。

2️⃣ 如何提前FMP

FMP是关键内容的出现,或者是大部分内容出现的时间。在这一点上客户端的渲染能力,远远比不上服务端的渲染能力,所以首推SSR

3️⃣ 如何提前TTI

TTI强调的点是交互,那么我们要消除或者减少的是页面UI渲染JS脚本执行

  • 保证首屏的组件先加载,非首屏内容/组件懒加载
  • 图片的懒加载,当遇到图片展示型的网页,大量的图片会严重地阻碍TTI。
  • 准备性的Javascript库尽量在DCL之前去加载执行,而不是让用户看到了页面再去等待加载时间,因为用户看到白屏至少不会想要去操作,或者说用户看到了所有内容就会立刻想去操作,若得不到反馈,会极为的不爽。

TTL最后的这一条优化,可以根据网站功能类型的不同去做不同优化,不要墨守成规。

4️⃣ 如何减少TTI后,对用户操作的干扰

在非首屏情况下,大体不会有渲染的问题,但是用户的行为会触发javascript的执行,比如说复杂的JS计算也是会导致用户操作的卡顿,页面进入假死状态。

  • 使用web-worker进行多线程计算,然后返回结果到主线程。
  • 还是尽量减少图片的大量渲染
  • 在onload事件中,尽量少或者不要去大量规模操作DOM元素出现或则隐藏,从而大量影响页面的排布。是不是想起来,你在页面上想点击一个东西,却因为页面不断在上下跳动,总是点不中呢?
  • 使用骨架屏,稳定首页或者全页的骨架,减少后续资源加载对页面高度宽度的影响。

参考文章

[1] 前端性能量化标准 -by 云栖社区
[2] DOMContentLoaded与load的区别

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant