網頁頂部進度條外掛的有四五種,基本原理就是動態地建立一個元素,然後通過設定它的width來實現動畫效果,width增長到達指定位置時,將其去掉。 來看看nanobar.js作者jacoborus是怎麼做到的吧!
/* http://nanobar.micronube.com/ || https://github.com/jacoborus/nanobar/ MIT LICENSE */
(function (root) {
'use strict'
// container styles
var css = '.nanobar{width:100%;height:4px;z-index:9999;top:0}.bar{width:0;height:100%;transition:height .3s;background:#000}'
// add required css in head div
function addCss () {
var s = document.getElementById('nanobarcss')
// check whether style tag is already inserted
if (s === null) {
s = document.createElement('style')
s.type = 'text/css'
s.id = 'nanobarcss'
document.head.insertBefore(s, document.head.firstChild)
// the world
if (!s.styleSheet) return s.appendChild(document.createTextNode(css))
// IE
s.styleSheet.cssText = css
}
}
function addClass (el, cls) {
if (el.classList) el.classList.add(cls)
else el.className += ' ' + cls
}
// create a progress bar
// this will be destroyed after reaching 100% progress
function createBar (rm) {
// create progress element
var el = document.createElement('div'),
width = 0,
here = 0,
on = 0,
bar = {
el: el,
go: go
}
addClass(el, 'bar')
// animation loop
function move () {
var dist = width - here
if (dist < 0.1 && dist > -0.1) {
place(here)
on = 0
if (width === 100) {
el.style.height = 0
setTimeout(function () {
rm(el)
}, 300)
}
} else {
place(width - dist / 4)
setTimeout(go, 16)
}
}
// set bar width
function place (num) {
width = num
el.style.width = width + '%'
}
function go (num) {
if (num >= 0) {
here = num
if (!on) {
on = 1
move()
}
} else if (on) {
move()
}
}
return bar
}
function Nanobar (opts) {
opts = opts || {}
// set options
var el = document.createElement('div'),
applyGo,
nanobar = {
el: el,
go: function (p) {
// expand bar
applyGo(p)
// create new bar when progress reaches 100%
if (p === 100) {
init()
}
}
}
// remove element from nanobar container
function rm (child) {
el.removeChild(child)
}
// create and insert progress var in nanobar container
function init () {
var bar = createBar(rm)
el.appendChild(bar.el)
applyGo = bar.go
}
addCss()
addClass(el, 'nanobar')
if (opts.id) el.id = opts.id
if (opts.classname) addClass(el, opts.classname)
// insert container
if (opts.target) {
// inside a div
el.style.position = 'relative'
opts.target.insertBefore(el, opts.target.firstChild)
} else {
// on top of the page
el.style.position = 'fixed'
document.getElementsByTagName('body')[0].appendChild(el)
}
init()
return nanobar
}
if (typeof exports === 'object') {
// CommonJS
module.exports = Nanobar
} else if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], function () { return Nanobar })
} else {
// Browser globals
root.Nanobar = Nanobar
}
}(this))
複製程式碼
大體看下來,這個外掛有這樣幾個特點:
- dom+js原生選擇器
- 支援模組化
- es5+IIFE
- 不用分號派
詳細來看:
在程式的開頭,定義了必要的Css屬性,包括bar(主體)和Nanobar(容器)兩個class:
.nanobar{
width:100%;
height:4px;
z-index:9999;
top:0
}
.bar{
width:0;
height:100%;
transition:height .3s;
background:#000
}
複製程式碼
從css內容來看,僅有.bar有transition:height .3s
的過渡設定,height
過渡發生的時間應該是被刪除時。在橫向應該是沒有動畫效果,但是從官網演示效果來看,橫向仍然有一定的動畫效果,這個問題下面會提到。
建構函式NanoBar
NanoBar接受一個opts作為引數,文件記載的opts詳細內容如下:
名稱 | 功能 |
---|---|
id | 指定nanobar的id |
classname | 指定nanobar的class |
target | 指定Nanobar的表示位置,一般對於做頂部進度條來說不到。值得一提的是,這個引數型別為DOM Element,你必須使用document.getxxxxx 之類的方法為其賦值。 |
首先宣告瞭三個變數:
名稱 | 描述 |
---|---|
el | 這就是動態建立的元素-一個既沒有ID也沒有Class的空div |
applyGo | 進度條移動的方法 |
nanobar | nanobar物件,它將在new建構函式時作為結果返回 |
其中,nanobar包含這兩個元素:
名稱 | 描述 |
---|---|
el | 上面動態建立的元素 |
go | 對外開放的方法,引數為數值,那麼它肯定代表了百分比而不是畫素等實際物理單位 |
此處的go處理內實質上呼叫的是applyGo,而applyGo此時肯定為undefined
,所以applyGo實際上在別處賦值。這樣處理的結果,相當於是一層封裝,隱藏了內部實際的go方法內容。
另外也可以得出nanobar的最簡單的使用方法:
var nanobar = new Nanobar();
nanobar.go(80);
複製程式碼
接下來,宣告瞭兩個內部函式,這兩個內部函式可以訪問上面提到的三個變數:
名稱 | 作用 |
---|---|
rm | 用於進度完成後,刪除動態建立的元素 |
init | 初始化方法,這個需要重點關注 |
然後是一些必要處理,由這三個部分組成:
addCss
方法,為head
節點內增加<style id="nanobarcss">
節點,並把上文的css填入其中。- 呼叫
addClass
方法,建立類名為nanobar
的容器。需要注意的是,相比於直接操作className
方法內呼叫了HTML5的新APIclassList
,使用它可以像jquery的addClass、removeClass一樣方便的對dom物件的class進行增加刪除判斷。更多資訊請看這裡。 - 接下來是對
opts
引數進行處理: 主要是為el元素賦予id和className,根據是否指定了父容器,也就是target
,改變容器的position,並且最終將它插入到對應的位置上。
接著來看init()方法:
前面所有的操作,建立了一個名為nanobar
的容器,接下來就該建立bar
主體了。
可以看到,bar
變數內仍然和nanobar
一樣,由el
和go
兩部分組成,go
最終將被賦值到外層容器的applyGo
,el
將被作為子元素插入到外層容器的el
內。
這樣,當用最簡單的方式呼叫go時,它的順序就是這樣的:
容器nanobar.go => applyGo => 本體bar.go
呼叫了go方法後,為什麼橫向會有一定的動畫效果呢?
觀察一下nanobar的動作方法go
、move
、place
其中的控制量有這麼幾個:
名稱 | 作用 |
---|---|
on | 相當於布林flag,標識了進度是否完成了 |
here | 終點位置 |
dist | 與終點相比的距離 |
實際處理流程可以這樣表示:
形成動畫的根本原因則是這麼兩個原因:
- 方法
place(width - dist / 4)
對剩餘空間的細分 - 第58緊隨其後的
setTimeout(go,16)
,假設把x軸看成是16ms,把Y軸看成是每次細分的長度,將會得到一個影象類似於log2x(前期趨勢大,後期趨勢平穩,類似於動畫函式中的ease-out
)的表示式。
另外,引用作者原話:
Nanobar injects a style tag in your HTML head. Bar divs has class .bar, and its containers .nanobar, so you can overwrite its values. You should know what to do with that ;)