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

Posted 铁锤妹妹@

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端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)

操作方法: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`

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

以上是关于前端js实现canvas压缩图片并上传的主要内容,如果未能解决你的问题,请参考以下文章

HTML5 file API加canvas实现图片前端JS压缩并上传

HTML5 file API加canvas实现图片前端JS压缩并上传 (转载)

前端实现图片压缩上传

前端JS利用canvas的drawImage()对图片进行压缩

前端通过canvas实现图片压缩

js图片前端压缩多图上传(旋转其实已经好了只是手机端有问题要先压缩再旋转)