介绍
NProgress 相信作为前端,大家应该都不陌生这个小玩意,它出现在页面的顶部,作为一个进度条,它默认长这样
基本上每个项目都会用到它,大大优化了用户体验,毕竟有个进度条在滚动,说明这个网页并没有停止运行,特别是对于一些需要加载大文件的项目,更需要这种进度条来不断提示用户,延长用户的耐心
原理
NProgress 最常用的 只有 2 个Api,分别是NProgress.start 和NProgress.done
当然还有其他Api,详细使用请戳👉NProgress Npm
在看源码之前,其实心里已经对于如何制作进度条的有想法了
- 简单来说,创建一个
元素,fixed在顶部- 然后不断的更改
元素的 位置 / 宽度,这样就形成了一个不断滚动的效果- 当 执行
done的时候,把这个元素删除
但是它作为一个经久不衰的一个库,肯定有他过人之处,看到最后,确实从这个十年前的老库中学到了不少东西
为了方便理解,一些配置量直接替换成默认值,想看完整代码,请戳👉NProgress gitHub
NProgress.start
// 默认 NProgress.status = null
NProgress.start = function() {
if (!NProgress.status) NProgress.set(0);
var work = function() {
setTimeout(function() {
if (!NProgress.status) return;
NProgress.trickle();
work();
}, 200);
};
work();
return this;
};
由于 NProgress.status = null,先执行NProgress.set
1. NProgress.set (部分)
NProgress.set = function(n) {
// ...
// 保证 n 在 0.08 和 1 之间
n = clamp(n, 0.08, 1);
NProgress.status = (n === 1 ? null : n);
// ...
return this;
};
工具函数 clamp
function clamp(n, min, max) {
if (n < min) return min;
if (n > max) return max;
return n;
}
此时 NProgress.status 被设置成 0.08 ;
ok, 再去看执行work 函数
work 函数中 一个定时器不断的执行NProgress.trickle 函数
trickle翻译成中文是细流的意思
2. NProgress.trickle
NProgress.trickle = function() {
return NProgress.inc();
};
得,再去找 NProgress.inc 函数
🚀 3. NProgress.inc(重要)
NProgress.inc = function(amount){
// ...
// NProgress.status = 0.08
var n = NProgress.status;
if (typeof amount !== 'number') {
if (n >= 0 && n < 0.2) { amount = 0.1; }
else if (n >= 0.2 && n < 0.5) { amount = 0.04; }
else if (n >= 0.5 && n < 0.8) { amount = 0.02; }
else if (n >= 0.8 && n < 0.99) { amount = 0.005; }
else { amount = 0; }
}
n = clamp(n + amount, 0, 0.994);
return NProgress.set(n);
}
这个是nProgress不断缓慢前进的关键
由于没有传入amount,typeof amount == undefined,又由于n == 0.08,所以是amount = 0.1,此时速度较快
不过随着n数值越来越大,amount 也就越来越小,增长的速度也会逐渐的慢下来 🐌
然后继续调用 NProgress.set
4. NProgress.set(补充)
NProgress.set = function(n) {
var started = typeof NProgress.status === 'number'
var progress = NProgress.render(!started),
/* 🔥 Repaint 这一步很关键,就是为了保证 dom 渲染完毕*/
progress.offsetWidth;
if (n === 1){
setTimeout(function() {
progress.style = {
transition: 'all 200ms linear'
opacity:0
}
// 移除元素
setTimeout(function() {
progress.parentNode.removeChild(progress)
}, 200);
}, 200);
}
}
工具方法render
NProgress.render = function(fromStart){
// 如果已经存在nprogress,就不需要重复创建了
if (!!document.getElementById('nprogress')) return document.getElementById('nprogress');
// 如果没有创建 元素
var progress = document.createElement('div');
progress.id = 'nprogress';
progress.innerHTML = `<div class="bar" role="bar">
<div class="peg"></div>
</div>
<div class="spinner" role="spinner">
<div class="spinner-icon"></div>
</div>`;
// 设置 bar 的 translateX 距离,刚开始是 -100
var bar = progress.querySelector("[role='bar']");
var parent = document.querySelector("body");
var perc = fromStart ? '-100' : ((NProgress.status || 0) -1)*100;
bar.style = {
transition: 'all 0 linear',
transform: 'translate3d(' + perc + '%,0,0)'
}
parent.appendChild(progress);
return progress;
}
总体流程
NProgress.done
NProgress.done = function(force) {
if (!force && !NProgress.status) return this;
return NProgress.inc(0.3 + 0.5 * Math.random()).set(1);
};
可以直接调用NProgress.set(1)的,就是为了走一下过渡效果
ts
declare namespace nProgress {
interface NProgressOptions {
minimum: number;
template: string;
easing: string;
speed: number;
trickle: boolean;
trickleSpeed: number;
showSpinner: boolean;
parent: string;
positionUsing: string;
barSelector: string;
spinnerSelector: string;
}
interface NProgress {
version: string;
settings: NProgressOptions;
status: number | null;
configure(options: Partial<NProgressOptions>): NProgress;
set(number: number): NProgress;
isStarted(): boolean;
start(): NProgress;
done(force?: boolean): NProgress;
inc(amount?: number): NProgress;
trickle(): NProgress;
/* Internal */
render(fromStart?: boolean): HTMLDivElement;
remove(): void;
isRendered(): boolean;
getPositioningCSS(): 'translate3d' | 'translate' | 'margin';
}
}
declare const nProgress: nProgress.NProgress;
🍎总结
这个NProgress 库虽然比较老,但是他的函数式编程和对Dom 的操作依旧是很值得学习的,特别是它的函数方法的编写及使用,基本做到了一个方法只做一件事,代码结构相当清晰