1. 程式人生 > >143行js頂部進度條最小外掛-nanobar.js原始碼解析

143行js頂部進度條最小外掛-nanobar.js原始碼解析

網頁頂部進度條外掛的有四五種,基本原理就是動態地建立一個元素,然後通過設定它的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 初始化方法,這個需要重點關注

然後是一些必要處理,由這三個部分組成:

  1. addCss方法,為head節點內增加<style id="nanobarcss">節點,並把上文的css填入其中。
  2. 呼叫addClass方法,建立類名為nanobar的容器。需要注意的是,相比於直接操作className方法內呼叫了HTML5的新APIclassList,使用它可以像jquery的addClass、removeClass一樣方便的對dom物件的class進行增加刪除判斷。更多資訊請看這裡
  3. 接下來是對opts引數進行處理: 主要是為el元素賦予id和className,根據是否指定了父容器,也就是target,改變容器的position,並且最終將它插入到對應的位置上。

接著來看init()方法:

前面所有的操作,建立了一個名為nanobar的容器,接下來就該建立bar主體了。

可以看到,bar變數內仍然和nanobar一樣,由elgo兩部分組成,go最終將被賦值到外層容器的applyGoel將被作為子元素插入到外層容器的el內。

這樣,當用最簡單的方式呼叫go時,它的順序就是這樣的:

容器nanobar.go => applyGo => 本體bar.go

呼叫了go方法後,為什麼橫向會有一定的動畫效果呢?

觀察一下nanobar的動作方法gomoveplace 其中的控制量有這麼幾個:

名稱 作用
on 相當於布林flag,標識了進度是否完成了
here 終點位置
dist 與終點相比的距離

實際處理流程可以這樣表示:

Created with Raphaël 2.2.0開始place(width - dist / 4)dist = width -heredist < 0.1高度置零,刪除元素結束yesno

形成動畫的根本原因則是這麼兩個原因:

  1. 方法place(width - dist / 4)對剩餘空間的細分
  2. 第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 ?