當前位置:
首頁 > 最新 > 寫一個網頁進度loading

寫一個網頁進度loading

作者:jack_lo

www.jianshu.com/p/4c93f5bd9861

loading隨處可見,比如一個app經常會有下拉刷新,上拉載入的功能,在刷新和載入的過程中為了讓用戶感知到 load 的過程,我們會使用一些過渡動畫來表達。最常見的比如「轉圈圈」,「省略號」等等。

網頁loading有很多用處,比如頁面的載入進度,數據的載入過程等等,數據的載入loading很好做,只需要在載入數據之前(before ajax)顯示loading效果,在數據返回之後(ajax completed)結束loading效果,就可以了。

但是頁面的載入進度,需要一點技巧。

頁面載入進度一直以來都是一個常見而又晦澀的需求,常見是因為它在某些「重」網頁(特別是網頁遊戲)的應用特別重要;晦澀是因為web的特性,各種零散資源決定它很難是「真實」的進度,只能是一種「假」的進度,至少在邏輯代碼載入完成之前,我們都不能統計到進度,而邏輯代碼自身的進度也無法統計。另外,我們不可能監控到所有資源的載入情況。

所以頁面的載入進度都是「假」的,它存在的目的是為了提高用戶體驗,使用戶不至於在打開頁面之後長時間面對一片空白,導致用戶流失。

既然是「假」的,我們就要做到「模擬」才有用。模擬是有意義的,事實上用戶並不在乎某一刻你是不是真的載入到了百分之幾,他只關心你還要load多久。所以接下來我們就來實現一個頁面載入進度loading。

首先準備一段loading的html:

寫一個網頁進度loading

0%

來點樣式裝扮一下:

.loading{

display:table;

position:fixed;

top:;

left:;

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 )

varprg=// 初始化進度

vartimer=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 )

varprg=

vartimer=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 )

varprg=

vartimer=

progress(80,100)

window.onload=()=>{

progress(100,10,()=>{

$loading.hide()

})

}

functionprogress(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函數,這個函數就是我們主要的功能模塊,通過傳入一個目標值、一個時間間隔,就可以模擬進度的演化過程。

目前來看,這個進度還是有些問題的:

進度太平均,相同的時間間隔,相同的增量,不符合網路環境的特點;

window.onload太快,我們還來不及看清100%,loading就已經不見了;

每次第一階段都是在80%就暫停了,露餡兒了;

第一個點,我們要讓時間間隔隨機,增量也隨機;第二個點很簡單,我們延遲一下就好了;第三點也需要我們隨機產生一個初始值。

增量隨機很好辦,如何讓時間間隔隨機?setInterval是無法動態設置delay的,那麼我們就要把它改造一下,使用setTimeout來實現。(setInterval跟setTimeout的用法和區別就不細說了吧?)

var$loading=$( #loading )

var$progress=$( #progress )

varprg=

vartimer=

progress([80,90],[1,3],100)// 使用數組來表示隨機數的區間

window.onload=()=>{

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

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

$loading.hide()

},1000)

})

}

functionprogress(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)

}

functionrandom(n){

if(typeofn=== object ){

vartimes=n[1]-n[]

varoffset=n[]

returnMath.random()*times+offset

}else{

returnn

}

}

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

but,還有一個比較隱蔽的問題,我們現在使用window.onload,發現從進入頁面,到window.onload這中間相隔時間十分短,我們基本是感受不到第一階段進度(80%)的,這是沒有問題的——我們在意的是,如果頁面的載入資源數量很多,體積很大的時候,從進入頁面,到window.onload就不是這麼快速了,這中間可能會很漫長(5~20秒不等),但事實上,我們只需要為 首屏資源 的載入爭取時間就可以了,不需要等待所有資源就緒,而且更快地呈現頁面也是提高用戶體驗的關鍵。

我們應該考慮頁面loading停留過久的情況,我們需要為loading設置一個超時時間,超過這個時間,假設window.onload還沒有完成,我們也要把進度推到100%,把loading結束掉。

var$loading=$( #loading )

var$progress=$( #progress )

varprg=

vartimer=

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)

functionprogress(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)

}

functionrandom(n){

if(typeofn=== object ){

vartimes=n[1]-n[]

varoffset=n[]

returnMath.random()*times+offset

}else{

returnn

}

}

我們直接設置了一個定時器,5s的時間來作為超時時間。這樣做是可以的。

but,還是有問題,這個定時器是在js載入完畢之後才開始生效的,也就是說,我們忽略了js載入完畢之前的時間,這誤差可大可小,我們設置的5s,實際用戶可能等待了8s,這是有問題的。我們做用戶體驗,需要從實際情況去考慮,所以這個開始時間還需要再提前一些,我們在head里來記錄這個開始時間,然後在js當中去做對比,如果時間差大於超時時間,那我們就可以直接執行最後的完成步驟,如果小於超時時間,則等待 剩餘的時間 過後,再完成進度。

先在head里埋點,記錄用戶進入頁面的時間loadingStartTime:

寫一個網頁進度loading

window.loadingStartTime=newDate()

0%

然後,我們對比 當前的時間 ,看是否超時:(為了方便復用代碼,我把完成的部分封裝成函數complete)

var$loading=$( #loading )

var$progress=$( #progress )

varprg=

vartimer=

varnow=newDate()// 記錄當前時間

vartimeout=5000// 超時時間

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

window.onload=()=>{

complete()

}

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

complete()

}else{

window.setTimeout(()=>{// 未超時,則等待剩餘時間

complete()

},timeout-(now-loadingStartTime))

}

functioncomplete(){// 封裝完成進度功能

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

window.setTimeout(()=>{

$loading.hide()

},1000)

})

}

functionprogress(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)

}

functionrandom(n){

if(typeofn=== object ){

vartimes=n[1]-n[]

varoffset=n[]

returnMath.random()*times+offset

}else{

returnn

}

}

至此,我們算是完整地實現了這一功能。

然而,事情還沒有結束,少年你太天真。

如果目的是為了寫一個純粹障眼法的偽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,但事實是,圖片不會按照順序來,誰早到誰晚到是說不準的,所以我們需要更合理的方式去管理這些進度增量,使它們不會互相覆蓋。

我們需要一個能夠替我們累計增量的變數next;

由於我們的progress都是傳目的進度的,我們需要另外一個函數add,來傳增量進度。

var$loading=$( #loading )

var$progress=$( #progress )

varprg=

vartimer=

varnow=newDate()

vartimeout=5000

varnext=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))

}

functioncomplete(){

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

window.setTimeout(()=>{

$loading.hide()

},1000)

})

}

functionadd(dist,speed,delay,callback){

var_dist=random(dist)

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

next=100

}else{

next+=_dist

}

progress(next,speed,delay,callback)

}

functionprogress(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)

}

functionrandom(n){

if(typeofn=== object ){

vartimes=n[1]-n[]

varoffset=n[]

returnMath.random()*times+offset

}else{

returnn

}

}

我們這裡為了方便,用setTimeout來模擬圖片的載入,真實應用應該是使用image.onload。

以上,就是我們一步步實現一個進度loading的過程了,演示代碼可以戳我的codePen 寫一個網頁進度loading。

看似很簡單的一個功能,其實仔細推敲,還是有很多細節要考慮的。

到這裡,其實真的已經完成了,代碼有點多有點亂是不是?你可以整理一下,封裝成為插件的。

然而,好吧,其實我已經把這個進度封裝成插件了。。。

沒錯,其實我就是來幫自己打廣告的。。。

好吧,github倉庫在此 ez-progress。

ez-progress 是一個web(偽)進度插件,使用 ez-progress 實現這個功能非常簡單:

varProgress=require( ez-progress )

varprg=newProgress()

var$loading=$( #loading )

var$progress=$( #progress )

prg.on( progress ,function(res){

varprogress=parseInt(res.progress)// 注意進度取整,不然有可能會出現小數

$progress.html(progress+ % )

})

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

prg.complete(null,[,5],[,50])// 飛一般地沖向終點

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

window.onload=function(){

prg.complete(null,[,5],[,50])// 飛一般地沖向終點

}

木油錯,94這麼簡單!

看完本文有收穫?請轉發分享給更多人

關注「前端大全」,提升前端技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 前端大全 的精彩文章:

Chrome 開發者控制台中,你可能意想不到的功能
管理頁面的 setTimeout&setInterval
為什麼我們要做三份 Webpack 配置文件
Web 的現狀:網頁性能提升指南
你知道URL、URI和URN三者之間的區別嗎?

TAG:前端大全 |