Layui upload 组件原生不提供剩余时间预测能力,因其 progress 回调仅暴露百分比 n,未透出 loaded、total、timeStamp 等底层参数,无法准确计算实时速率与剩余时间;必须改用原生 XMLHttpRequest 手动接管上传流程,并通过滑动窗口法结合防错逻辑(如 lengthComputable 判断、速率阈值、多文件独立计算等)实现可靠预估。
layui upload 组件原生不提供「剩余时间」预测能力,progress 回调只给百分比 n,没有已上传字节数、总字节数、时间戳等原始数据,无法直接算出剩余秒数。必须手动采集并计算。
为什么 progress 回调里拿不到上传速度和剩余时间
Layui 的 progress 是封装后的高层回调,只暴露 n(0–100 的整数),隐藏了底层 XMLHttpRequest.upload.onprogress 的原始事件参数(event.loaded、event.total、event.timeStamp)。这意味着你无法在 progress 里直接拿到瞬时速率或预估剩余时间。
- 想用
n做线性估算(比如“已传 30%,花了 2 秒,预计还要 4.7 秒”)——不可靠,上传速度非匀速,尤其网络抖动、服务端处理延迟时误差极大 - 没 access 到
loaded/total,就无法算真实速率,所有“剩余时间”都是拍脑袋 - 别试图在
progress里用Date.now()算两次调用间隔再除 ——progress触发频率不固定(浏览器节流),且首次触发时n可能直接跳到 5%+,起始点不准
正确做法:改用原生 XMLHttpRequest 手动接管上传
放弃 upload.render 的自动模式,用 auto: false + choose 拿到 File 对象,再手写 XMLHttpRequest 并监听 upload.onprogress。这样你才能拿到 event.loaded 和 event.total。
- 必须设
auto: false,否则文件立刻发走,choose后没机会插手 - 在
choose回调里拿到obj,调用obj.files获取File[]数组(注意不是obj.file,那是单文件旧写法) - 对每个
file实例化XMLHttpRequest,手动构造FormData,append 文件和其他字段 - 绑定
xhr.upload.onprogress = function(e) { if (e.lengthComputable) { ... } },此时e.loaded和e.total可用 - 用滑动窗口法算速率:存最近 3 次的
{time, loaded},线性拟合斜率,避免单次抖动干扰
剩余时间计算逻辑与防错要点
公式是:remainingSec = (total - loaded) / (loaded / elapsedMs) * 1000,但必须加保护逻辑,否则除零、负值、NaN 会崩 UI。
- 只在
e.lengthComputable === true时计算,否则e.total是 0,导致除零 - 要求至少两次有效进度事件(
loaded > 0且timeGap > 200ms),否则不启动估算 - 速率低于 1KB/s 时禁用剩余时间显示,改为“上传中…”——太慢时预测毫无意义
- 当
n === 100后还没进done,说明卡在服务端处理,此时剩余时间应清空或显示“正在保存…” - 多文件场景下,每个文件独立算,不要共用同一个计时器或速率缓存
UI 更新别用 setInterval,用 requestAnimationFrame
剩余时间文案每秒更新一次就够了,但用 setInterval(fn, 1000) 容易和 progress 事件不同步,造成跳变。更稳的方式是:
- 在
onprogress里记录最新loaded、total、timestamp - 用
requestAnimationFrame驱动 UI 更新,它会自然对齐屏幕刷新节奏 - 每次帧内检查距上次更新是否 ≥ 1000ms,满足才重算并更新 DOM(如
document.getElementById('remain-' + id).textContent = text) - IE11 下用
textContent替代innerText,避免空白
真正难的不是算时间,而是让「发了多少」「当前速率」「服务端是否卡住」三者状态在 UI 上不打架。比如进度条走到 100% 但后端还在校验,这时显示“剩余 0s”反而误导用户——得切到中间态文案,而不是硬套公式。


















