相信大家经常遇到网页加载的时候半天内容不出来,也不到要等待多久,或者说有进度条但是无法反应页面的加载进度,这种用户体验还是非常差的,给站点一个真实的进度条对于用户体验还是非常友好的,一个真实的进度条可以一定程度上可以增加用户继续等待的机率。
假进度条
很多网页的进度条基本上就是装装样子的,基本上页面打开了就显示加载到了20%,然后页面开始渲染了就显示加载到了50%,加载完成之后就显示100%。这种进度条一旦网站加载一卡顿,就会出现在一个进度值半天不动,体验式非常差的。
今天潘某人SEO分享一个基于原生javascript语法不需要任何插件来实现进度条的真实加载,按照加载资源数量的控制进度条的变化,可以实现只要网站每加载完成一个资源进度条同步变化。
程序逻辑思路
基本原理是在页面添加一个progress标签,控制标签的value值来更新进度条的加载进度;而进度值通过计算页面已加载完成资源数量和总资源数的比例获取。
- 第一步获取资源数
- 第二步计算完成加载的资源数的比例
- 第三步将得到加载完成的资源数的比例值更新progress标签的value值
代码实现
别看上面逻辑很简单,但是要把功能做的完美还是有单独的,这里涉及到了浏览器页面渲染相关的知识,重点在于如何知道资源的加载状态以及如何知道资源加载完成。文末结尾有完整代码示例,接下来先一个个步骤简单讲解下。
进度条
DOCTYPE html>
<html>
<head>
<title>页面资源加载进度条---潘某人SEOtitle>
<style>
#progressBar{
position: fixed;
z-index: 200;
top: 0;
left: 0;
width: 100%;
height: 3px;
box-shadow: 1px 2px 3px #eee;
background-color: blue;
transition: width 1s ease-in-out;
}
style>
head>
<body>
<progress id="progressBar" value="0" max="100">progress>
body>
html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
我们使用通一个progress标签作为进度条,大家可根据自己的需求来调整进度条的样式,进度条进度的变化是通过修改 progress 标签的value值实现,当value值达到和max值相等,进度条即加载完成。接下来我们需要做的就是计算页面资源加载的比例的数值来更新value值即可。
如何获取资源的比例
progress标签的value值 = 资源加载完成的数量/资源的总数,对计算结果取整。获取页面资源的数量非常简单,难点在于什么时候去获取。正确的时间应该是在dom树构建完成,就需要获取页面资源及其数量,获取早了资源会获取不全,获取晚了资源可能就加载完成的都差不多了,都会影响进度条的准确性。
// 监听dom构建完成
document.addEventListener('DOMContentLoaded', function() {
console.log("dom构建完成")
})
2
3
4
要准确的把握时机,就需要监听‘DOMContentLoaded’事件,当页面dom构建完成的时候会触发此时间,此时就可以去获取页面的资源。DOM树大致可以认为的就是页面可以看到的html变迁结构,DOM树构建完成的时候,大部分资源都是没有开始加载的,这里也暗藏下了一个问题。
那么由如何知道这些资源加载的情况呢?这就需要我们将获取到资源,每个资源添加一个‘load’事件的监听,当资源加载完成的时候会被触发,将这些触发的次数累加就得到了资源加载完成的数量。
// 更新进度条
function updateProgress(loadedResources) {
progress = Math.round((loadedResources / totalResources) * 100);
console.log('加载完成度:',progress,'%')
progressBar.value = progress;
}
//获取页面所有资源
const resources = document.querySelectorAll('img, script, link');// 获取图片、脚本和链接资源
window.totalResources = resources.length; // 获取资源总数
window.loadedResources = 0;//已加载完成的资源数
window.progress = 0;// 进度值
window.progressBar = document.getElementById('progressBar');
resources.forEach((resource) => {//遍历元素添加load监听事件
resource.addEventListener('load', () => {
loadedResources++;
console.log('加载完成的资源数量:',loadedResources)
//更新进度条的处理函数
updateProgress(loadedResources);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
上述代码就实现了,获取资源总数以及资源加载完成的数量,然后每当一个资源加载完成,就会触发‘load’事件,更新对应progress标签的value值。
当然我们不一定需要获取所有的资源,可以在第8行代码选择获取哪些类型。
存在的问题
前面说到了存在一个问题,上述代码执行的时候会发现,进度条是无法达到100%的,这是因为在‘DOMContentLoaded’事件触发完成的时候已经有部分资源已经加载完成,这个主要适合浏览器页面解析渲染的原理有关,具体的大家可以去学习了解下页面的渲染的,比较复杂这里一两句讲不清。
如js脚本会阻塞dom的构建,换句话说dom构建完成的时候,部分的js脚本的加载完成,这也是导致部分元素在‘DOMContentLoaded’事件触发的时候已经加载完成的原因之一。
解决方法是通过监听页面‘readyState’状态,在页面渲染完成的时候,将进度条更新100%进度即可,由于这部分资源数量占比比较少,对于进度条体验上不会有很大的影响。考虑的太复杂也没有意义,并且进度条的展示也是要基于js和css实现的,将资源获取的时间在往前提,个人认为意义不大。
//页面渲染完成
document.onreadystatechange = function () {
if (document.readyState === 'complete') {
console.log('页面加载完成');
//执行结束进度条的函数
}
}
2
3
4
5
6
7
成果展示,完整代码
上图则是在潘某人SEO站点测试下来的效果,在页面构建完成之后,开始页面加载的时候就会显示进度条,随着资源的加载进度条可以非常丝滑并且反应真实页面加载进度,页面加载完成之后,进度条达到100%后消失隐藏。
DOCTYPE html>
<html>
<head>
<title>页面资源加载进度条---潘某人SEOtitle>
<style>
#progressBar{
position: fixed;
z-index: 200;
top: 0;
left: 0;
width: 100%;
height: 3px;
box-shadow: 1px 2px 3px #eee;
background-color: blue;
transition: width 1s ease-in-out;
}
style>
head>
<body>
<progress id="progressBar" value="0" max="100">progress>
body>
<script>
/**
* @autor 潘某人SEO www.chateach.com
* @param {String} state ---"start"开启进度条加载、“end”进度条到达100结束进度条
*/
function changeProgress(state) {
// 更新进度条
function updateProgress(loadedResources) {
progress = Math.round((loadedResources / totalResources) * 100);
console.log('加载完成度:',progress,'%')
progressBar.value = progress;
}
switch (state) {
//开启进度条
case "start":
//获取页面所有资源
const resources = document.querySelectorAll('img, script, link');// 获取图片、脚本和链接资源
window.totalResources = resources.length; // 获取资源总数
window.loadedResources = 0;//已加载完成的资源数
window.progress = 0;// 进度值
window.progressBar = document.getElementById('progressBar');
console.log("resources:",resources)
resources.forEach((resource) => {//遍历元素添加load监听事件
resource.addEventListener('load', () => {
loadedResources++;
console.log('加载完成的资源数量:',loadedResources)
//更新进度条
updateProgress(loadedResources);
});
});
break;
//结束进度条
case "end":
updateProgress(totalResources);
progressBar.style.opacity = 0;
setTimeout(() => {
progressBar.style.display = 'none';
} , 1000);
break;
default:
break;
}
}
//dom构建完成
document.addEventListener('DOMContentLoaded', function() {
console.log("dom构建完成")
changeProgress ("start");//开启进度条
})
//页面渲染完成
document.onreadystatechange = function () {
if (document.readyState === 'complete') {
console.log('页面加载完成');
changeProgress ("end");//结束进度条,
}
}
script>
html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82