作者:jack_lo

www.jianshu.com/p/4c93f5bd9861

如有好文章投稿,请点击 → 这里了解详情

loading随处可见,比如一个app经常会有下拉刷新,上拉加载的功能,在刷新和加载的过程中为了让用户感知到 load 的过程,我们会使用一些过渡动画来表达。最常见的比如“转圈圈”,“省略号”等等。

网页loading有很多用处,比如页面的加载进度,数据的加载过程等等,数据的加载loading很好做,只需要在加载数据之前(before ajax)显示loading效果,在数据返回之后(ajax completed)结束loading效果,就可以了。

但是页面的加载进度,需要一点技巧。

页面加载进度一直以来都是一个常见而又晦涩的需求,常见是因为它在某些“重”网页(特别是网页游戏)的应用特别重要;晦涩是因为web的特性,各种零散资源决定它很难是“真实”的进度,只能是一种“假”的进度,至少在逻辑代码加载完成之前,我们都不能统计到进度,而逻辑代码自身的进度也无法统计。另外,我们不可能监控到所有资源的加载情况。

所以页面的加载进度都是“假”的,它存在的目的是为了提高用户体验,使用户不至于在打开页面之后长时间面对一片空白,导致用户流失。

既然是“假”的,我们就要做到“仿真”才有用。仿真是有意义的,事实上用户并不在乎某一刻你是不是真的加载到了百分之几,他只关心你还要load多久。所以接下来我们就来实现一个页面加载进度loading。

首先准备一段loading的html:

<!DOCTYPE html>

<html>

<head>

<title>写一个网页进度loading</title>

</head>

<body>

<div class="loading" id="loading">

<div class="progress" id="progress">0%</div>

</div>

</body>

</html>

来点样式装扮一下:

.loading {

display: table;

position: fixed;

top: 0;

left: 0;

width: 100%;

height: 100%;

background-color: #fff;

z-index: 5;

}

.loading .progress {

display: table-cell;

vertical-align: middle;

text-align: center;

}

我们先假设这个loading只需要在页面加载完成之后隐藏,中间不需要显示进度。那么很简单,我们第一时间想到的就是window.onload:

(以下内容为了方便演示,默认使用jQuery,语法有es6的箭头函数)

var $loading = $('#loading')

var $progress = $('#progress')

window.onload = () => {

$loading.hide()

}

ok,这样基本的loading流程就有了,我们增加一个进度的效果,每隔100ms就自增1,一直到100%为止,而另一方面window loaded的时候,我们把loading给隐藏。

我们来补充一下进度:

var $loading = $('#loading')

var $progress = $('#progress')

var prg = 0  // 初始化进度

var timer = window.setInterval(() => {  // 设置定时器

if (prg >= 100) {  // 到达终点,关闭定时器

window.clearInterval(timer)

prg = 100

} else {  // 未到终点,进度自增

prg++

}

$progress.html(prg + '%')

console.log(prg)

}, 100)

window.onload = () => {

$loading.hide()

}

效果不错,但是有个问题,万一window loaded太慢了,导致进度显示load到100%了,loading还没有隐藏,那就打脸了。所以,我们需要让loading在window loaded的时候才到达终点,在这之前,loading可以保持一个等待的状态,比如在80%的时候,先停一停,然后在loaded的时候快速将进度推至100%。这个做法是目前绝大部份进度条的做法。

var $loading = $('#loading')

var $progress = $('#progress')

var prg = 0

var timer = window.setInterval(() => {

if (prg >= 80) {  // 到达第一阶段80%,关闭定时器,保持等待

window.clearInterval(timer)

prg = 100

} else {

prg++

}

$progress.html(prg + '%')

console.log(prg)

}, 100)

window.onload = () => {

window.clearInterval(timer)

window.setInterval(() => {

if (prg >= 100) {  // 到达终点,关闭定时器

window.clearInterval(timer)

prg = 100

$loading.hide()

} else {

prg++

}

$progress.html(prg + '%')

console.log(prg)

}, 10)  // 时间间隔缩短

}

ok,这差不多就是我们想要的功能了,我们来提炼一下代码,把重复的代码给封装一下:

var $loading = $('#loading')

var $progress = $('#progress')

var prg = 0

var timer = 0

progress(80, 100)

window.onload = () => {

progress(100, 10, () => {

$loading.hide()

})

}

function progress (dist, delay, callback) {

window.clearInterval(timer)

timer = window.setInterval(() => {

if (prg >= dist) {

window.clearInterval(timer)

prg = dist

callback && callback()

} else {

prg++

}

$progress.html(prg + '%')

console.log(prg)

}, delay)

}

我们得到了一个progress函数,这个函数就是我们主要的功能模块,通过传入一个目标值、一个时间间隔,就可以模拟进度的演化过程。

目前来看,这个进度还是有些问题的:

  1. 进度太平均,相同的时间间隔,相同的增量,不符合网络环境的特点;

  2. window.onload太快,我们还来不及看清100%,loading就已经不见了;

  3. 每次第一阶段都是在80%就暂停了,露馅儿了;

第一个点,我们要让时间间隔随机,增量也随机;第二个点很简单,我们延迟一下就好了;第三点也需要我们随机产生一个初始值。

增量随机很好办,如何让时间间隔随机?setInterval是无法动态设置delay的,那么我们就要把它改造一下,使用setTimeout来实现。(setInterval跟setTimeout的用法和区别就不细说了吧?)

var $loading = $('#loading')

var $progress = $('#progress')

var prg = 0

var timer = 0

progress([80, 90], [1, 3], 100)  // 使用数组来表示随机数的区间

window.onload = () => {

progress(100, [1, 5], 10, () => {

window.setTimeout(() => {  // 延迟了一秒再隐藏loading

$loading.hide()

}, 1000)

})

}

function progress (dist, speed, delay, callback) {

var _dist = random(dist)

var _delay = random(delay)

var _speed = random(speed)

window.clearTimeout(timer)

timer = window.setTimeout(() => {

if (prg + _speed >= _dist) {

window.clearTimeout(timer)

prg = _dist

callback && callback()

} else {

prg += _speed

progress (_dist, speed, delay, callback)

}

$progress.html(parseInt(prg) + '%')  // 留意,由于已经不是自增1,所以这里要取整

console.log(prg)

}, _delay)

}

function random (n) {

if (typeof n === 'object') {

var times = n[1] - n[0]

var offset = n[0]

return Math.random() * times + offset

} else {

return n

}

}

至此,我们差不多完成了需求。

but,还有一个比较隐蔽的问题,我们现在使用window.onload,发现从进入页面,到window.onload这中间相隔时间十分短,我们基本是感受不到第一阶段进度(80%)的,这是没有问题的——我们在意的是,如果页面的加载资源数量很多,体积很大的时候,从进入页面,到window.onload就不是这么快速了,这中间可能会很漫长(5~20秒不等),但事实上,我们只需要为 首屏资源 的加载争取时间就可以了,不需要等待所有资源就绪,而且更快地呈现页面也是提高用户体验的关键。

我们应该考虑页面loading停留过久的情况,我们需要为loading设置一个超时时间,超过这个时间,假设window.onload还没有完成,我们也要把进度推到100%,把loading结束掉。

var $loading = $('#loading')

var $progress = $('#progress')

var prg = 0

var timer = 0

progress([80, 90], [1, 3], 100)  // 使用数组来表示随机数的区间

window.onload = () => {

progress(100, [1, 5], 10, () => {

window.setTimeout(() => {  // 延迟了一秒再隐藏loading

$loading.hide()

}, 1000)

})

}

window.setTimeout(() => {  // 设置5秒的超时时间

progress(100, [1, 5], 10, () => {

window.setTimeout(() => {  // 延迟了一秒再隐藏loading

$loading.hide()

}, 1000)

})

}, 5000)

function progress (dist, speed, delay, callback) {

var _dist = random(dist)

var _delay = random(delay)

var _speed = random(speed)

window.clearTimeout(timer)

timer = window.setTimeout(() => {

if (prg + _speed >= _dist) {

window.clearTimeout(timer)

prg = _dist

callback && callback()

} else {

prg += _speed

progress (_dist, speed, delay, callback)

}

$progress.html(parseInt(prg) + '%')  // 留意,由于已经不是自增1,所以这里要取整

console.log(prg)

}, _delay)

}

function random (n) {

if (typeof n === 'object') {

var times = n[1] - n[0]

var offset = n[0]

return Math.random() * times + offset

} else {

return n

}

}

我们直接设置了一个定时器,5s的时间来作为超时时间。这样做是可以的。

but,还是有问题,这个定时器是在js加载完毕之后才开始生效的,也就是说,我们忽略了js加载完毕之前的时间,这误差可大可小,我们设置的5s,实际用户可能等待了8s,这是有问题的。我们做用户体验,需要从实际情况去考虑,所以这个开始时间还需要再提前一些,我们在head里来记录这个开始时间,然后在js当中去做对比,如果时间差大于超时时间,那我们就可以直接执行最后的完成步骤,如果小于超时时间,则等待 剩余的时间 过后,再完成进度。

先在head里埋点,记录用户进入页面的时间loadingStartTime:

<!DOCTYPE html>

<html>

<head>

<title>写一个网页进度loading</title>

<script>

window.loadingStartTime = new Date()

</script>

<script src="index.js"></script>

</head>

<body>

<div class="loading" id="loading">

<div class="progress" id="progress">0%</div>

</div>

</body>

</html>

然后,我们对比 当前的时间 ,看是否超时:(为了方便复用代码,我把完成的部分封装成函数complete)

var $loading = $('#loading')

var $progress = $('#progress')

var prg = 0

var timer = 0

var now = new Date()  // 记录当前时间

var timeout = 5000  // 超时时间

progress([80, 90], [1, 3], 100)

window.onload = () => {

complete()

}

if (now - loadingStartTime > timeout) {  // 超时

complete()

} else {

window.setTimeout(() => {  // 未超时,则等待剩余时间

complete()

}, timeout - (now - loadingStartTime))

}

function complete () {  // 封装完成进度功能

progress(100, [1, 5], 10, () => {

window.setTimeout(() => {

$loading.hide()

}, 1000)

})

}

function progress (dist, speed, delay, callback) {

var _dist = random(dist)

var _delay = random(delay)

var _speed = random(speed)

window.clearTimeout(timer)

timer = window.setTimeout(() => {

if (prg + _speed >= _dist) {

window.clearTimeout(timer)

prg = _dist

callback && callback()

} else {

prg += _speed

progress (_dist, speed, delay, callback)

}

$progress.html(parseInt(prg) + '%')

console.log(prg)

}, _delay)

}

function random (n) {

if (typeof n === 'object') {

var times = n[1] - n[0]

var offset = n[0]

return Math.random() * times + offset

} else {

return n

}

}

至此,我们算是完整地实现了这一功能。

然而,事情还没有结束,少年你太天真。

如果目的是为了写一个纯粹障眼法的伪loading,那跟其他loading的实现就没什么区别了,我们做事讲究脚踏实地,能实现的实现,不能实现的,为了团队和谐,我们不得已坑蒙拐骗。那么我们还能更贴近实际情况一点吗?其实是可以的。

我们来分析一个场景,假设我们想让我们的loading更加真实一些,那么我们可以选择性地对页面上几个比较大的资源的加载进行跟踪,然后拆分整个进度条,比如我们页面有三张大图a、b、c,那么我们将进度条拆成五段,每加载完一张图我们就推进一个进度:

随机初始化[10, 20] ->

图a推进20%的进度 ->

图b推进25%的进度 ->

图c推进30%的进度 ->

完成100%

这三张图要占20% + 25% + 30% = 75%的进度。

问题是,如果图片加载完成是按照顺序来的,那我们可以很简单地:10(假设初始进度是10%) -> 30 -> 55 -> 85 -> 100,但事实是,图片不会按照顺序来,谁早到谁晚到是说不准的,所以我们需要更合理的方式去管理这些进度增量,使它们不会互相覆盖。

  1. 我们需要一个能够替我们累计增量的变量next;

  2. 由于我们的progress都是传目的进度的,我们需要另外一个函数add,来传增量进度。

var $loading = $('#loading')

var $progress = $('#progress')

var prg = 0

var timer = 0

var now = new Date()

var timeout = 5000

var next = prg

add([30, 50], [1, 3], 100)  // 第一阶段

window.setTimeout(() => {  // 模拟图a加载完

add(20, [1, 3], 200)

}, 1000)

window.setTimeout(() => {  // 模拟图c加载完

add(30, [1, 3], 200)

}, 2000)

window.setTimeout(() => {  // 模拟图b加载完

add(25, [1, 3], 200)

}, 2500)

window.onload = () => {

complete()

}

if (now - loadingStartTime > timeout) {

complete()

} else {

window.setTimeout(() => {

complete()

}, timeout - (now - loadingStartTime))

}

function complete () {

add(100, [1, 5], 10, () => {

window.setTimeout(() => {

$loading.hide()

}, 1000)

})

}

function add (dist, speed, delay, callback) {

var _dist = random(dist)

if (next + _dist > 100) {  // 对超出部分裁剪对齐

next = 100

} else {

next += _dist

}

progress(next, speed, delay, callback)

}

function progress (dist, speed, delay, callback) {

var _delay = random(delay)

var _speed = random(speed)

window.clearTimeout(timer)

timer = window.setTimeout(() => {

if (prg + _speed >= dist) {

window.clearTimeout(timer)

prg = dist

callback && callback()

} else {

prg += _speed

progress (dist, speed, delay, callback)

}

$progress.html(parseInt(prg) + '%')

console.log(prg)

}, _delay)

}

function random (n) {

if (typeof n === 'object') {

var times = n[1] - n[0]

var offset = n[0]

return Math.random() * times + offset

} else {

return n

}

}

我们这里为了方便,用setTimeout来模拟图片的加载,真实应用应该是使用image.onload。

以上,就是我们一步步实现一个进度loading的过程了,演示代码可以戳我的codePen 写一个网页进度loading。

看似很简单的一个功能,其实仔细推敲,还是有很多细节要考虑的。

到这里,其实真的已经完成了,代码有点多有点乱是不是?你可以整理一下,封装成为插件的。

然而,好吧,其实我已经把这个进度封装成插件了。。。

没错,其实我就是来帮自己打广告的。。。

好吧,github仓库在此 ez-progress。

ez-progress 是一个web(伪)进度插件,使用 ez-progress 实现这个功能非常简单:

var Progress = require('ez-progress')

var prg = new Progress()

var $loading = $('#loading')

var $progress = $('#progress')

prg.on('progress', function (res) {

var progress = parseInt(res.progress)  // 注意进度取整,不然有可能会出现小数

$progress.html(progress + '%')

})

prg.go([60, 70], function (res) {

prg.complete(null, [0, 5], [0, 50])  // 飞一般地冲向终点

}, [0, 3], [0, 200])

window.onload = function () {

prg.complete(null, [0, 5], [0, 50])  // 飞一般地冲向终点

}

木油错,94这么简单!

写一个网页进度loading

作者 jack_lo 关注

2016.12.23 19:51* 字数 2402 阅读 1040评论 9喜欢 18

loading随处可见,比如一个app经常会有下拉刷新,上拉加载的功能,在刷新和加载的过程中为了让用户感知到 load 的过程,我们会使用一些过渡动画来表达。最常见的比如“转圈圈”,“省略号”等等。

网页loading有很多用处,比如页面的加载进度,数据的加载过程等等,数据的加载loading很好做,只需要在加载数据之前(before ajax)显示loading效果,在数据返回之后(ajax completed)结束loading效果,就可以了。

但是页面的加载进度,需要一点技巧。

页面加载进度一直以来都是一个常见而又晦涩的需求,常见是因为它在某些“重”网页(特别是网页游戏)的应用特别重要;晦涩是因为web的特性,各种零散资源决定它很难是“真实”的进度,只能是一种“假”的进度,至少在逻辑代码加载完成之前,我们都不能统计到进度,而逻辑代码自身的进度也无法统计。另外,我们不可能监控到所有资源的加载情况。

所以页面的加载进度都是“假”的,它存在的目的是为了提高用户体验,使用户不至于在打开页面之后长时间面对一片空白,导致用户流失。

既然是“假”的,我们就要做到“仿真”才有用。仿真是有意义的,事实上用户并不在乎某一刻你是不是真的加载到了百分之几,他只关心你还要load多久。所以接下来我们就来实现一个页面加载进度loading。

首先准备一段loading的html:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>写一个网页进度loading</title>
  5. </head>
  6. <body>
  7. <div class="loading" id="loading">
  8. <div class="progress" id="progress">0%</div>
  9. </div>
  10. </body>
  11. </html>

来点样式装扮一下:

  1. .loading {
  2. display: table;
  3. position: fixed;
  4. top: 0;
  5. left: 0;
  6. width: 100%;
  7. height: 100%;
  8. background-color: #fff;
  9. z-index: 5;
  10. }
  11. .loading .progress {
  12. display: table-cell;
  13. vertical-align: middle;
  14. text-align: center;
  15. }

我们先假设这个loading只需要在页面加载完成之后隐藏,中间不需要显示进度。那么很简单,我们第一时间想到的就是window.onload:

(以下内容为了方便演示,默认使用jQuery,语法有es6的箭头函数)

  1. var $loading = $('#loading')
  2. var $progress = $('#progress')
  3. window.onload = () => {
  4. $loading.hide()
  5. }

ok,这样基本的loading流程就有了,我们增加一个进度的效果,每隔100ms就自增1,一直到100%为止,而另一方面window loaded的时候,我们把loading给隐藏。

我们来补充一下进度:

  1. var $loading = $('#loading')
  2. var $progress = $('#progress')
  3. var prg = 0 // 初始化进度
  4. var timer = window.setInterval(() => { // 设置定时器
  5. if (prg >= 100) { // 到达终点,关闭定时器
  6. window.clearInterval(timer)
  7. prg = 100
  8. } else { // 未到终点,进度自增
  9. prg++
  10. }
  11. $progress.html(prg + '%')
  12. console.log(prg)
  13. }, 100)
  14. window.onload = () => {
  15. $loading.hide()
  16. }

效果不错,但是有个问题,万一window loaded太慢了,导致进度显示load到100%了,loading还没有隐藏,那就打脸了。所以,我们需要让loading在window loaded的时候才到达终点,在这之前,loading可以保持一个等待的状态,比如在80%的时候,先停一停,然后在loaded的时候快速将进度推至100%。这个做法是目前绝大部份进度条的做法。

  1. var $loading = $('#loading')
  2. var $progress = $('#progress')
  3. var prg = 0
  4. var timer = window.setInterval(() => {
  5. if (prg >= 80) { // 到达第一阶段80%,关闭定时器,保持等待
  6. window.clearInterval(timer)
  7. prg = 100
  8. } else {
  9. prg++
  10. }
  11. $progress.html(prg + '%')
  12. console.log(prg)
  13. }, 100)
  14. window.onload = () => {
  15. window.clearInterval(timer)
  16. window.setInterval(() => {
  17. if (prg >= 100) { // 到达终点,关闭定时器
  18. window.clearInterval(timer)
  19. prg = 100
  20. $loading.hide()
  21. } else {
  22. prg++
  23. }
  24. $progress.html(prg + '%')
  25. console.log(prg)
  26. }, 10) // 时间间隔缩短
  27. }

ok,这差不多就是我们想要的功能了,我们来提炼一下代码,把重复的代码给封装一下:

  1. var $loading = $('#loading')
  2. var $progress = $('#progress')
  3. var prg = 0
  4. var timer = 0
  5. progress(80, 100)
  6. window.onload = () => {
  7. progress(100, 10, () => {
  8. $loading.hide()
  9. })
  10. }
  11. function progress (dist, delay, callback) {
  12. window.clearInterval(timer)
  13. timer = window.setInterval(() => {
  14. if (prg >= dist) {
  15. window.clearInterval(timer)
  16. prg = dist
  17. callback && callback()
  18. } else {
  19. prg++
  20. }
  21. $progress.html(prg + '%')
  22. console.log(prg)
  23. }, delay)
  24. }

我们得到了一个progress函数,这个函数就是我们主要的功能模块,通过传入一个目标值、一个时间间隔,就可以模拟进度的演化过程。

目前来看,这个进度还是有些问题的:

  1. 进度太平均,相同的时间间隔,相同的增量,不符合网络环境的特点;
  2. window.onload太快,我们还来不及看清100%,loading就已经不见了;
  3. 每次第一阶段都是在80%就暂停了,露馅儿了;

第一个点,我们要让时间间隔随机,增量也随机;第二个点很简单,我们延迟一下就好了;第三点也需要我们随机产生一个初始值。

增量随机很好办,如何让时间间隔随机?setInterval是无法动态设置delay的,那么我们就要把它改造一下,使用setTimeout来实现。(setInterval跟setTimeout的用法和区别就不细说了吧?)

  1. var $loading = $('#loading')
  2. var $progress = $('#progress')
  3. var prg = 0
  4. var timer = 0
  5. progress([80, 90], [1, 3], 100) // 使用数组来表示随机数的区间
  6. window.onload = () => {
  7. progress(100, [1, 5], 10, () => {
  8. window.setTimeout(() => { // 延迟了一秒再隐藏loading
  9. $loading.hide()
  10. }, 1000)
  11. })
  12. }
  13. function progress (dist, speed, delay, callback) {
  14. var _dist = random(dist)
  15. var _delay = random(delay)
  16. var _speed = random(speed)
  17. window.clearTimeout(timer)
  18. timer = window.setTimeout(() => {
  19. if (prg + _speed >= _dist) {
  20. window.clearTimeout(timer)
  21. prg = _dist
  22. callback && callback()
  23. } else {
  24. prg += _speed
  25. progress (_dist, speed, delay, callback)
  26. }
  27. $progress.html(parseInt(prg) + '%') // 留意,由于已经不是自增1,所以这里要取整
  28. console.log(prg)
  29. }, _delay)
  30. }
  31. function random (n) {
  32. if (typeof n === 'object') {
  33. var times = n[1] - n[0]
  34. var offset = n[0]
  35. return Math.random() * times + offset
  36. } else {
  37. return n
  38. }
  39. }

至此,我们差不多完成了需求。

but,还有一个比较隐蔽的问题,我们现在使用window.onload,发现从进入页面,到window.onload这中间相隔时间十分短,我们基本是感受不到第一阶段进度(80%)的,这是没有问题的——我们在意的是,如果页面的加载资源数量很多,体积很大的时候,从进入页面,到window.onload就不是这么快速了,这中间可能会很漫长(5~20秒不等),但事实上,我们只需要为 首屏资源 的加载争取时间就可以了,不需要等待所有资源就绪,而且更快地呈现页面也是提高用户体验的关键。

我们应该考虑页面loading停留过久的情况,我们需要为loading设置一个超时时间,超过这个时间,假设window.onload还没有完成,我们也要把进度推到100%,把loading结束掉。

  1. var $loading = $('#loading')
  2. var $progress = $('#progress')
  3. var prg = 0
  4. var timer = 0
  5. progress([80, 90], [1, 3], 100) // 使用数组来表示随机数的区间
  6. window.onload = () => {
  7. progress(100, [1, 5], 10, () => {
  8. window.setTimeout(() => { // 延迟了一秒再隐藏loading
  9. $loading.hide()
  10. }, 1000)
  11. })
  12. }
  13. window.setTimeout(() => { // 设置5秒的超时时间
  14. progress(100, [1, 5], 10, () => {
  15. window.setTimeout(() => { // 延迟了一秒再隐藏loading
  16. $loading.hide()
  17. }, 1000)
  18. })
  19. }, 5000)
  20. function progress (dist, speed, delay, callback) {
  21. var _dist = random(dist)
  22. var _delay = random(delay)
  23. var _speed = random(speed)
  24. window.clearTimeout(timer)
  25. timer = window.setTimeout(() => {
  26. if (prg + _speed >= _dist) {
  27. window.clearTimeout(timer)
  28. prg = _dist
  29. callback && callback()
  30. } else {
  31. prg += _speed
  32. progress (_dist, speed, delay, callback)
  33. }
  34. $progress.html(parseInt(prg) + '%') // 留意,由于已经不是自增1,所以这里要取整
  35. console.log(prg)
  36. }, _delay)
  37. }
  38. function random (n) {
  39. if (typeof n === 'object') {
  40. var times = n[1] - n[0]
  41. var offset = n[0]
  42. return Math.random() * times + offset
  43. } else {
  44. return n
  45. }
  46. }

我们直接设置了一个定时器,5s的时间来作为超时时间。这样做是可以的。

but,还是有问题,这个定时器是在js加载完毕之后才开始生效的,也就是说,我们忽略了js加载完毕之前的时间,这误差可大可小,我们设置的5s,实际用户可能等待了8s,这是有问题的。我们做用户体验,需要从实际情况去考虑,所以这个开始时间还需要再提前一些,我们在head里来记录这个开始时间,然后在js当中去做对比,如果时间差大于超时时间,那我们就可以直接执行最后的完成步骤,如果小于超时时间,则等待 剩余的时间 过后,再完成进度。

先在head里埋点,记录用户进入页面的时间loadingStartTime

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>写一个网页进度loading</title>
  5. <script>
  6. window.loadingStartTime = new Date()
  7. </script>
  8. <script src="index.js"></script>
  9. </head>
  10. <body>
  11. <div class="loading" id="loading">
  12. <div class="progress" id="progress">0%</div>
  13. </div>
  14. </body>
  15. </html>

然后,我们对比 当前的时间 ,看是否超时:(为了方便复用代码,我把完成的部分封装成函数complete)

  1. var $loading = $('#loading')
  2. var $progress = $('#progress')
  3. var prg = 0
  4. var timer = 0
  5. var now = new Date() // 记录当前时间
  6. var timeout = 5000 // 超时时间
  7. progress([80, 90], [1, 3], 100)
  8. window.onload = () => {
  9. complete()
  10. }
  11. if (now - loadingStartTime > timeout) { // 超时
  12. complete()
  13. } else {
  14. window.setTimeout(() => { // 未超时,则等待剩余时间
  15. complete()
  16. }, timeout - (now - loadingStartTime))
  17. }
  18. function complete () { // 封装完成进度功能
  19. progress(100, [1, 5], 10, () => {
  20. window.setTimeout(() => {
  21. $loading.hide()
  22. }, 1000)
  23. })
  24. }
  25. function progress (dist, speed, delay, callback) {
  26. var _dist = random(dist)
  27. var _delay = random(delay)
  28. var _speed = random(speed)
  29. window.clearTimeout(timer)
  30. timer = window.setTimeout(() => {
  31. if (prg + _speed >= _dist) {
  32. window.clearTimeout(timer)
  33. prg = _dist
  34. callback && callback()
  35. } else {
  36. prg += _speed
  37. progress (_dist, speed, delay, callback)
  38. }
  39. $progress.html(parseInt(prg) + '%')
  40. console.log(prg)
  41. }, _delay)
  42. }
  43. function random (n) {
  44. if (typeof n === 'object') {
  45. var times = n[1] - n[0]
  46. var offset = n[0]
  47. return Math.random() * times + offset
  48. } else {
  49. return n
  50. }
  51. }

至此,我们算是完整地实现了这一功能。

然而,事情还没有结束,少年你太天真。

如果目的是为了写一个纯粹障眼法的伪loading,那跟其他loading的实现就没什么区别了,我们做事讲究脚踏实地,能实现的实现,不能实现的,为了团队和谐,我们不得已坑蒙拐骗。那么我们还能更贴近实际情况一点吗?其实是可以的。

我们来分析一个场景,假设我们想让我们的loading更加真实一些,那么我们可以选择性地对页面上几个比较大的资源的加载进行跟踪,然后拆分整个进度条,比如我们页面有三张大图a、b、c,那么我们将进度条拆成五段,每加载完一张图我们就推进一个进度:

随机初始化[10, 20] ->
图a推进20%的进度 ->
图b推进25%的进度 ->
图c推进30%的进度 ->
完成100%

这三张图要占20% + 25% + 30% = 75%的进度。

问题是,如果图片加载完成是按照顺序来的,那我们可以很简单地:10(假设初始进度是10%) -> 30 -> 55 -> 85 -> 100,但事实是,图片不会按照顺序来,谁早到谁晚到是说不准的,所以我们需要更合理的方式去管理这些进度增量,使它们不会互相覆盖。

  1. 我们需要一个能够替我们累计增量的变量next
  2. 由于我们的progress都是传目的进度的,我们需要另外一个函数add,来传增量进度。
  1. var $loading = $('#loading')
  2. var $progress = $('#progress')
  3. var prg = 0
  4. var timer = 0
  5. var now = new Date()
  6. var timeout = 5000
  7. var next = prg
  8. add([30, 50], [1, 3], 100) // 第一阶段
  9. window.setTimeout(() => { // 模拟图a加载完
  10. add(20, [1, 3], 200)
  11. }, 1000)
  12. window.setTimeout(() => { // 模拟图c加载完
  13. add(30, [1, 3], 200)
  14. }, 2000)
  15. window.setTimeout(() => { // 模拟图b加载完
  16. add(25, [1, 3], 200)
  17. }, 2500)
  18. window.onload = () => {
  19. complete()
  20. }
  21. if (now - loadingStartTime > timeout) {
  22. complete()
  23. } else {
  24. window.setTimeout(() => {
  25. complete()
  26. }, timeout - (now - loadingStartTime))
  27. }
  28. function complete () {
  29. add(100, [1, 5], 10, () => {
  30. window.setTimeout(() => {
  31. $loading.hide()
  32. }, 1000)
  33. })
  34. }
  35. function add (dist, speed, delay, callback) {
  36. var _dist = random(dist)
  37. if (next + _dist > 100) { // 对超出部分裁剪对齐
  38. next = 100
  39. } else {
  40. next += _dist
  41. }
  42. progress(next, speed, delay, callback)
  43. }
  44. function progress (dist, speed, delay, callback) {
  45. var _delay = random(delay)
  46. var _speed = random(speed)
  47. window.clearTimeout(timer)
  48. timer = window.setTimeout(() => {
  49. if (prg + _speed >= dist) {
  50. window.clearTimeout(timer)
  51. prg = dist
  52. callback && callback()
  53. } else {
  54. prg += _speed
  55. progress (dist, speed, delay, callback)
  56. }
  57. $progress.html(parseInt(prg) + '%')
  58. console.log(prg)
  59. }, _delay)
  60. }
  61. function random (n) {
  62. if (typeof n === 'object') {
  63. var times = n[1] - n[0]
  64. var offset = n[0]
  65. return Math.random() * times + offset
  66. } else {
  67. return n
  68. }
  69. }

我们这里为了方便,用setTimeout来模拟图片的加载,真实应用应该是使用image.onload

以上,就是我们一步步实现一个进度loading的过程了,演示代码可以戳我的codePen 写一个网页进度loading

看似很简单的一个功能,其实仔细推敲,还是有很多细节要考虑的。

到这里,其实真的已经完成了,代码有点多有点乱是不是?你可以整理一下,封装成为插件的。

然而,好吧,其实我已经把这个进度封装成插件了。。。

没错,其实我就是来帮自己打广告的。。。

好吧,github仓库在此 ez-progress

ez-progress 是一个web(伪)进度插件,使用 ez-progress 实现这个功能非常简单:

  1. var Progress = require('ez-progress')
  2. var prg = new Progress()
  3. var $loading = $('#loading')
  4. var $progress = $('#progress')
  5. prg.on('progress', function (res) {
  6. var progress = parseInt(res.progress) // 注意进度取整,不然有可能会出现小数
  7. $progress.html(progress + '%')
  8. })
  9. prg.go([60, 70], function (res) {
  10. prg.complete(null, [0, 5], [0, 50]) // 飞一般地冲向终点
  11. }, [0, 3], [0, 200])
  12. window.onload = function () {
  13. prg.complete(null, [0, 5], [0, 50]) // 飞一般地冲向终点
  14. }

木油错,94这么简单!

这可能是我目前写过最短的博文了,由此看出以前是有多么的啰嗦,哈哈哈哈!

写一个网页进度loading的更多相关文章

  1. 转---写一个网页进度loading

    作者:jack_lo www.jianshu.com/p/4c93f5bd9861 如有好文章投稿,请点击 → 这里了解详情 loading随处可见,比如一个app经常会有下拉刷新,上拉加载的功能,在 ...

  2. 前端网页进度Loading

    loading随处可见,比如一个app经常会有下拉刷新,上拉加载的功能,在刷新和加载的过程中为了让用户感知到 load 的过程,我们会使用一些过渡动画来表达.最常见的比如“转圈圈”,“省略号”等等. ...

  3. 如何用SVG写一个环形进度条以及动画

    本次案例主要使用了svg的三个元素,分别为circle.text.path,关于svg的介绍大家可以看MDN上的相关教程,传送门 由于svg可以写到HTML中,所以这里我们就可以很方便的做进度条加载动 ...

  4. 如何写一个网页标题title的闪动提示(转)

    通过网页title来提示用户有新消息这个功能很常见,比如现在的微博,还有一些邮箱,这个功能都很常见.如何实现则个功能呢? 思路是:通过ajax访问后台,若有新消息,则将网页的title替换为 提示信息 ...

  5. 如何写一个网页标题title的闪动提示

    通过网页title来提示用户有新消息这个功能很常见,比如现在的微博,还有一些邮箱,这个功能都很常见.如何实现则个功能呢? 思路是:通过ajax访问后台,若有新消息,则将网页的title替换为 提示信息 ...

  6. 自己写一个网页版的Markdown实时编辑器

    这几天忙着使用Python+Django+sqlite 搭建自己的博客系统,但是单纯的使用H5的TextArea,简直太挫了有木有.所以,就想模仿一下人家内嵌到网页上的Markdown编辑器,从而让自 ...

  7. 用原生JS写一个网页版的2048小游戏(兼容移动端)

    这个游戏JS部分全都是用原生JS代码写的,加有少量的CSS3动画,并简单的兼容了一下移动端. 先看一下在线的demo:https://yuan-yiming.github.io/2048-online ...

  8. python写一个网页翻译器

    import urllib.requesturl='https://fanyi.baidu.com/v2transapi'data={}data['i']=opdata['from']='AUTO'd ...

  9. 通过用jQuery写一个页面,我学到了什么

    概述 前几天面试,hr发来一个测试文件,让我做做看.我一看,其实就是根据PSD需求写一个页面,再加上一些互动效果即可. 刚好我之前学了切图,jquery等知识还没练手过,于是高兴的答应了. 最后花了3 ...

随机推荐

  1. MySql查询分页数据

    MySql查询分页数据

  2. SQL SERVER 备份数据库时候错误处理

    当备份数据库时候出现如下错误时候 只需要删除备份目标就行了

  3. dockerfile自动创建docker镜像

    特点:类似于ansible 剧本,大小几kb 而,手动做的镜像,要几百M,甚至上G ,传输不方便 dockerfile 支持自定义容器的初始命令 dockerfile只要组成部分: 基础镜像信息 FR ...

  4. 普及组R4

    T3 链接:C 输入一个长度为n的数组a[i],下标从0开始(0到n-1) 保证n是2的整数次幂, 对于每个i (0 <= i < n) 求所有满足((i & j) == j)的a ...

  5. CodeForces 258D Little Elephant and Broken Sorting(期望)

    CF258D Little Elephant and Broken Sorting 题意 题意翻译 有一个\(1\sim n\)的排列,会进行\(m\)次操作,操作为交换\(a,b\).每次操作都有\ ...

  6. leetcode-77-组合-字典序

    题目描述: 第一次提交: class Solution: def combine(self, n: int, k: int) -> List[List[int]]: res = [] def b ...

  7. 洛谷P5338 [TJOI2019]甲苯先生的滚榜

    原题链接洛谷P5338 [TJOI2019]甲苯先生的滚榜 题目描述 甲苯先生在制作一个online judge,他发现做比赛的人们很关心自己的排名(显而易见),在acm赛制的比赛中,如果通过题目数量 ...

  8. Spring Boot与监控管理

    概念: 通过引入spring-boot-starter-actuator,可以使用Spring Boot为我们提供的准生产环境下的应用监控和管理功能.我们可以通过HTTP,JMX,SSH协议来进行操作 ...

  9. springcluoud入门

    概念: Spring Cloud是一个分布式的整体解决方案. Spring Cloud 为开发者提供了在分布式系统(配置管理,服务发现,熔断,路由,微代理,控制总线,一次性token,全局琐,lead ...

  10. 基于SpringBoot+AntDesign的快速开发平台,JeecgBoot 2.0.2 版本发布

    Jeecg-Boot 是一款基于SpringBoot+代码生成器的快速开发平台! 采用前后端分离架构:SpringBoot,Ant-Design-Vue,Mybatis,Shiro,JWT. 强大的代 ...