news 2026/4/23 6:41:26

前端文件【上传下载】姿势大全

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端文件【上传下载】姿势大全

本文介绍浏览器与服务器之间文件传输的常见方式,涵盖文件的获取、上传、下载全流程,并附带代码示例。
1 浏览器获取用户本地文件
在浏览器中根据不同场景,有多种获取文件的方式。
1.1 点击上传
通过点击文件表单实现上传,最基础、兼容性最好的方式。

<input type="file" id="file" /> <script> const file = document.getElementById('file') file.addEventListener('change', (e) => { const file = e.target.files[0] console.log('🚀 ~ file -->', file) }) </script>
<input type="file" id="dir" webkitdirectory multiple /> <script> const input = document.getElementById('dir'); input.addEventListener('change', (e) => { const files = e.target.files; console.log('🚀 ~ files -->', files) }); </script>

1.2 拖拽上传
直接拖动文件到页面,交互体验好,符合直觉,适合批量文件。

<div id="drop" style="width:300px;height:200px;border:2px dashed #999;"> Drop files here </div> <script> const drop = document.getElementById('drop'); drop.addEventListener('dragover', (e) => { e.preventDefault(); }); drop.addEventListener('drop', (e) => { e.preventDefault(); const files = e.dataTransfer.files; console.log('🚀 ~ files -->', files) }); </script>

1.3 粘贴上传
将剪切板文件粘贴到页面中,交互体验好,效率高,特别适合上传临时截图。

<p>在此页面 Ctrl / Cmd + V 粘贴文件</p> <script> document.addEventListener('paste', (e) => { const items = e.clipboardData.items; for (const item of items) { if (item.kind === 'file') { const file = item.getAsFile(); console.log('🚀 ~ file -->', file) } } }); </script>

1.4 通过媒体设备获取资源
适用于需要捕获摄像头或麦克风媒体资源时的特殊场景。

<button id="btnOpen">打开摄像头</button> <button id="btnShot">截图</button> <video id="video" autoplay playsinline muted></video> <img id="preview" alt="" /> <script> const btnOpen = document.getElementById('btnOpen'); const btnShot = document.getElementById('btnShot'); const video = document.getElementById('video'); const preview = document.getElementById('preview'); let stream = null; btnOpen.addEventListener('click', async () => { stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false }); video.srcObject = stream; }); btnShot.addEventListener('click', async () => { if (!stream) throw new Error('Camera not started'); const track = stream.getVideoTracks()[0]; const imageCapture = new ImageCapture(track); const blob = await imageCapture.takePhoto(); const url = URL.createObjectURL(blob); preview.src = url; console.log('🚀 ~ file -->', new File( [blob], `screenshot-${Date.now()}.png`, { type: 'image/png' } )) }); </script>

1.5 File System Access API
非标准的实验性接口,功能强大,支持读写。

<button id="openFile">打开文件</button> <button id="openFiles">打开多个文件</button> <button id="openDir">打开目录</button> <script> const buttonFile = document.getElementById('openFile'); buttonFile.addEventListener('click', async () => { const [fileHandle] = await window.showOpenFilePicker(); const file = await fileHandle.getFile(); console.log('🚀 ~ file -->', file) }); const buttonFiles = document.getElementById('openFiles'); buttonFiles.addEventListener('click', async () => { const fileHandles = await window.showOpenFilePicker({ multiple: true, }); const files = await Promise.all(fileHandles.map(fileHandle => fileHandle.getFile())); console.log('🚀 ~ files -->', files) }); const buttonDir = document.getElementById('openDir'); buttonDir.addEventListener('click', async () => { const dir = await window.showDirectoryPicker(); const files = []; for await (const entry of dir.values()) { if (entry.kind === 'file') { files.push(await entry.getFile()); } } console.log('🚀 ~ files -->', files) }); </script>

2 浏览器发送文件
拿到File之后,可以使用fetch发送,如果需要显示进度可以使用XMLHttpRequest
请求体通常使用multipart/form-data编码格式,或者直接上传二进制文件数据。

  • Content-Type: multipart/form-data
  • Content-Type: application/octet-stream
  • Content-Type: [mimetype]

2.1 FormData
使用FormData作为请求体,最通用的方式,适配大多数后端框架与对象存储直传。

const fd = new FormData() fd.append('biz', 'avatar') fd.append('file', file) const res = await fetch('/api/upload', { method: 'POST', body: fd, }) if (res.ok) { const data = await res.json() console.log('🚀 ~ data -->', data) }

浏览器会自动将请求编码为multipart/form-data,并在请求头中设置合适的 boundary。

Content-Length 123456 Content-Type multipart/form-data; boundary=----WebKitFormBoundaryA1B2C3D4 ------WebKitFormBoundaryA1B2C3D4 Content-Disposition: form-data; name="biz" avatar ------WebKitFormBoundaryA1B2C3D4 Content-Disposition: form-data; name="file"; filename="avatar.jpg" Content-Type: image/jpeg [binary content of JPG...] ------WebKitFormBoundaryA1B2C3D4--

2.2 二进制流
直接上传裸文件数据,实现简单,服务端无需解析 multipart,直接写入文件。

const res = await fetch('/api/upload', { method: 'POST', headers: { 'Content-Type': file.type || 'application/octet-stream', 'X-Filename': encodeURIComponent(file.name), }, body: file, }) if (res.ok) { const data = await res.json() console.log('🚀 ~ data -->', data) }
Content-Length 123456 Content-Type image/jpeg X-Filename avatar.jpg

3 浏览器下载文件
下载文件主要有两大类:下载服务端资源,或前端生成/操作文件数据。
3.1 基于服务端资源
服务端提供文件下载链接:

Content-Type: image/jpeg Content-Disposition: attachment; filename="avatar.jpg" Transfer-Encoding: chunked

前端可以基于静态<a>标签,动态创建<a>标签,或者通过导航window.location/window.open触发下载:
<

<a href="/api/file-stream">下载</a> <button id="download-stream">按钮下载</button> <button id="nav-stream">导航下载</button> <script> document.getElementById('download-stream').addEventListener('click', () => { const a = document.createElement('a') a.download = 'avatar.jpg' a.click() }) document.getElementById('nav-stream').addEventListener('click', () => { window.location.href = '/api/file-stream' }) </script>

如果服务端没有返回Content-Disposition,浏览器需要通过<a>标签配合download属性下载文件。

<a href="/api/file-stream" download="avatar.jpg">下载</a>

如果服务端有通过Content-Disposition指定文件名,浏览器通过download属性配置的文件名无效。
💡浏览器导航文件链接大致过程

  1. 解析响应头
  2. 根据Content-Type判断 MIME 类型,决定浏览器如何处理文件
  3. 根据Content-Disposition决定显示还是下载
    1. inline→ 默认值,在页面中显示文件内容
    2. attachment→ 下载文件

  4. 下载文件时会根据Content-Length决定是否显示进度条
  5. 接收响应体并写入内存或文件

3.2 基于二进制数据
适用于前端生成文件或者导出 canvas 等场景。

<button id="download">下载文本</button> <button id="downloadCanvas">下载 canvas 图片</button> <canvas id="canvas" width="200" height="200" style="border: 1px solid #ccc"></canvas> <script> document.getElementById('download').addEventListener('click', () => { const blob = new Blob(['Hello world'], { type: 'text/plain' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = 'hello.txt' a.click() URL.revokeObjectURL(url) }) const canvas = document.getElementById('canvas') const ctx = canvas.getContext('2d') ctx.fillStyle = '#4a90d9' ctx.fillRect(20, 20, 160, 160) ctx.fillStyle = '#fff' ctx.font = '20px sans-serif' ctx.fillText('Canvas', 60, 105) document.getElementById('downloadCanvas').addEventListener('click', () => { canvas.toBlob((blob) => { const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = 'canvas.png' a.click() URL.revokeObjectURL(url) }, 'image/png') }) </script>

3.3 基于 data URL
适用于极小文本数据,特定场景有用。

<button id="download">下载 SVG</button> <script> const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"> <rect fill="#4a90d9" width="200" height="200"/> <text x="100" y="110" text-anchor="middle" fill="#fff" font-size="24">Hello SVG</text> </svg>` document.getElementById('download').addEventListener('click', () => { const dataUrl = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg) const a = document.createElement('a') a.href = dataUrl a.download = 'hello.svg' a.click() }) </script>

3.4 File System Access API
非标准的实验性接口,直接保存到指定位置。

<button id="download">下载文本</button> <script> document.getElementById('download').addEventListener('click', async () => { const handle = await window.showSaveFilePicker({ suggestedName: 'data.txt', }) const writable = await handle.createWritable() await writable.write('Hello World!') await writable.close() }) </script>

4 服务端返回文件
NodeJS一般可以通过BufferStream的方式返回文件。
4.1 非流式下载(buffered download)
同步阻塞,服务器将整个文件读入内存。

ctx.body = fs.readFileSync('girl.jpg')

服务端会返回Content-Length,浏览器可以据此显示下载进度。
4.2 流式下载(streaming download)
异步文件流,分块读取,适合大文件。

ctx.body = fs.createReadStream('girl.jpg')

流式传输不适合使用Content-Length,所以不需要返回。
💡流式传输时 HTTP/1.1 和 HTTP/2 区别
HTTP/1.1 默认使用分块传输Transfer-Encoding: chunked。因为 HTTP/1.1 使用纯文本 + 字节流的格式实现流式传输,浏览器要么提前知道Content-Length,要么在服务器主动关闭连接时才算结束。当响应长度“不可能提前知道”时,就只能靠chunked解决:遇到 chunk-size 为 0 时即整个响应结束。
HTTP/2 的数据帧机制知道什么时候传输结束,因此不支持chunked
📝 总结
本文介绍了浏览器与服务器之间文件传输的完整流程:

  • 获取文件:支持表单上传、拖拽、粘贴、设备采集和 File System Access API 等多种方式
  • 发送文件:FormData 最通用,二进制流更高效
  • 下载文件:服务器链接、Blob 对象、data URL 和 File System Access API 各有适用场景
  • 服务端返回:小文件用 Buffer,大文件用 Stream

选择合适的文件传输方案需要综合考虑:文件大小、用户体验、浏览器兼容性、服务器资源等因素。

原文: https://juejin.cn/post/75962309
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/20 10:42:13

Kubernetes入门地图——核心对象、网络与存储的抽象关系与心智模型

写在前面&#xff0c;本人目前处于求职中&#xff0c;如有合适内推岗位&#xff0c;请加&#xff1a;lpshiyue 感谢。同时还望大家一键三连&#xff0c;赚点奶粉钱。Kubernetes的本质不是简单的容器编排&#xff0c;而是一套完整的分布式系统抽象模型在掌握了容器镜像的工程化构…

作者头像 李华
网站建设 2026/4/8 11:34:24

关于“上瘾”的思考

如何理解上瘾这个事&#xff1f; 比如喝茶&#xff0c;喝咖啡&#xff0c;焚香&#xff0c;打游戏这些事儿。 我觉得他会让我浪费很多时间&#xff0c;长期对自己身体有害。 当“我想要”变成了“我需要”&#xff0c;我究竟是这些仪式的主人&#xff0c;还是它们的囚徒&#x…

作者头像 李华
网站建设 2026/3/13 0:04:05

当我们系统遇到wsnmp32.dll丢失情况 免费下载方法分享

在使用电脑系统时经常会出现丢失找不到某些文件的情况&#xff0c;由于很多常用软件都是采用 Microsoft Visual Studio 编写的&#xff0c;所以这类软件的运行需要依赖微软Visual C运行库&#xff0c;比如像 QQ、迅雷、Adobe 软件等等&#xff0c;如果没有安装VC运行库或者安装…

作者头像 李华
网站建设 2026/4/19 0:56:02

如何在升级前轻松备份 iPhone(4 种方法)

在升级到新手机之前&#xff0c;你应该备份你的 iPhone 吗&#xff1f;当然&#xff0c;如果你不想丢失重要数据&#xff0c;或者打算将数据恢复到新手机&#xff0c;就应该备份。而且&#xff0c;备份步骤很简单&#xff0c;只要你读完本指南&#xff0c;就能知道如何在升级前…

作者头像 李华
网站建设 2026/4/18 11:21:15

探秘无锡大厂成熟Foc电机控制代码,解锁电动车控制新高度

无锡某大厂成熟Foc电机控制 代码&#xff0c;有原理图&#xff0c;用于很多电动车含高端电动自行车厂在用。 直接可用&#xff0c;不是一般的普通代码可比的。 有上位机用于调试和显示波形&#xff0c;直观调试。 代码基于Stm32F030&#xff0c;国产很多芯片可以通用。 本产品包…

作者头像 李华