web端图片上传

630 阅读6分钟

使用ajax提交图片,在这里做一下记录,方便以后查阅,如有不对,欢迎指正。包含在项目中使用的图片压缩。

使用到的技术点:

  1. 选择文件上传,使用input标签的file类型
  2. 拖拽文件上传,input标签也可以通过拖拽上传,这里用div。通过ondrop事件的e.dataTransfer.files获取文件
  3. 图片限制(大小、类型、像素大小)
  4. FileReader对象读取文件内容
  5. 图片压缩(image-conversionnpm
  6. 图片预览
  7. FormData对象,构造表单数据
  8. axios上传

input标签上传图片

<input type="file" @change="inputChange">

设置inputtype类型为filefile类型的input有如下属性:

  • accept,用于规定文件上传控件中期望的文件类型,accept="image/*"
  • capture,文件上传控件中媒体拍摄的方式。当文件类型为图片或视频且在移动端时,此属性才有意义。
    • capture = 'user' 调用前置摄像头
    • capture = 'environment' 调用后置摄像头
    • 不设置则为用户自己选择
  • multiple,布尔值。 是否可以选择多个文件

当点击选择文件按钮后,出现文件列表(pc端),选择好文件,就会触发inputchange事件。(如果是已经选择好了文件,再次点击选择文件按钮,也会触发input的change事件,不选择文件关闭弹窗,e.target.files为空。),通过e.target.files获取文件。

async inputChange (e) {
  console.log(e)
  const file = e.target.files[0]; // e.target.files返回FileList,为文件列表,单文件上传取第0个
  console.log(file);
}

拖拽文件上传

input标签也可以拖拽上传,和上面的上传流程一样,文件拖拽到input上,放下的时候会触发inputchange事件。

这里使用div标签,监听divdrop事件,通过e.dataTransfer.files获取文件。

这里一定要把dragoverdrop默认事件阻止了,否则浏览器默认会打开或下载文件。

<div id="drap">
  拖拽上传图片
</div>

let drapEl = document.querySelector('#drap')

drapEl.addEventListener('dragover', e => {
  e.preventDefault()
}, false)

drapEl.addEventListener('drop', e => {
  e.preventDefault()
  console.log(e.dataTransfer.files)
}, false)

自定义类型和大小

文件类型限制

inputaccept属性可以定义一些可接受的文件类型,但是在使用拖拽上传的时候,这个属性就没用了。一些别的类型的文件,还是会触发change事件,并且在e.target.files里能获取到。accept属性只是在点击选择文件按钮的时候,除规定类型,其他的类型不可见。使用我们一般都需要自己校验一下文件类型。

let file = e.target.files[0]
if (!file) return false // 如果文件不存在,就终止。这种情况发生在,已经选择了文件,再次点击选择文件按钮,但是没选择文件,点了取消按钮。
let reg = /(jpe?g|bmp|gif|png)$/; 
if (!reg.test(file.type)) {
  console.log('类型不符')
  return //类型不符
}

图片大小限制

有时候我们也需要限制一下图片的大小,图片太大可能会上传不上去:

if (file.size > 1024*200) { // file.size是字节大小
  console.log('图片不能大于200K')
  return
}

图片像素大小限制

有时候我们也会对图片的像素大小做一些限制,譬如只能传大于等于100*100的图片:

let picInfo = {}
try {
  picInfo = await checkPicture(file)
} catch (e) {
  console.log(e)
}
if (!picInfo.width || !picInfo.height || picInfo.width < 100 || picInfo.height < 100) {
  console.log('请上传指定大小文件')
  return
}

function checkPicture (file) { // 这个函数返回一个promise
  return new Promise((resolve, reject) => {
    const reader = new FileReader() // FileReader对象
    reader.readAsDataURL(file)
    reader.onload = function (e) { 
      const data = e.target.result // result的类型取决于读取操作方法,readAsDataURL返回base64字符串
      const image = new Image()
      image.onload = function () {
        const {width, height} = image
        resolve({
          width,
          height
        })
      }
      image.onerror = function (e) {
        reject(e)
      }
      image.src= data
    }  
  })
}

FileReader对象

FileReader对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。

const reader = new FileReader()

属性

  1. FileReader.readyState的状态值(只读):
常量名描述
EMPTY0还没有加载任何数据
LOADING1数据正在被加载
DONE2已完成全部的读取请求
  1. FileReader.result文件的内容。该属性仅在读取操作完成后才有效,数据的格式取决于使用哪个方法来启动读取操作。

方法

  1. FileReader.abort()。中止读取操作。在返回时,readyState属性为DONE
  2. FileReader.readAsArrayBuffer()。开始读取指定的 Blob中的内容, 一旦完成, result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象。
  3. FileReader.readAsDataURL()。开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个data: URL格式的Base64字符串以表示所读取文件的内容。
  4. FileReader.readAsText()。开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个字符串以表示所读取的文件内容。

事件处理

  1. FileReader.onabort。处理abort事件。该事件在读取操作被中断时触发。
  2. FileReader.onerror。处理error事件。该事件在读取操作发生错误时触发。
  3. FileReader.onload。处理load事件。该事件在读取操作完成时触发。
  4. FileReader.onloadstart。处理loadstart事件。该事件在读取操作开始时触发。
  5. FileReader.onloadend。处理loadend事件。该事件在读取操作结束时(要么成功,要么失败)触发。
  6. FileReader.onprogress。处理progress事件。该事件在读取Blob时触发。

我一般都是使用 onloadFileReader.readAsDataURL()处理图片。如上面checkPicture方法。

图片压缩

图片压缩可以使用canvas.toDataURL方法进行压缩,比较简单,需要注意的是图片跨域问题。

这里我用的是npmimage-conversion,使用起来非常方便,还可以压缩到指定大小。

import imageConversion from 'image-conversion'

let res // 需要一个变量存图片压缩后的blob对象
try {
  res = await imageConversion.compressAccurately(file, 200) // 图片压缩,指定压缩到200K左右
  res.name = file.name // 压缩后没name属性了,需要添加一个。如果页面上不需要展示name,则不必。
} catch (e) {
  console.log(e)
}
if (res && res.size && res.size / 1024 > this.size && this.size !== 0) { // this.size是指定的图片大小。
  return // 压缩后再次判断图片大小,超过大小则提示
} else if (!res && !res.size && file.size / 1024 > this.size && this.size !== 0) {
  return // 压缩失败了也再次判断图片大小,超过大小则提示
}
this.file = (res && res.size) ? res : file // 如果压缩失败,就用没压缩的文件

image-conversion还有一些别的用法,参考image-conversion git地址

图片预览

FileReader读取完的图片,就可以预览了,可以在上面checkPicture方法里resolve(img),然后设置img标签的src就可以了。

FormData对象

FormData 接口提供了一种表示表单数据的键值对的构造方式,经过它的数据可以使用 XMLHttpRequest.send() 方法送出,本接口和此方法都相当简单直接。如果送出时的编码类型被设为 "multipart/form-data",它会使用和表单一样的格式。

使用:

const formDate = new FormData() // new一个FormData实例
formDate.append('file', e.file) // 添加数据
// formDate.set('file', e.file)
  • formDate.append()FormData 中添加新的属性值,FormData 对应的属性值存在也不会覆盖原值,而是新增一个值,如果属性不存在则新增一项属性值。
  • formDate.set()FormData 设置属性值,如果FormData 对应的属性值存在则覆盖原值,否则新增一项属性值。

我一般都用这2个方法就够了,其他的方法参考MDN

axios上传文件

需要设置 header 中的 Content-Typemultipart/form-data

let config = {
  headers: {
    'Content-Type': 'multipart/form-data'
  },
  // `onUploadProgress` 允许为上传处理进度事件
  onUploadProgress: function (progressEvent) {
    // 对原生进度事件的处理
  },

  // `onDownloadProgress` 允许为下载处理进度事件
  onDownloadProgress: function (progressEvent) {
    // 对原生进度事件的处理
  },
}
axios
  .post('serverUrl', formDate, config)
  .then(res => {
    console.log(res)
  })
  .catch(err => {
    console.log(err)
  })

最后

以上就是目前上传使用到的相关点。关于大文件上传和断点续传,可以参考字节跳动面试官:请你实现一个大文件上传和断点续传