跳转到内容

前端文件上传与相关操作

刀刀
0字
0分钟
2026/8/16

前置知识

上传方案

  • 通过二进制 blob 传输,如通过 formData 传输
  • 通过转为 base64 字符串上传,但字符串体积会较大

相关对象

files

用户选择一个或多个文件后,可通过 <input type="file"> 元素获取文件数据,返回 FileList 对象,包含用户选择的文件。可获取文件类型、名称、大小等信息,前端用于做其他处理。

js
const fileInput = document.getElementById('fileInput')

fileInput.addEventListener('change', (event) => {
  const selectedFiles = event.target.files

  // 遍历选中的文件
  for (let i = 0; i < selectedFiles.length; i++) {
    const file = selectedFiles[i]
    console.log('文件名:', file.name)
    console.log('文件类型:', file.type)
    console.log('文件大小:', file.size, 'bytes')

    if (file.type !== 'video/mp4') {
      alert('只能上传mp4文件')
    }
  }
})

其中,file 打印结果如下所示:

pCHx6Zn.png

拓展

file 对象本质是 File 类的实例化。这是另一种创建文件的方式,但一般不这么做,因为无法让用户自己选择,也无法写入磁盘。

js
console.log(new File(['content'], 'a.txt')) // 第一个参数必须是数组,第二个参数是文件名

打印结果如下所示:

pCHxoL9.png

注意

  1. 获取到的对象不能直接给后端,后端不认识前端对象,需使用 formData 转换
  2. file 对象是 blob 对象的子类,本质也是 blob

blob

Blob(Binary Large Object)是一种数据对象,用于表示二进制数据。前端上传文件给后端时,通常将文件数据转为 Blob 对象进行传输。

使用 Blob 构造函数创建 Blob 对象,接受一个参数 blobParts,包含要构建 Blob 的数据。blobParts 可以是数组,每个元素代表一部分数据,也可以是包含数据的字符串。

file 对象放进 new Blob() 方法内即可转为 blob 对象。

blob 对象可使用 slice 进行切割操作,放进 new File() 方法内即可转为 file 对象。将切割后的 blob 放进去可转为新的 file 对象。步骤如下:

  1. 获取文件并转为 blob 对象
  2. 通过 slice() 方法截取
  3. 通过 new File() 将截取后的 blob 转为 files 对象
js
const fileInput = document.getElementById('fileInput')

fileInput.addEventListener('change', () => {
  const selectedFile = fileInput.files[0]

  // 将文件转换为 Blob 对象
  const blob = new Blob([selectedFile], { type: selectedFile.type })

  // 执行其他操作,例如将 Blob 对象上传到服务器
})
vue
<input type="file" @change="change" />

<script>
const change = (e) => {
  let file = e.target.files[0]
  const blob = new Blob([file])
  const sliceBlob = new Blob([file]).slice(0, 5000) // 切割0到5000位
  const sliceFile = new File([sliceBlob], 'text.png')
  console.log(sliceFile)
}
</script>

最后可以把切割好的 sliceFile 调用接口传给后端。

注意

后端直接给前端的数据前端也不认识,需要转换。

formData

FormData 是用于构建和处理表单数据的 JavaScript 对象。可将表单数据格式化为可通过 XMLHttpRequestFetch API 发送到服务器的数据体。

使用 FormData 可处理包括文本字段、文件和二进制数据在内的表单数据。搭载 files 对象传递给后端。本质是将前端的 file 对象转为 blob 传递给后端。

js
const change = (e) => {
  const formData = new FormData()
  formData.append('user', 'content')
  formData.append('file', e.target.file[0])
  axios.post('/xx', formData)
}

最终查看接口,传递的参数是二进制文件,请求头也是相应的 form-data 类型。

注意

使用 FormData 对象可方便地处理带有文件和二进制数据的表单,但对于只包含文本数据的简单表单,也可直接将数据格式化为 JSON 对象或查询字符串发送到服务器。具体处理方式取决于服务器端要求和所使用的前端框架或库。

fileReader

FileReader 是用于读取文件内容的 JavaScript 对象。提供了异步读取文件数据的方法,并可按文本、二进制数据、base64 或数据 URL 等不同方式处理。

js
const fileInput = document.getElementById('fileInput')

fileInput.addEventListener('change', (event) => {
  const selectedFile = event.target.files[0]

  const reader = new FileReader()

  reader.addEventListener('load', (event) => {
    // 读取完成后的回调函数
    const fileData = event.target.result
    console.log(fileData)
    // 在这里可以对文件数据进行进一步处理
  })

  // 将文件读取为文本
  // reader.readAsText(selectedFile);

  // 将文件读取为二进制数据
  // reader.readAsArrayBuffer(selectedFile);

  // 将文件读取为数据 URL
  // reader.readAsDataURL(selectedFile);

  // 将文件读取为指定长度的字符串
  // reader.readAsBinaryString(selectedFile);
})
方法操作
readAsText()将文件读取为文本
readAsArrayBuffer()将文件读取为二进制数据
readAsDataURL()将文件读取为数据 URL
readAsBinaryString()将文件读取为指定长度的字符串

每个方法都是异步的,操作完成时触发 load 事件,文件数据存储在 event.target.result 中。可根据需要选择合适的读取方法,在 load 事件处理函数中对数据做进一步处理。例如将文本显示在页面上、将二进制数据发送到服务器,或将数据 URL 用作图片的 src

注意

读取大型文件或使用错误的读取方法可能影响性能或导致异常。确保选择正确的方法,并适当处理读取操作的成功或失败。

案例:缩略图

运用前置知识实现缩略图效果,步骤如下:

  1. 获取用户选择的 file 文件
  2. file 转为 blob 格式,便于通过 slice 实现裁剪
  3. 裁剪后通过 new File() 转为 file 对象
  4. 使用 fileReaderreadAsDataURL() 方法获取图片的 base64 格式,赋值给 img 标签的 src 属性
js
fileChange = (e) => {
  const file = e.target.files[0]
  const blob = new Blob([file])
  const sliceBlob = new Blob([file]).slice(0, 5000) // 切割0到5000位
  const sliceFile = new File([sliceBlob], 'text.png')
  const reader = new FileReader()
  reader.readAsDataURL(sliceFile)
  reader.onload = (e) => {
    this.imgSrc = e.target.result
  }
}
vue
<template>
  <input type="file" @change="fileChange" />
  <img :src="imgSrc" alt="" />
</template>

转换关系

转换关系如下:

  • files 对象前端可直接获取,通过 new Blob() 转为blobblob 也可以通过 new File() 转为 files 对象。filesblob 的子类
  • filesblob 都可用 fileReader 读取为文本和 base64
  • filesblob 都要 appendformdata 内,调用接口传输

关系图

上传方式

单多文件上传

多文件上传就是遍历文件数组,一个个声明 formData 对象,调接口传参。(即循环单文件上传)

步骤如下:

  1. 每次用户选择文件后把文件保存到一个数组内
  2. 点击提交时遍历数组依次 append 添加文件并各自提交

代码如下所示:

js
// 用户选择图片
const onChange = (e) => {
  if (e.target.files.length > 1) {
    imageList.value.concat(e.target.files)
  } else {
    imageList.value.push(e.target.files[0])
  }
}

// 点击提交按钮
const onSubmit = async () => {
  let _formData = new FormData()
  imageList.value.forEach((item) => {
    _formData.append(item.name + 'file', item)
    axios.post('/xx', _formData)
  })
}

切片上传

思路:

设置一个最大值,定义一个变量 current 初始值为 0,上传文件时通过使用 slice(current, current + 设置的允许最大上传的尺寸) 上传文件,上传完后 current 加上当前上传的文件大小即可。

实现步骤如下:

  1. 获取用户选择的文件
  2. 点击提交按钮后获取文件的大小,初始化设置一个变量,保存切片上传的大小,默认为 0
  3. while 循环,只要上传切片的大小没有超过文件总大小,就调用接口传递参数,参数使用 slice 截取大小并累加已上传的文件大小
  4. 通过 asyncawait 把请求变为同步任务,保证上一个切片上传成功再上传下一个切片

代码如下所示:

vue
<script setup>
const fileObj = ref({}) // 文件上传
const precent = ref(0) // 百分比

const onChange = (e) => {
  fileObj.value = e.target.files[0]
}

const onSubmit = async () => {
  let size = 2 * 1024 * 1024
  let current = 0
  let fileSize = fileObj.value.size
  while (current < fileSize) {
    let formData = new FormData()
    formData.append(
      fileObj.value.name,
      fileObj.value.slice(current, current + size),
    )
    // 切片上传
    await axios.post('/upload', formData)
    // 累加文件大小
    current += size
    // 计算百分比
    precent.value = Math.min((current / fileSize) * 100, 100)
  }
}
</script>

拓展

后端的做法是获取到切片的文件判断名称是否相同,相同则在后面做拼接的操作。

断点续传

思路:

当上传的时候中断了,例如 4mb,把中断的值本地存储,下次上传时从第 4mb 开始传起。

以上方代码示例为例:

vue
<script setup>
const fileObj = ref({}) // 文件上传
const precent = ref(0) // 百分比

const onChange = (e) => {
  fileObj.value = e.target.files[0]
}

const onSubmit = async () => {
  if (!fileObj.value)
    fileObj.value = JSON.parse(localStorage.getItem(fileObj.value.name)).file

  let size = 2 * 1024 * 1024
  let current = 0
  let fileSize = fileObj.value.size

  while (current < fileSize) {
    let formData = new FormData()
    formData.append(
      fileObj.value.name,
      fileObj.value.slice(current, current + size),
    )
    // 切片上传
    const res = await axios.post('/upload', formData)
    if (res.code === 200) {
      // 累加文件大小
      current += size
      // 计算百分比
      precent.value = Math.min((current / fileSize) * 100, 100)
    } else {
      // 存储当前文件信息
      localStorage.setItem(
        fileObj.value.name,
        JSON.stringify({ file: fileObj.value, current }),
      )
    }
  }
}
</script>

总体效果

跳转预览:点击跳转

贡献者

The avatar of contributor named as v_duyilin v_duyilin
The avatar of contributor named as 刀刀 刀刀
The avatar of contributor named as 平安喜乐 平安喜乐
The avatar of contributor named as duyidao duyidao

页面历史

刀刀博客累计访客 人;文档累计访问量共