导读:本期聚焦于唐振业创作的《如何在file://本地页面中用jQuery的$.ajaxTransport模拟上传进度?》,敬请观看详情。直接打开本地HTML文件做上传功能时,进度条常常纹丝不动,甚至直接报错。这是因为file://协议下浏览器会阻止真实的HTTP上传请求,XMLHttpRequest的upload.onprogress根本不会触发。本文给出一个可落地的模拟方案:利用jQuery的$.ajaxTransport扩展点拦截请求,不经过网络层,而是在本地按文件大小分块推进加载字节数,并逐次回调progress函数,最终让进度条平滑走到百分之百。文章会先解释file://环境下的网络限制和进度事件缺失原因,然后说明$.ajaxTransport在jQuery请求管道中的位置,最后给出一段完整的HTML示例代码,包含文件选择、FormData构造、自定义dataType、定时器模拟上传以及abort清理逻辑。代码可直接放在本地双击运行,适合离线工具、Electron原型、演示预览等场景。需要注意的是,模拟进度只适合本地无真实服务端的阶段,生产环境仍应替换为真实的HTTP上传并获得原生进度事件。

直接通过 file:// 协议打开包含上传功能的 HTML 页面时,很多逻辑会变得不可靠:点选文件后,进度条要么停在 0%,要么直接触发 error 回调。核心原因并不在 jQuery,而在于浏览器对本地文件页面的网络请求以及进度事件做了严格限制。标准上传流程中,XMLHttpRequest 的 upload.onprogress 会在请求体逐步发送到服务器时持续触发,但在 file:// 页面里,这个发送动作往往不会真实发生,所以进度事件也不会产生。一个可行的思路是借助 jQuery 的 $.ajaxTransport 扩展机制,拦截这类上传请求,在本地用定时器或分片读取来模拟数据消费过程,并把模拟出的 loaded、total、percent 回传给进度回调。

如何在file://本地页面中用jQuery的$.ajaxTransport模拟上传进度?

为什么 file:// 下拿不到上传进度

真实上传的进度由浏览器网络栈在发送请求体时产生。file:// 页面本身不是通过 HTTP 服务器加载的,浏览器的同源策略和安全沙箱会阻止像 XMLHttpRequest 这样的对象向 http:// 或 https:// 地址发送带本地文件内容的请求;即便某些浏览器允许发送,也常常处于受限模式,upload.onprogress 不被触发。你可以把文件选择、本地读取、网络上传理解为三个独立阶段,<input type="file"> 只负责拿到 File 引用,FileReader 只负责把内容读进内存,而真正的上传进度只发生在网络阶段。file:// 下网络阶段缺失,进度自然没有来源。

还有一个容易混淆的地方:FileReader 的 onprogress 虽然能反映读取本地文件的进度,但它更新的是“读取多少字节到内存”,而不是“上传了多少字节”。如果直接把 FileReader 的进度当成上传进度展示,会出现进度条很快走完,但后续提交尚未开始的错位感。用 jQuery 的 $.ajax 直接传 FormData 在 file:// 页面中也极有可能直接失败,因为底层 xhr 无法完成发送。要解决这个问题,就需要在 jQuery 的 AJAX 管道中插入自定义的传输层,把请求“截下来”,按自己的节奏推进度。

$.ajaxTransport 在请求管道中的角色

jQuery 的 $.ajax 并不是直接调用 XMLHttpRequest,而是先经过 prefilter、converter、transport 等环节。transport 是最终负责把请求发出去并接收响应的组件,默认情况下 jQuery 会根据 url 协议选择 xhr 或 script 等传输器。我们可以通过 $.ajaxTransport('+mock-upload', function(options, originalOptions, jqXHR) {...}) 注册一个新的传输器。参数中 '+mock-upload' 的加号表示只要请求的 dataType 包含 mock-upload 字符串,就轮到这个传输器处理。

当 jQuery 匹配到一个自定义传输器后,会调用它返回对象的 send 方法,把请求头和完成回调传进去;如果用户调用 abort(),则会触发返回对象里的 abort 方法。因此我们在 send 里完全不用发网络请求,而是启动一个模拟循环;在循环中根据文件大小推进 loaded,并把计算好的事件对象回传给 originalOptions.progress;最后调用 completeCallback 结束这次请求。这样外层 $.ajax 的 done、fail、progress 等回调可以继续工作,业务代码几乎不需要改动。

下面先给出一个最小的 transport 结构,帮助理解它如何接管请求:

$.ajaxTransport('+mock-upload', function(options, originalOptions, jqXHR) {
  return {
    send: function(headers, completeCallback) {
      // 实际不做网络请求,直接告诉 jQuery 请求成功
      completeCallback(200, 'success', { text: 'ok' });
    },
    abort: function() {
      // 请求被取消时调用
    }
  };
});

完整实现:在 file:// 页面中模拟本地文件上传进度

首先在页面中放置一个文件选择控件、一个按钮和一个进度条,结构大致如下:<input type="file" id="fileInput">、<button id="uploadBtn">上传</button> 以及一个用于展示进度的 <div>。接着在 JavaScript 里监听文件变化,把选中的 File 对象保存到变量中。点击上传按钮后构造一个 FormData,将文件 append 进去,并调用 $.ajax,同时把 dataType 设置为 mock-upload。

自定义 transport 的核心任务是从 FormData 中提取 File 对象,并拿到文件总大小。由于 FormData 没有公开的 get 方法,遍历 entries 迭代器是兼容性较好的做法。取到 file 后,用 file.size 作为 total 值,然后启动定时器,每次增加一个 chunk。chunk 可以取总大小的六十分之一左右,这样进度条大约三秒钟走完,观感比较自然。每次更新后用 originalOptions.progress 把自定义事件对象传出去,事件对象中应包含 loaded、total、lengthComputable 和 percent 四个关键字段。

下面是完整的 transport 注册代码:

$.ajaxTransport('+mock-upload', function(options, originalOptions, jqXHR) {
  var timer = null;
  return {
    send: function(headers, completeCallback) {
      var file = null;
      if (originalOptions.data instanceof FormData) {
        var entries = originalOptions.data.entries();
        var entry = entries.next();
        while (!entry.done) {
          if (entry.value[1] instanceof File) {
            file = entry.value[1];
            break;
          }
          entry = entries.next();
        }
      }
      if (!file) {
        completeCallback(400, 'error', { text: '未找到文件对象' });
        return;
      }

      var total = file.size;
      var loaded = 0;
      var chunk = Math.max(Math.ceil(total / 60), 1024);
      timer = setInterval(function() {
        loaded += chunk;
        if (loaded > total) {
          loaded = total;
        }
        var event = {
          loaded: loaded,
          total: total,
          lengthComputable: true,
          percent: total ? (loaded / total * 100) : 100
        };
        if (originalOptions.progress && typeof originalOptions.progress === 'function') {
          originalOptions.progress(event);
        }
        if (loaded >= total) {
          clearInterval(timer);
          timer = null;
          completeCallback(200, 'success', { text: '模拟上传完成' });
        }
      }, 50);
    },
    abort: function() {
      if (timer) {
        clearInterval(timer);
        timer = null;
      }
    }
  };
});

外层调用代码同样简单。选择文件后,点击按钮发起 ajax 请求,在 progress 回调里用返回的 percent 更新进度条样式,在 done 里显示完成状态。这样整个流程就从“真实发送请求”切换成了“本地模拟发送”,但业务侧感知不到差异。

$('#uploadBtn').on('click', function() {
  var fileInput = $('#fileInput')[0];
  if (!fileInput.files || !fileInput.files[0]) {
    alert('请先选择文件');
    return;
  }
  var fd = new FormData();
  fd.append('file', fileInput.files[0]);

  $.ajax({
    url: 'mock://local-upload',
    type: 'POST',
    data: fd,
    dataType: 'mock-upload',
    progress: function(e) {
      var percent = 0;
      if (e.total) {
        percent = Math.round(e.loaded / e.total * 100);
      }
      $('#progressFill').css('width', percent + '%');
      $('#progressText').text(percent + '%');
    }
  }).done(function(res) {
    $('#status').text('上传完成:' + res.text);
  }).fail(function(xhr, status, error) {
    $('#status').text('上传失败:' + error);
  });
});

注意事项与适用边界

模拟进度只适用于没有真实服务端的本地原型、离线演示或 Electron 早期阶段。生产环境务必替换为 HTTP 上传,因为真实上传进度能反映网络波动、服务端反压等实际状态。模拟进度虽然能带来完整交互体验,但它会掩盖一些潜在问题,例如后端接收失败、超时、跨域限制等,因此不要把模拟逻辑留在正式代码中。

对于大文件场景,定时器模拟并不实际读取文件内容,所以不会造成内存压力,进度速度也是固定的。如果希望进度更贴近真实文件的读取节奏,可以改用 FileReader 的分片读取方式,每次读取一个 slice,在读取完成后手动推进 loaded。不过 FileReader 是异步的,需要写成递归或链式调用,并且在 file:// 环境下读文件可能仍受到安全策略限制。

此外还需要注意几点:自定义 dataType 不要与真实后端的 dataType 冲突;abort 时必须清理定时器,避免定时器继续运行并更新已经取消的请求;多个文件可以分别模拟,也可以按文件总大小合并计算;file:// 页面在 Chrome、Edge、Firefox 中的行为略有差异,建议在本地启动 HTTP 服务做更稳定的测试,例如使用 python -m http.server 或 npx serve 启动静态服务。

jQuery ajaxTransport本地文件上传上传进度模拟修改时间:2026-09-18 19:07:24

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0918/58927.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。