简介:这是一份 jQuery progressbar 进度条插件的配套演示源码包,面向前端开发者和 jQuery UI 学习者,帮助解决在页面中快速创建、动态更新进度条并同步展示百分比数据的问题。资源包含完整的 demo 页面与运行所需脚本,演示了从初始化进度条、调用 value 方法到结合异步上传事件实时刷新百分比的具体写法,并配有动画开关与自定义样式参考。压缩包共9个文件,包括2个html演示页、2个js脚本、1个txt说明文档及若干图片/gif效果示意,包体仅34KB,适合直接下载后在浏览器中运行查看。目前已有404人学习下载。通过阅读配套页面与脚本注释,可直观理解 progressbar 插件的事件参数、更新流程和样式定制思路,也可将其中代码迁移到自己的上传、加载或数据处理等项目中,快速增强界面反馈体验。
1. jQuery progressbar 进度条插件:为什么默认不显示百分比数据
老后台项目里最常见的交互改造,就是给导入、导出、批量评分页面加一根能显示百分比数据的进度条。很多开发者第一反应是装一个 jQuery 进度条插件,装上后却发现:条能走,字没有。jQuery 生态里使用频率最高的进度条组件,也就是 jQuery UI 的 progressbar,默认只渲染一块会变宽的色块,并不自带“23%”这样的提示数据。组件这样设计是为了保持纯净,可真实业务里没人愿意盯着一根看不出剩余量的条干等。这篇文章把进度条百分比怎么算、怎么把百分比文字稳定地提示出来,以及那些最容易翻车的边界问题一次性讲清楚。适合正在维护 jQuery 后台系统,又不想为进度条单独引入前端框架的开发者。
2. 先搞懂 progressbar 的 value 模型:百分比提示数据从哪来
进度条插件说到底只做一件事:把一个数值映射成一段宽度。要把“百分比数据”提示出来,前提是先理解这个数值模型,否则你会写出很多“看着对但一动就错”的更新逻辑。
2.1 progressbar 的 DOM 结构与样式挂钩
用 jQuery UI progressbar 初始化一个进度条后,渲染出的 DOM 结构是两层嵌套:
<div id="pbar" class="ui-progressbar ui-widget ui-widget-content"> <div class="ui-progressbar-value ui-widget-header"> <div class="ui-progressbar-overlay"></div> </div> </div>外层.ui-progressbar是轨槽,内层.ui-progressbar-value是彩色填充条。组件内部把value除以max得到一个比例,再用这个比例去设置填充条的宽度。所以百分比的数学基础非常直接:百分比 = 当前值 / 最大值 * 100。默认情况下max是 100,min是 0,于是 value 直接就是百分比数值,但这恰恰是很多人在后续把 max 改掉之后翻车的原因。
样式挂钩这点容易被忽略:.ui-progressbar-value的宽度是组件动态计算的,如果你把百分比文字节点塞进这个填充条内部,当 value 只有 3% 时文字会被挤压到看不见,当 value 到 100% 时又可能溢出圆角区域。常见的做法是把百分比文字放在轨槽内做绝对定位,或者直接放在进度条旁边作为一个独立节点,让它的位置只依赖组件事件里的数据,不依赖 CSS 宽度推算。
2.2 百分比提示的两类做法:内嵌标签与浮动气泡
我一般会把百分比提示分为两种形态,选哪种取决于页面里还有没有别的信息要展示。
内嵌标签是把<span>固定在进度条右上角或右侧,文本始终可见,不随进度移动。适合表格行内的细进度条,例如批量导入任务列表里每一行的状态。浮动气泡是把文本做成跟随填充条右边缘移动的浮层,视觉上更有“实时感”,适合单条大进度条,比如上传大文件时的中央面板。
| 做法 | 结构位置 | 适合场景 | 注意点 |
|---|---|---|---|
| 内嵌标签 | 独立<span>,置于进度条内部或右侧 | 列表行内、多实例并行 | 注意父容器宽度,避免换行 |
| 浮动气泡 | <span>绝对定位,left 跟随百分比 | 单条大进度条、上传面板 | 气泡宽约 30px,100% 时别超出轨槽 |
两类做法的数据源完全一样,都是进度条 change 回调里的ui.value。区别只在于你把这个值写到哪个节点、用 CSS 怎么摆。接下来要解决的核心问题是:什么时候更新文字。
2.3 事件与刷新时机:change 回调才是百分比数据的源头
progressbar 每次值变化都会触发progressbarchange事件,同时执行你在初始化时传入的change回调。回调里能拿到两个东西:事件对象和ui.value。这里有一个最容易踩的认知偏差:很多人直接在按钮点击或 AJAX 进度事件里手动改文字,绕过了 progressbar 的 change 回调,结果进度条宽度和数据对不上。
正确的数据流是单向的:业务代码只负责调用progressbar("value", 新值),组件内部完成宽度更新并触发 change 回调,回调里再同步百分比文字。文字的更新点永远只有一个,否则多路更新早晚出现某一帧数字跳变。
还有一点需要留意:初始化时如果给了一个非默认 value,某些情况下 change 回调不会自动触发。我习惯在初始化参数里同时声明create回调,在create里手动执行一次百分比同步函数,确保第一次渲染就是对的。关于这个坑,第 5 章会专门展开一次完整排查。
3. 把“进度百分比数据”提示出来:初始化、更新与完整示例
原理清楚之后,落地就快得多。这一章直接给出最小初始化、三种常见更新写法,以及一个能直接抄进项目里改的完整示例。
3.1 最小初始化与百分比文字输出
先写一个最简版本:进度条旁边放一个span,进度每变一次,span 里的文本跟着变。
<div id="uploadPbar" style="width: 400px;"></div> <span id="uploadPct">0%</span>$("#uploadPbar").progressbar({ value: 0, max: 100, change: function (event, ui) { var pct = Math.round(ui.value / 100 * 100); $("#uploadPct").text(pct + "%"); } });代码逻辑很直白:初始化时声明value和max,组件内部用ui.value / max算出比例。Math.round是必须的,因为真实业务里的 progress 事件给的值经常带小数,比如上传进度到了 73.6%,直接拼字符串会出现“73.6%”这种干扰视觉的文本。ui.value是组件事件里直接给出的本次值,不需要再去调用progressbar("value")读取,减少一次方法调用也就少一处出错的可能。
参数说明:max不传时默认是 100,所以这段代码里即使不写max结果也一样,但显式写出来能让后面接手的人一眼明白换算逻辑。change回调在每次调用progressbar("value", v)后执行,不会在初始化 value=0 时执行,这也是为什么后面要补 create 同步。
3.2 动态更新:两种 set value 的正确写法
业务里更新进度条主要有两种写法,结果等价但容易混淆。
// 写法一:组件方法 $("#uploadPbar").progressbar("value", 55); // 写法二:option 方式 $("#uploadPbar").progressbar("option", "value", 55);两种写法最终都会走到同一个内部更新逻辑,都会触发 change 回调。写法一更语义化,写法二在处理多个 option 时更方便,例如同时改 value 和 max。要注意的是别写成$("#uploadPbar").progressbar({ value: 55 })这种重复初始化形式,它会重建整个组件,旧的事件绑定和状态会被重置,等于把进度条从零再来一遍。
实际项目中我固定用写法一,因为团队里其他人一看方法名就知道在操作进度条。如果你要同时更新 max,再额外用一次option("max", ...),不要在一个初始化对象里混着做。
3.3 完整示例:一个导入进度面板
下面这段是一个手工模拟进度的完整面板,适合先在本机把交互逻辑跑通,再把内部数值替换成真实 AJAX 进度。
<div class="import-panel"> <div id="pbar" style="width: 100%;"></div> <div id="pbarTip" class="pct-label">0%</div> <button id="btnAdd">步进 10%</button> <button id="btnReset">重置</button> </div>(function () { var $bar = $("#pbar"); var $tip = $("#pbarTip"); var current = 0; function syncPct(val, max) { var pct = Math.round(val / max * 100); pct = Math.min(pct, 100); $tip.text(pct + "%"); } $bar.progressbar({ value: 0, max: 100, create: function () { syncPct(0, 100); }, change: function (event, ui) { var max = $bar.progressbar("option", "max"); syncPct(ui.value, max); } }); $("#btnAdd").on("click", function () { current = Math.min(current + 10, 100); $bar.progressbar("value", current); }); $("#btnReset").on("click", function () { current = 0; $bar.progressbar("value", 0); }); })();这里一个关键动作是syncPct被 create 和 change 两处调用。create只在初始化时执行一次,负责把首屏文本“0%”写出来;change负责后续所有更新。Math.min(pct, 100)是最后一道保险,防止浮点误差或越界数据让文本出现“101%”。
参数说明:按钮里的递增上限 100 和max保持一致,如果以后把 max 改成 200,按钮逻辑里的 100 也要跟着改,否则进度条走到一半就到顶了。更稳妥的做法是每次从$bar.progressbar("option", "max")读取最大值,而不是在业务代码里写死。
4. 参数与边界:max 不为 100、未知总量与 AJAX 进度接入
默认参数能跑通,但真实项目没有多少“刚好 100 个任务”的场景。这一章讲清楚三个高频边界:改过 max 后怎么算百分比、进度总量未知怎么办、怎么把真实上传进度接进来。
4.1 max/min 参数与百分比换算公式
progressbar 的完整数值范围由min和max共同决定,默认min=0、max=100。一旦 max 改成 200,直接用value当百分比就完全错了。
function pctFromBar($bar) { var value = $bar.progressbar("value"); var min = $bar.progressbar("option", "min"); var max = $bar.progressbar("option", "max"); return Math.round((value - min) / (max - min) * 100); }| 参数 | 默认值 | 作用 | 需要注意 |
|---|---|---|---|
| value | false | 当前进度数值 | false 表示不确定状态 |
| min | 0 | 数值下限 | 通常不用改 |
| max | 100 | 数值上限 | 改为任务总数十,百分比要重算 |
| disabled | false | 禁用组件 | 禁用后外观变灰 |
这个公式把三种常见情况都覆盖了:max=100 时它就是直接百分比,max=200 时能正确换算,min≠0 时也不会把基准值算错。组件内部对 value 会自动钳制在[min, max]区间,所以你在业务里传入超范围值它不会报错,但也不会显示超过 100% 的条。
4.2 indeterminate:总量未知时怎么提示
传文件时经常出现一种情况:服务端不返回总大小,ev.total是 0,这时候任何百分比计算都是除以 0。progressbar 专门留了一个不确定状态。
// 进入不确定状态 $("#pbar").progressbar("option", "value", false);设置 value 为false后,组件会渲染一个持续滑动的条纹效果,表示“在动,但不知道还剩多少”。从不确定状态切回确定状态也很简单:再调用一次progressbar("value", 40)即可。这里有个隐藏行为:设置为 false 时 change 回调的ui.value会是 false,所以百分比同步函数里要做一次类型判断,避免false / max算出 0。
我自己的习惯是:在开始请求前先把条设为 false 状态,拿到第一个lengthComputable=true的 progress 事件后再切成百分比模式。这样用户不会看到一根停在 0% 的条误以为卡死。
4.3 多个进度条实例的隔离与批量更新
后台列表页经常同时存在十几条导入任务,每行一个进度条。这时最容易犯的错误是用一个全局变量存进度,结果所有行的百分比一起变。
var bars = { task1: { el: "#pbar1", current: 0 }, task2: { el: "#pbar2", current: 0 } }; function initTaskBar(key) { var info = bars[key]; $(info.el).progressbar({ value: 0, max: 100, change: function (event, ui) { $(info.el).next(".row-pct").text(Math.round(ui.value) + "%"); } }); } function updateTaskBar(key, val) { var info = bars[key]; info.current = val; $(info.el).progressbar("value", val); }每条任务独立存一个对象,更新时通过 key 找到对应 DOM 节点。$(info.el).next(".row-pct")依赖 DOM 结构中百分比 span 紧跟在进度条后面,这种结构写起来简洁,但如果后期调整 DOM 位置要同步改选择器。更稳的是在进度条的外层容器上用>$.ajax({ url: "/api/import", method: "POST", data: formData, processData: false, contentType: false, xhr: function () { var xhr = $.ajaxSettings.xhr(); if (xhr.upload) { xhr.upload.addEventListener("progress", function (ev) { if (ev.lengthComputable) { var pct = Math.round(ev.loaded / ev.total * 100); $("#pbar").progressbar("value", pct); } }, false); } return xhr; }, success: function () { $("#pbar").progressbar("value", 100); } });
进度事件必须挂在xhr.upload上,这表示上传方向的数据传输进度。如果挂在xhr.onprogress上,对应的是下载响应内容的进度,在上传场景里几乎不触发。ev.lengthComputable判断很关键,有些请求的总长度不可计算,这时 loaded/total 会得到 NaN,直接赋值会让进度条显示异常。
success 里把值补到 100 是兜底动作,因为最后一个 progress 事件不保证刚好到 100%,加上网络层可能有压缩或编码差异,最终差几个百分点是常态。
5. progressbar 常见问题与排查:五个能让你浪费半天的坑
进度条代码量不大,出问题时往往不在插件本身,而在调用方式和 DOM 结构。这章把实际项目里最容易踩的五类问题按现象、原因、解决拆开。
5.1 百分比永远停在 0% 的罪魁祸首
现象:进度条颜色块在变宽,旁边的百分比文字纹丝不动,一直显示 0%。
原因:最常见的是用了$("#pbar").val()这类普通表单方法读值,progressbar 的 value 是组件内部状态,不是 jQuery 的 val;另一个常见原因是 change 回调里用的是闭包捕获的旧变量,页面里翻页或组件重建后变量已经失效。还有一种隐蔽情况是初始化 value=0 时 change 不触发,而你又没有在 create 里同步首屏文本。
解决:统一在change回调里读ui.value,不接受任何外部变量;初始化时用create手动同步一次百分比文本。如果是动态渲染的列表页,页面每次重新渲染后都要重新执行初始化,不要只执行一次然后指望回调长期有效。
5.2 AJAX 进度条瞬间满与回退闪动
现象:上传一开始进度条不动,最后突然跳到 100%;或者进度走了一半又回退到 0% 再继续走。
原因:瞬间满多半是进度事件挂错了对象,监听的是xhr.onprogress而不是xhr.upload.onprogress,导致进度事件攒到最后一次性触发。回退闪动则是因为同时存在两个更新源,比如 AJAX 进度回调里 set 一次值,定时轮询接口又 set 一次较小值,两者交替覆盖。
解决:只用xhr.upload上的 progress 事件,去掉定时轮询。如果确实需要轮询兜底,就让轮询只负责超时判断,不参与进度值更新;多个数据源要做“只涨不跌”的限制,即新值小于当前值时直接丢弃。
5.3 文字被填充条裁切与样式错位
现象:百分比文字显示不全,字被进度条色块盖住或者挤到下一行;进度到 50% 时文字位置突然跳动。
原因:把百分比 span 放进了.ui-progressbar-value内部。填充条宽度随 value 动态变化,宽度小的时候文字放不下,宽度大的时候又和着色块一起变形。另一个常见原因是没有给外层轨槽设position: relative,绝对定位的文本相对页面定位,行为完全不可预期。
解决:百分比节点永远放在.ui-progressbar-value外面。内嵌标签就放在轨槽内,用绝对定位固定在右上角;浮动气泡则放进步度条的外层容器,定位相对那个容器计算。写 CSS 之前先确认渲染出来的 DOM 层级,不要凭记忆猜。
5.4 动态创建进度条后事件失效
现象:页面初始化时创建的进度条一切正常,通过 AJAX 返回 HTML 片段后新插入的进度条不显示百分比,点击更新也无反应。
原因:事件绑定发生在动态节点存在之前。代码里写的是$(".pbar").progressbar(...),这个选择器在新节点插入后不会自动对新节点生效,除非重新执行初始化。常见场景是对话框里的进度条,对话框内容由模板字符串拼出来,初始化代码写在页面加载时。
解决:把初始化动作放回动态节点插入 DOM 之后执行。比如在对话框dialog("open")回调里初始化进度条,或者在 AJAX success 渲染完列表后,对列表内每一个.pbar重新执行.progressbar(...)。另外需要注意不能对同一个节点重复初始化,重复初始化会重建组件,导致之前初始化的状态被清空。
5.5 百分比数据来源混乱与计算偏差
现象:进度条到达 100% 后,数字偶尔显示 99% 或 101%;批量导入场景里某几行百分比始终少 1%。
原因:value / max * 100的结果是浮点数,真实进度事件每次给的值略有差异,最后几帧可能停在 99.6%。同时组件内部对 value 的钳制是逐次执行的,如果一次事件里传入的 value 超过 max,它会被钳到 max,但你的文本更新如果用了未钳制的原始值,就可能看到 101%。
解决:文本计算统一用Math.round,再套一层Math.min封顶。批量场景里不要把百分比算好再传,直接把原始任务数和已完成数传给更新函数,让同一个函数内部完成除法、取整、钳制三步,不同行之间就不会出现各自微小的计算差异。
6. 进阶技巧:用 MutationObserver 监听 aria-valuenow 自动同步百分比
做到这一步,进度条百分比已经能稳定显示。最后一个技巧解决的是维护成本问题:当项目里有十几个进度条,每个都要手动在 change 回调里同步文字,加一个新入口就写一遍重复代码,很容易漏。
progressbar 组件内部更新 value 时会同步写aria-valuenow和aria-valuemax属性。利用这个特性,可以做一个通用的自动同步函数,监听这两个属性的变化,任何代码路径里改值都会触发文本刷新。
function bindPctAuto(barSel, tipSel) { var $bar = $(barSel); var $tip = $(tipSel); var rafId = null; function render() { var value = Number($bar.attr("aria-valuenow")) || 0; var max = Number($bar.attr("aria-valuemax")) || 100; var pct = Math.round(value / max * 100); pct = Math.min(pct, 100); $tip.text(pct + "%"); rafId = null; } var observer = new MutationObserver(function () { if (rafId) { return; } rafId = requestAnimationFrame(render); }); observer.observe($bar[0], { attributes: true, attributeFilter: ["aria-valuenow", "aria-valuemax"] }); render(); } // 页面里任意一个 progressbar,初始化后调用一次绑定 $("#taskPbar").progressbar({ value: 0, max: 100 }); bindPctAuto("#taskPbar", "#taskPct");逻辑说明:MutationObserver 捕获属性变化,requestAnimationFrame 把同一帧内的多次变化合并成一次渲染,避免连续调用 set value 时文字闪烁。render从 aria 属性里读值,所以无论业务代码用progressbar("value", v)还是option("value", v)更新,文字都会跟得上。
参数说明:attributeFilter 只监听两个属性,缩小监听范围减少不必要回调。requestAnimationFrame在 IE9 及以下不存在,旧项目里建议回退到 change 回调方案,或者用setTimeout做节流。这个绑定方式有个额外优点:即使未来别人在新代码里用别的方式修改了 progressbar 的值,只要组件内部同步了 aria 属性,百分比文本就不会掉线。
想起在某个后台系统里,早期每个进度条都单独写一遍文本更新,后来功能多了几个入口,总有那么一两个页面百分比不动。换成 aria 监听后,新增页面只需要一行bindPctAuto,再没出现漏更新。技术不一定要多新,把这个老插件的既有行为利用到位,维护成本能降下一大截。希望帮到你。
本文还有配套的精品资源,点击获取