简短的问题
我如何(尽可能可靠地)计算“首屏”内容在视觉上完整的时间,包括应用外部 CSS 和字体以及加载的任何图像。
完整问题
抱歉,这个问题有很多内容,我只是想表明我已经解决了这个问题以及我所处的位置,所以它最终没有成为“我该怎么做”类型的问题并关闭 insta!
我正在尝试确定渲染“首屏”内容所需的所有资源是否已完全下载到客户端浏览器中。
这是纯粹使用浏览器 API(即不使用屏幕截图时间线)模拟SpeedIndex的更大目标的一部分。
我需要收集什么
- 出现在页面首屏上方的所有元素。
- 确保已加载所有相关资产。
- 此外,这些数据正在发送到服务器进行分析,因此我想尽可能地尝试将其保留在一个请求中。
我无法克服的挑战
- 必须运行多次获取首屏元素的函数
- 确保所有关键资产实际上都加载在非常慢的连接上。
- 如果网络流量很高,请确保该功能确实运行,如果网络上从来没有一个安静的 2 秒窗口(可能是由于每秒轮询服务器的老式聊天),它永远不会触发。
首先,这不一定是完美的,它是一个近似值,但我越准确越好!
我目前这样做的方式是使用PerformanceObserver
在下载所有资源时列出它们。
第二个我得到一个 2 秒的窗口,没有完成任何请求,我假设关键的 CSS 已经下载并开始查看页面上的图像。
//getRects is the function to get all of the rectangles above the fold.
var rectInterval = setTimeout(getRects, 2500);
var xx = new PerformanceObserver(function (ll, p) {
ll.getEntries().forEach(function (en) {
if (en.name.indexOf(apiEndpoint) == -1) {
if (en.entryType == "layout-shift") {
if (en.entryType == "resource"){
//any resources I reset the timer waiting for a quiet time
clearTimeout(rectInterval);
rectInterval = setTimeout(getRects, 2000);
}
}
});
});
xx.observe({
entryTypes: ['largest-contentful-paint', 'longtask', 'resource', 'paint', 'navigation', 'mark', 'measure', 'layout-shift', 'first-input']
});
getRects
我在函数中使用页面上的背景图像获取所有图像和元素的尺寸,然后使用等getBoundingRect()
计算它们是否出现在折叠上方。window.innerHeight
这些是下载时要检查的候选对象(以及之前的资源列表等)
var doc = window.document;
var browserWidth = window.innerWidth || doc.documentElement.clientWidth;
var browserHeight = window.innerHeight || doc.documentElement.clientHeight;
function checkRectangle(el){
var rtrn = false;
if (el.getBoundingClientRect) {
var rect = el.getBoundingClientRect();
//check if the bottom is above the top to ensure the element has height, same for width.
//Then the last 4 checks are to see if the element is in the above the fold viewport.
if (rect.bottom <= rect.top || rect.right <= rect.left || rect.right < 0 || rect.left > browserWidth || rect.bottom < 0 || rect.top > browserHeight) {
rtrn = false;
}else{
rtrn = {};
rtrn.bot = rect.bottom;
rtrn.top = rect.top;
rtrn.left = rect.left;
rtrn.right = rect.right;
}
}
return rtrn;
}
//function to get the rectangles above the fold
function getRects(){
var rects = [];
var elements = doc.getElementsByTagName('*');
var re = /url\(.*(http.*)\)/ig;
for (var i = 0; i < elements.length; i++) {
var el = elements[i];
var style = getComputedStyle(el);
if(el.tagName == "IMG"){
var rect = checkRectangle(el);
if(rect){
//The URL is stored here for later processing where I match performance timings to the element, it is not relevant other than to show why I convert the `getBoundingClientRect()` to a simple object.
rect.url = el.src;
rects.push(rect);
}
}
//I also need to check for background images set in either CSS or with inline styles.
if (style['background-image']) {
var rect = checkRectangle(el);
if(rect){
var matches = re.exec(style['background-image']);
if (matches && matches.length > 1){
rect.url = matches[1].replace('"', '');
rects.push(rect);
}
}
}
}
这一点很好(尽管任何缩小搜索范围的提示,所以我不会遍历所有内容都会很棒),但我的问题出在加载缓慢的网站上。如果请求之间的间隔超过 2 秒(这可能发生在连接特别慢或服务器距离用户很远的情况下),那么我将无法获得完整的数据。
我的解决方法是监视进一步的网络请求(再次等待请求之间的 2 秒延迟)并重新运行该函数以收集上述折叠内容。如果站点在滚动时使用延迟加载,这显然不能很好地工作,因为请求可以在整个页面生命周期中不断触发。
由于在一个非常重的页面上收集元素的维度可能会占用大量 CPU,再加上需要将此数据发送到服务器进行分析,因此我试图找到一种更可靠的方法来确保加载所有关键内容。或者一种只开火getRect
一次但确保所有初始加载完成的方法。
假设如果有效负载足够小(例如小于 1kb),任何数据操作都可以稍后在服务器上完成
我考虑过的事情
- 寻找任何
<links>
等<scripts>
并检查它们是否已加载。问题来自于动态添加的链接以及外部资源(即链接在另一个样式表中的样式表)。这可能会更强大,但会变得非常复杂。 - 设置检查之间的时间(我正在等待的安静时间)更长。这显然会使流量大的网站问题变得更糟,因为“安静时间”可能永远不会发生。
- 用于
MutationObserver
监视页面并再次等待安静时间。但是,据我所知,如果页面具有任何交互性,这会更频繁地被触发? - 我知道我的方法会过度报告正确内联其 CSS 的网站,这不是问题。
作为解决这个难题的一种方式,我是否走在正确的轨道上,或者是否有一些我可以根据window.performance
数据(或类似的 API)使用的简单公式让我说“所有折叠元素都已加载和渲染”。
我希望这很清楚,但任何问题都只是问,因为我知道这个问题有很多可以简单地回答“我如何检查所有关键资源是否已加载”。