写一个网页进度loading

2020年05月16日 阅读数:83
这篇文章主要向大家介绍写一个网页进度loading,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

做者:jack_lojavascript

www.jianshu.com/p/4c93f5bd9861css

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

 

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

 

网页loading有不少用处,好比页面的加载进度,数据的加载过程等等,数据的加载loading很好作,只须要在加载数据以前(before ajax)显示loading效果,在数据返回以后(ajax completed)结束loading效果,就能够了。git

 

可是页面的加载进度,须要一点技巧。es6

 

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

 

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

 

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

 

首先准备一段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

144 
做者  jack_lo 
2016.12.23 19:51*  字数 2402  阅读 1040 评论 9

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这么简单!

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