简介:jQuery UI progressbar 是前端开发中非常实用的进度条组件,常用于文件上传、数据处理、资源加载等需要向用户反馈任务完成状态的场景。这套资源围绕该插件整理出一份可直接上手的小型示例包:包含两个可直接在浏览器中运行的 HTML 页面,配套的 jQuery 核心脚本和 tipsy 插件脚本,进度条与进度容器的设计图片,以及一张用于交互提示的 GIF 动图,另有必读说明文档帮助快速了解文件用途。压缩包内共 9 个文件,整体大小约 34KB,体积轻巧、结构清晰,尤其适合想快速体验插件效果的前端学习者。资源中演示了进度条的初始化方法、通过 value 接口动态更新百分比数值,以及结合 tipsy 插件在鼠标悬停或进度变化时实时显示当前百分比的典型写法;读者不仅能对照示例快速复现效果,还能基于这套基础代码继续扩展自定义配色、动画过渡和事件回调,灵活嵌入到自己的网页项目中。目前已有 405 人学习下载,对于希望使用 jQuery 实现轻量进度反馈的开发者而言,是一份省时高效的实用参考。
1. jQuery progressbar进度条插件:一个老项目里最实用的小组件
第一次在模拟项目A里用 jQuery progressbar 的时候,我踩了个不大不小的坑:进度条已经跑到 100%,页面上却一个百分比数字都看不到。后来才意识到,这个插件本身只负责画一条进度条,要想把“进度到哪了”直观地标出来,得靠它自带的文本提示位。如果你手头正好有一个维护中的 jQuery 老项目,需要做上传进度、数据导入进度或者表单分步加载,那这个进度条插件可以说是一个救急的好东西。它体积小、依赖少、开箱即用,最关键的是能把进度百分比直接显示在条形上方,用户不用猜当前到底完成了多少。这篇文章我会从功能设计讲到源码实现,再给出二次开发的思路,看完你就能用在自己的项目里。
2. 功能与设计:进度条的三种形态和两套通知机制
2.1 插件的三种显示形态
这个小插件最讨喜的地方,是它并不强制你只能有一种长相。默认的进度条是纯色填充,但它预留了两种常见形态的切换空间。第一种是基础实心条,适合后台任务进度提示;第二种是条纹动画条,适合不确定耗时的操作,比如导出报表时看起来没那么死板;第三种就是带百分比文本的形态,这也是这个插件的主打卖点。形态的切换本质上就是一个样式类名的变化,不涉及额外的逻辑代码,对二次开发很友好。
实现上,插件会在目标元素内部插入两个子节点:一个负责条形背景,一个负责显示百分比文字。背景节点通过宽度百分比来控制填充比例,文字节点默认定位在进度条右侧,也可以配置成居中。这里有一个常见的设计取舍:是把文本放在进度条内部,还是放在条外挨着。插件采用的是默认覆盖在上方,但通过textPosition参数可以改成inside或outside,算是在灵活性和实现成本之间找了个平衡。
2.2 默认配置和能改的参数
任何插件拿到手里,第一件事就是看它支持哪些配置项。这个进度条插件的参数不算多,但每一条都在实际项目里用得上。核心参数包括value、max、showText、textFormat、textPosition,以及onChange和onComplete两个回调。value是当前进度值,max是最大值,默认 100,这俩决定了百分比怎么算。textFormat支持percent和ratio两种,前者显示 45%,后者显示 45/100,看个人喜好。
如果你需要调整条的颜色、高度、圆角,插件没有把样式锁死,而是留了一个barClass参数,让你传入自定义的 CSS 类名。这样做的考虑是:进度条的视觉部分本来就该交给样式去管,插件代码只负责结构、行为和状态,这样职责更清晰。我一般在项目里会直接覆盖.progressbar-bar的背景色和高度,而不是在选项里再加一个 color 参数。
2.3 事件通知机制:change 与 complete
真正让这个插件脱离“玩具”级别的,是它的两个回调事件。onChange在每一次进度值变化时触发,参数里带着当前值和百分比,适合用来联动其他 DOM 元素,比如把百分比同步到一个表格的某个字段上。onComplete则是在进度值到达max时触发一次,典型用途是隐藏 loading 遮罩、弹出完成提示,或者自动跳转。
这两个回调的设计思路是“只通知,不干预”,插件本身不会因为你传入了一个onComplete就去执行页面跳转或者弹窗,它只负责在合适的时机把控制权交给你。这种松耦合的设计在组件里非常常见,我后面拆源码的时候你会发现,触发点的位置其实就藏在setValue方法里。
3. 快速跑通:从引入文件到完成一个带百分比的上传进度示例
3.1 引入方式与静态文件结构
拿到这份源码包,解压后你能看到两个核心文件:jquery.progressbar.js和jquery.progressbar.css。前者是插件逻辑,后者是默认样式。因为它是 jQuery 插件,所以你还得先引入 jQuery,版本上我建议用 1.9 以上,但不用太新的 3.x,因为老项目里往往还跑着旧插件。静态文件顺序不能乱:先引 jQuery,再引插件,然后引样式,最后才是你的业务脚本。
如果你用的是打包工具,也可以直接把插件源码放进 vendor 目录里,按 CommonJS 或 ES Module 的方式导入。不过这个插件本身没有做模块化封装,我一般会手动给它外面包一层module.exports,这样既不影响行内<script>引入,也能在 webpack 项目里正常工作。
3.2 一行初始化,体验最简用法
默认的 HTML 结构非常简单,你只需要一个空的<div>容器,插件会在初始化时自动往里填充进度条内部的 DOM。下面的代码展示的是最基础、最不需要动脑子的用法:
<div id="uploadProgress"></div> <script src="jquery.min.js"></script> <script src="jquery.progressbar.js"></script> <script> $('#uploadProgress').progressbar({ value: 0, max: 100, showText: true, textFormat: 'percent' }); </script>这段代码里,value: 0表示初始进度为 0,max: 100表示最大值是 100,showText: true打开百分比文本显示,textFormat: 'percent'指明文本格式为百分比。初始化之后,页面上会出现一条灰色的空进度条,文字位置显示 0%。如果你把value改成 60,刷新就能看到 60% 的蓝色填充和对应的文字。
3.3 配合 AJAX 实时更新百分比
最常用的场景还是配合文件上传或者数据请求来用。这里我给出一个基于 jQuery 的$.ajax的完整示例,它通过xhr的回调拿到上传进度,然后调用插件的更新方法:
function startUpload(file) { $('#uploadProgress').progressbar({ value: 0 }); $.ajax({ url: '/api/upload', method: 'POST', data: file, processData: false, contentType: false, xhr: function() { var xhr = new XMLHttpRequest(); xhr.upload.addEventListener('progress', function(e) { if (e.lengthComputable) { var percent = Math.round(e.loaded / e.total * 100); $('#uploadProgress').progressbar('value', percent); } }); return xhr; } }); }这段代码里最关键的一行是$('#uploadProgress').progressbar('value', percent),它调用的是插件暴露出来的方法接口,而不是直接改内部属性。这么做的好处是,插件内部会自己去修正数值范围、更新宽度、刷新文字、触发回调,你不必关心这些细节。对于不熟悉 jQuery 插件规范的人来说,这里有个容易混淆的地方:初始化用的是传对象,更新数值用的是传字符串加参数,这是两种不同的调用约定。
4. 核心源码拆解:一行.progressbar()背后发生了什么
4.1 插件入口与默认参数合并
用 jQuery 写插件,百分之九十九都会走$.fn扩展这条路。这个插件的入口函数做了两件事:判断调用方式是初始化还是更新,然后对初始化的情况做参数合并。下面我把简化后的入口逻辑摆出来:
;(function($) { 'use strict'; var defaults = { value: 0, max: 100, showText: true, textFormat: 'percent', textPosition: 'outside', onChange: null, onComplete: null }; function Plugin(element, options) { this.element = element; this.options = $.extend({}, defaults, options); this.value = this.options.value; this.max = this.options.max; this.init(); } $.fn.progressbar = function(options, arg) { if (typeof options === 'string') { var method = options; return this.each(function() { var data = $.data(this, 'progressbar'); if (data && typeof data[method] === 'function') { data[method](arg); } }); } return this.each(function() { if (!$.data(this, 'progressbar')) { $.data(this, 'progressbar', new Plugin(this, options)); } }); }; })(jQuery);这里的关键点是$.data缓存实例对象,第二次调用同一个元素时,插件不会重复创建内部 DOM,而是把控制权交给已存在的实例。这种写法在 jQuery 插件里被大量使用,是避免状态混乱的标准做法。$.extend({}, defaults, options)保证了自定义参数不会污染默认配置,也不受用户传入参数顺序的影响。
4.2 DOM 结构的创建与层级关系
初始化方法init的部分代码,负责把进度条内部结构铺进容器里。结构上它是一个三层嵌套:最外层.progressbar,中间是填充条.progressbar-bar,再往外是文字层.progressbar-text。这里我把创建过程简化示例一下:
Plugin.prototype.init = function() { this.$element = $(this.element); this.$element.addClass('progressbar'); this.$bar = $('<div>', { class: 'progressbar-bar' }); this.$bar.appendTo(this.$element); this.$bar.css('width', this.value / this.max * 100 + '%'); if (this.options.showText) { this.$text = $('<div>', { class: 'progressbar-text' }); this.$text.text(this.formatText(this.value)); this.$text.appendTo(this.$element); } };在这段代码里,$('<div>', { class: '...' })是 jQuery 的快捷创建方式,第二个参数里的class会被框架转换成className。条纹动画的原理实际很简单:CSS 里给.progressbar-bar加了一个重复的渐变背景,配合animation属性让背景位置不断位移,代码层面根本不涉及任何定时器。如果你不想用默认的蓝条,直接在样式文件里改.progressbar-bar的background就能换色。
4.3 值更新与百分比计算的核心方法
进度条最核心的活都在setValue里。它要处理三件事:值越界修正、视觉宽度刷新、文本内容重算。这段代码我给你拆开了看:
Plugin.prototype.setValue = function(value) { if (typeof value !== 'number' || isNaN(value)) { return; } var oldValue = this.value; this.value = Math.max(0, Math.min(value, this.max)); var percent = this.value / this.max * 100; this.$bar.css('width', percent + '%'); if (this.options.showText) { this.$text.text(this.formatText(this.value)); } if (oldValue !== this.value && $.isFunction(this.options.onChange)) { this.options.onChange.call(this.element, this.value, percent); } if (this.value >= this.max && $.isFunction(this.options.onComplete)) { this.options.onComplete.call(this.element, this.value, percent); } };这里最容易被忽略的是Math.max(0, Math.min(value, this.max))这行。它把传入值强行夹在 0 和max之间,防止进度值出现负数或者超过最大值。oldValue !== this.value这个判断是为了避免重复触发onChange——如果你连续给进度条设置同一个值,回调不应当被反复执行,这一点在做动画循环时非常重要。
4.4 回调触发与链式调用
从setValue可以看到,onChange和onComplete的触发位置都在核心更新动作之后。这样处理的好处是,回调执行的时候,进度条的 UI 已经全部刷新完毕,你在回调里读取到的 DOM 状态是可靠的。还有一个细节是call(this.element, ...),这句把回调函数里的this指向了原始的容器元素,这样在回调里可以直接用this去改元素样式,或者用$(this)重新包装成 jQuery 对象。
至于链式调用,入口函数末尾的return this.each(...)保证了$('#a, #b').progressbar(...)可以同时作用于多个元素,并且调用后还能继续追加其他 jQuery 方法。这算是 jQuery 插件的基本素养,不过在实现时容易漏掉each遍历,一漏掉多元素场景就会失效。源码包里这一块处理得挺干净。
5. 避坑与排查:进度条不更新、百分比错乱的四个高频问题
5.1 初始化后进度条没出来
现象:$('#xxx').progressbar()执行完,页面上什么都没有,控制台也不报错。
原因:最常见的是容器元素本身的高度或者宽度为 0。进度条内部的填充条是块级元素,宽度靠百分比计算,但高度如果父元素没确定,填充条就会是 0px 高,看起来像没渲染。第二个可能原因是 jQuery 和插件的引入顺序反了,插件在 jQuery 加载之前执行,$.fn上根本挂不上方法。
解决:给容器设置一个确定的height,比如 20px,并加上display: block。检查引入顺序,确保先加载 jQuery,再加载插件脚本。排查时可以先在浏览器控制台执行typeof $.fn.progressbar,如果是function,说明插件已经挂载成功。
5.2 百分比数字一直是 0
现象:进度条宽度明显在变化,但文字始终显示 0%,或者文字没出现。
原因:这个坑往往出在showText参数上。插件默认是显示文字的,但如果你复制了一段别人的示例代码,里面开了showText: false,文字就会消失。另一种情况是textFormat传了percent,但初始value为 0,造成文字一直是 0%,看起来像没生效。
解决:优先检查初始化参数,确认showText为true。如果确认配置无误,再打开浏览器控制台查看.progressbar-text是否存在。如果不存在,说明初始化走了缓存分支,你可以先执行$('#xxx').data('progressbar', null)强行重置,再重新初始化。
5.3 宽度能变但文本不刷新
现象:调用setValue之后,条变宽了,文字还停在旧数值上。
原因:这不是插件逻辑问题,多半是你绕过了插件接口,直接操作内部 DOM,比如用$('.progressbar-bar').css('width', ...)。插件内部记录的值this.value没有更新,下次再调用插件方法时,它会拿旧的内部值算百分比,把你的宽度刷新覆盖回去。
解决:更新进度这样的事,永远走$('#xxx').progressbar('value', newValue)这样的公开接口,不要直接动内部 DOM。如果确实需要手动修改样式,改完再调用一次setValue把内部状态和视觉统一回来。
5.4 快速更新时回调重复触发
现象:在setInterval或者上传事件里高频调用progressbar('value', ...),onChange被频繁触发,甚至onComplete也执行了好几次,导致页面弹出多个完成提示。
原因:高频更新时,如果传入的数值之间差别很小,但又不完全相等,插件每次都会触发onChange,这是符合设计的。onComplete只在值第一次达到max时触发,可如果你在回调里把值重新设成了小于max的数,下一次更新又会触发一次。
解决:在业务代码里做节流,比如每 100 毫秒只更新一次。对于onComplete重复触发,可以在回调函数里加一个标志位,执行过一次后置为true,后续判断不再处理。这是我在类似插件里惯用的做法,简单可靠。
6. 二次开发:把插件从 jQuery 迁移到原生 JS,并增加模拟进度功能
6.1 保留核心接口,移掉 jQuery 依赖
如果哪天你想把这个进度条插件用到新团队里,但新项目已经不用 jQuery 了,完全可以把它改造成原生组件。我一般保留setValue、setMax和onComplete这几个接口,把$.extend换成Object.assign,把$.data换成 WeakMap 或者直接挂在元素属性上。改造后的初始化方法长这样:
function createProgressbar(container, options) { var settings = Object.assign({ value: 0, max: 100 }, options); var instance = { value: settings.value, max: settings.max, setValue: setValue }; return instance; }迁移的关键在于把 jQuery 链式调用拆解成普通函数调用。你不需要一比一复刻所有参数,保留核心业务需要的部分就够了。迁移完成后用单元测试把setValue的边界情况跑一遍,尤其是“值超过 max 被夹紧”和“回调只触发一次”这两个行为,必须和原插件一致。
6.2 给组件添加一个“模拟进度”方法
进度条最常见的增值功能是模拟一个不确定耗时的任务进度,比如数据清洗时你不知道要跑多久,但又不希望用户盯着 0% 看。我习惯在插件上增加一个autoRun(interval, step)方法,内部用setInterval自增,到达 100% 时自动清除定时器。实现思路很直接:
Plugin.prototype.autoRun = function(interval, step) { var self = this; this.timer = setInterval(function() { var next = self.value + step; if (next >= self.max) { next = self.max; clearInterval(self.timer); } self.setValue(next); }, interval); return this; };这样一个方法加进去,插件就从手工设定进度的工具,升级成了能独立模拟任务的组件。要注意的是定时器必须保存在实例上,这样你在页面跳转离开时才能clearInterval清理,防止内存泄漏。
6.3 改造时的验证路径和我的习惯
每次改完这种第三方插件,我都会搭一个最精简的 HTML 页面,把初始化、更新、回调三个路径手动点一遍。我会写一个测试按钮分别绑定+10、-10、setTimeout模拟完成这三种操作,打开浏览器控制台观察数值和回调输出。只有这条链路走顺了,我才会把插件塞回正式项目里。从那以后,我每次接二手插件都会先建一个裸 HTML 文件做好隔离测试,再谈业务集成,这个习惯帮我少踩了很多隐藏的坑。希望这篇文章的思路和踩坑记录能帮到你。
本文还有配套的精品资源,点击获取