前端js实现canvas压缩图片并上传

一. 上传前压缩图片的好处

  1. 可以减少用户的等待时间,提升使用体验,目前手机拍摄的图片文件大小一般在几 M 左右,文件直接上传时会有卡顿现象。
  2. 可以减少服务端存储空间
  3. 再次回去图片资源是也可以快速的加载。虽然目前阿里云的 oss 有相对应的 api 可以通过降低图片质量等方法减少体积,不过使用 canvas 可以直接减少源文件的体积。

因此我们很有必要对上传的图片进行压缩。

二. 处理流程

主要包括以下流程:

  1. 用户通过van-uploader选择图片。
  2. 使用FileReader进行图片预览。
  3. 将图片绘制到canvas画布上,使用canvas画布的能力进行图片压缩todataurl()方式会把图片自动转成base64。
  4. 将压缩后的Base64(DataURL)格式的数据转换成Blob对象进行上传,或者直接上传base64格式,这个根据自己需求。
  5. 将图片上传服务端,上传时候创建Formdata对象实例 new FromData(),把base64或Blob,append()到Formdata里面请求服务端接口,提交图片。
  6. 上传成功,后台接口返回图片的URL。

三. 了解概念

在写代码之前,先来了解几个概念。当然也可以跳过这部分,直接看代码。

1)new FileReader()
我们先来看看官方文档的介绍 FileReader

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

FileReader常用的两个方法如下:

FileReader.onload: 处理load事件。即该钩子在读取操作完成时触发,通过该钩子函数可以完成例如读取完图片后进行预览的操作,或读取完图片后对图片内容进行二次处理等操作。
FileReader.readAsDataURL():读取方法,并且读取完成后,result属性中将包含一个data: URL 格式的 Base64 字符串以表示所读取文件的内容(比如图片)。

在图片上传中,我们可以通过readAsDataURL()方法进行了文件的读取,并且通过result属性拿到了图片的Base64(DataURL)格式的数据,然后通过该数据实现了图片预览的功能。

2)new FormData()
我们先来看看官方文档的介绍 FormData 对象的使用

FormData 对象用以将数据编译成键值对,以便用XMLHttpRequest来发送数据。其主要用于发送表单数据,但亦可用于发送带键数据 (keyed data),而独立于表单使用。如果表单enctype属性设为 multipart/form-data,则会使用表单的submit()方法来发送数据,从而,发送数据具有同样形式。

let formdata = new FormData()
console.log(formdata)

前端js实现canvas压缩图片并上传_第1张图片

操作方法:new FormData()对象的作用以及使用方法

3)new Image()

我们先来看看官方文档的介绍 Image()

Image()函数将会创建一个新的HTMLImageElement实例。它的功能等价于 document.createElement('img')
Image对象是JS中的宿主(或内置)对象,他代表嵌入的图像。当我们创建一个Image对象时,就相当于给浏览器缓存了一张图片,Image对象也常用来做预加载图片(也就是将图片预先加载到浏览器中,当浏览图片的时候就能享受到极快的加载速度)。在HTML页面中,标签每出现一次,也就创建了一个Image对象。

建立图像对象:`let 图像对象名称=new Image([宽度],[高度])`
图像对象的属性: `border complete height hspace lowsrc name src vspace width`
图像对象的事件:`onabort onerror onkeydown onkeypress onkeyup onload`

前端js实现canvas压缩图片并上传_第2张图片

var img = new Image(); //创建一个Image对象 ,会渲染为img标签
img.src = "17.png";  //相当于给浏览器缓存了一张图片
img.onload = function(){alert("img is loaded")};  //onload 事件在图片加载完成后立即执行。
img.onerror = function(){alert("error!")};  // 当图片加载出现错误,会触发,经常在该事件中将图片导向报错图片,以免页面上出现红色的叉叉
img.border="3px solid #ccc"; 

4)canvas的toDataURL()方法
我们先来看看官方文档的介绍HTMLCanvasElement.toDataURL()

HTMLCanvasElement.toDataURL() 方法返回一个包含图片展示的 data URI 。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为 96dpi。

语法:

canvas.toDataURL(type, encoderOptions);

参数:

type:图片格式,默认为 image/png,可以是其他image/jpeg等
encoderOptions:01之间的取值,主要用来选定图片的质量,只要导出为jpg和webp格式的时候此参数才有效果。默认值是0.92,超出范围也会选择默认值。

返回值:

返回值是一个数据url,是base64组成的图片的源数据、可以直接赋值给图片的src属性。

5)图片的展示方式有三种:分别为file(文件流)、blob(本地流)、base64(二进制流)

四. 代码实现

1)通过van-uploader来获取图片

<van-field name="business_license" :rules="[{ required: true, message: '请上传图片' }]">
     <template #input>
         <van-uploader
           :max-size="3000 * 1024"
           @oversize="onOversize"
           :after-read="afterReadLicense"
           :before-delete="deleteLicense"
           v-model="form.licenseArray"
           :max-count="1"
          />
     template>
van-field>
    //获取上传图片大小,进行压缩处理
  compressImg (file) {
      let quality = 1
      if (file.file.size / (1024 * 1024) > 1) {
        quality = 1 / Math.ceil(file.file.size / (1024 * 1024)) // 默认到1m以下
      }
      quality = quality / 2 // 默认质量减半(超过1m的图片,默认质量为500k)
      var that = this
      return new Promise(function (resolve, reject) {
        if (file == []) {
          reject(error)
        } else {
          if (that.isImageAutomaticRotation) {
            that.imgHandle(file.file, quality).then(res => {
              resolve(res)
            })
          } else {
            lrz(file.file, { quality: quality }).then(res => {
              resolve(res.base64)
            })
          }
        }
      })
    },
    // 这是图片上传压缩的核心所在,我们先使用CanvasRenderingContext2D.drawImage()方法将上传的图片文件在画布上绘制出来;
   // 再使用Canvas.toDataURL()将画布上的图片信息转换成base64(DataURL)格式的数据。
   imgHandle (file, quality) {
      return new Promise(function (resolve, reject) {
        let fileType = file.type
        let imgResult = ''
        let reader = new FileReader()
        reader.readAsDataURL(file)
        reader.onload = function () {
          let img = new Image() //先创建图片对象
          img.src = this.result
          let canvas = document.createElement('canvas')
          let ctx = canvas.getContext('2d')
          img.onload = function () { //图片加载完后
            // 做适配
            let width = 500
            if (img.width < 500) {
              width = img.width
            }
            canvas.width = width
            canvas.height = (img.height / img.width) * width
            ctx.drawImage(img, 0, 0, canvas.width, canvas.height)  //绘制图像;把大图片画在一张小画布上,压缩就这么实现了
            // 返回base64
            //quality表示导出的图片质量,只要导出为jpg和webp格式的时候此参数才有效果,默认值是0.92
            imgResult = canvas.toDataURL(fileType, quality)
            resolve(imgResult)
            
            // 这时可能后端要求我们传文件格式图片 base64转Blob
            // const blobImg = that.converrVase64UrlToBlob(imgResult , fileType)
            // resolve(blobImg)
          }
        }
        reader.onerror = function (error) {
          reject(error)
        }
      })
    },
  // base64转Blob
  converrVase64UrlToBlob (base64, mimeType) {
      // mimeType 图片类型,例如 mimeType='image/png'
      const bytes = window.atob(base64.split(',')[1]) // atob方法用于解码base64
      // 创建一个长度为 bytes.length 的 buffer(一个二进制文件), 它会分配一个 16 字节(byte)的连续内存空间,并用 0 进行预填充。
      const ab = new ArrayBuffer(bytes.length)

      // Uint8Array —— 将 ArrayBuffer 中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位)。这称为 “8 位无符号整数”。
      const ia = new Uint8Array(ab)
      for (let i = 0; i < bytes.length; i++) {
        // 更改里面的初始化内容
        ia[i] = bytes.charCodeAt(i)
      }
      // 创建blob格式数据,并传入二进制文件和文件原本类型
      return new Blob([ia], { type: mimeType })
    },
    
  // 上传前置处理
  // 将图片上传到服务端
  afterReadLicense (file) {
      this.compressImg(file).then(res => {
        let formData = new FormData()
        formData.append('m_pic', res)  //append方法往formData里添加数据
        formData.append('token', sessionStorage.getItem('token'))
        console.log(formData.get('m_pic'), 'm_pic') //只能通过get方法查看添加的m_pic
        //请求接口,上传图片
        indexApi
          .uploadImg(formData)
          .then(response => {
            if (response.data.code === 200) {
              this.form.business_license = response.data.data.url //赋值
            } else {
              this.$toast.fail(response.data.message)
            }
          })
          .catch(error => {
            console.log(error)
          })
      })
    },
// 封装的上传图片接口
 uploadImg (val) {
    return formRequest({
      url: 'merchant/upload_img',
      method: 'post',
      data: val,
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    })
  },

注意:
在实际开发中,我们要不要把图片转化为FormData形式上传到服务端,这就看具体的业务需要了。
我们还可以把canvas压缩过的图片,通过axios请求 URL,并通过FormData附带额外的参数,上传到阿里云oss,会返回一个url,就是我们上传到阿里云的图片地址;然后前端拼成一个图片 url 用于from表单提交上传。

学习过程中参考了其他文章:
浅析图片上传及canvas压缩的流程(canvas图片压缩)
Canvas怎么实现上传并压缩图片
vue + elementUi + upLoadIamge组件 上传文件到阿里云oss

你可能感兴趣的:(JavaScript,前端,服务器,javascript)