首屏时间,你说你优化了,那你倒是计算出给我看啊!
SegmentFault
共 9412字,需浏览 19分钟
·
2022-02-27 16:29
作者:Sunshine_Lin
来源:SegmentFault 思否社区
前言
大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的初心
背景
当我们在做项目的性能优化的时候,优化首屏时间是一个避不过去的优化方向,但是又有多少人想过这两个东西的区别呢:
白屏时间 首屏时间
白屏时间
是什么?
怎么算?
浏览器支持 performance.timing
<head>
<title>Document</title>
</head>
<script type="text/javascript">
// 白屏时间结束点
var firstPaint = Date.now()
var start = performance.timing.navigationStart
console.log(firstPaint - start)
</script>
浏览器不支持 performance.timing
<head>
<title>Document</title>
<script type="text/javascript">
window.start = Date.now();
</script>
</head>
<script type="text/javascript">
// 白屏时间结束点
var firstPaint = Date.now()
console.log(firstPaint - window.start)
</script>
首屏时间
是什么?
为什么不直接用生命周期?
为什么不直接用nextTick?
怎么算?
<body>
<div>
<div>1</div>
<div>2</div>
</div>
</body>
首屏时间实践
现在我们开始计算首屏时间吧!
前置准备
index.html:html页面
<!DOCTYPE html>
<html lang="en">
<head> </head>
<body>
<div>
<div>
<div>1</div>
<div>2</div>
</div>
<div>3</div>
<div>4</div>
</div>
<ul id="ulbox"></ul>
</body>
<script src="./computed.js"></script>
<script src="./request.js"></script>
</html>
computed.js :计算首屏时间的文件
const observerData = []
let observer = new MutationObserver(() => {
// 计算每次DOM修改时,距离页面刚开始加载的时间
const start = window.performance.timing.navigationStart
const time = new Date().getTime() - start
const body = document.querySelector('body')
const score = computedScore(body, 1)
// 加到数组 observerData 中
observerData.push({
score,
time
})
})
observer.observe(
document, {
childList: true,
subtree: true
}
)
function computedScore(element, layer) {
let score = 0
const tagName = element.tagName
// 排除这些标签的情况
if (
tagName !== 'SCRIPT' &&
tagName !== 'STYLE' &&
tagName !== 'META' &&
tagName !== 'HEAD'
) {
const children = element.children
if (children && children.length) {
// 递归计算分数
for (let i = 0; i < children.length; i++) {
score += computedScore(children[i], layer + 1)
}
}
score += 1 + 0.5 * layer
}
return score
}
request.js :模拟请求修改DOM
// 模拟请求列表
const requestList = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(
[1, 2, 3,
4, 5, 6,
7, 8, 9
]
)
}, 1000)
})
}
const ulbox = document.getElementById('ulbox')
// 模拟请求数据渲染列表
const renderList = async () => {
const list = await requestList()
const fragment = document.createDocumentFragment()
for (let i = 0; i < list.length; i++) {
const li = document.createElement('li')
li.innerText = list[i]
fragment.appendChild(li)
}
ulbox.appendChild(fragment)
}
// 模拟对列表进行轻微修改
const addList = async () => {
const li = document.createElement('li')
li.innerText = '加上去'
ulbox.appendChild(li)
}
(async () => {
// 模拟请求数据渲染列表
await renderList()
// 模拟对列表进行轻微修改
addList()
})()
observerData
计算首屏时间
const observerData = []
let observer = new MutationObserver(() => {
// 计算每次DOM修改时,距离页面刚开始加载的时间
const start = window.performance.timing.navigationStart
const time = new Date().getTime() - start
const body = document.querySelector('body')
const score = computedScore(body, 1)
observerData.push({
score,
time
})
// complete时去调用 unmountObserver
if (document.readyState === 'complete') {
// 只计算10秒内渲染时间
unmountObserver(10000)
}
})
observer.observe(
document, {
childList: true,
subtree: true
}
)
function computedScore(element, layer) {
let score = 0
const tagName = element.tagName
// 排除这些标签的情况
if (
tagName !== 'SCRIPT' &&
tagName !== 'STYLE' &&
tagName !== 'META' &&
tagName !== 'HEAD'
) {
const children = element.children
if (children && children.length) {
// 递归计算分数
for (let i = 0; i < children.length; i++) {
score += computedScore(children[i], layer + 1)
}
}
score += 1 + 0.5 * layer
}
return score
}
// 计算首屏时间
function getFirstScreenTime() {
let data = null
for (let i = 1; i < observerData.length; i++) {
// 计算幅度
const differ = observerData[i].score - observerData[i - 1].score
// 取最大幅度,记录对应时间
if (!data || data.rate <= differ) {
data = {
time: observerData[i].time,
rate: differ
}
}
}
return data
}
let timer = null
function unmountObserver(delay) {
if (timer) return
timer = setTimeout(() => {
// 输出首屏时间
console.log(getFirstScreenTime())
// 终止MutationObserver的监控
observer.disconnect()
observer = null
clearTimeout(timer)
}, delay)
}
总结
我这个计算方法其实很多漏洞,没把删除元素也考虑进去,但是想让大家知道计算首屏时间的计算思想,这才是最重要的,希望大家能理解这个计算思想。
评论