H5 canvas画布 图片批量压缩上传

1,799 阅读4分钟

背景

压缩原因

图片上传一般不上传大体积图片,因为一些清晰度要求不高的场景,上传大体积图片意义不大且消耗资源。一是上传耗时比较长,二是增加了存储的开销,三是展示时消耗下载带宽,影响加载效率。 影响用户体验,所以有了前端进行图片压缩的需求

压缩方案

前端图片压缩主要思路,将图片绘制到canvas中,通过canvas的toDataURL方法,控制图片质量进行压缩。

  • 通过 input 框选择图片 监听input的change(上传)事件
  • 通过FileReader读取<input>元素上选择文件后返回的FileList对象
  • 注册FileReader读取文件成功事件(load)回调,new Image()去赋值src进行加载预览
  • 通过用readAsDataURL方法转base64编码
  • 取图片原始大小,按要求的大小,等比例设置canvas宽高,绘制到 canvas 画布上
  • 使用 canvas 的toDataURL获取base64 格式的图片数据,可指定图片质量
  • 将压缩后的 Base64(DataURL) 格式的数据转换成 Blob 对象进行上传(不限,看业务需求)

另外,贴一个在线图片压缩网址 亲测好用 tinypng.com/

涉及知识点介绍

FileReader

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

其中File对象可以是来自用户在一个input元素上选择文件后返回的FileList对象, 也可以来自拖放操作生成的 DataTransfer对象, 还可以是来自在一个HTMLCanvasElement上执行mozGetAsFile()方法后返回结果。

FileReader.readAsDataURL(), readAsDataURL 方法会读取指定的 Blob 或 File 对象。 读取操作完成的时候,readyState 会变成已完成DONE, 并触发load事件, 同时 result 属性将包含一个data:URL格式的字符串(base64编码)以表示所读取文件的内容。

Blob对象

一直以来,JS都没有比较好的可以直接处理二进制的方法。而Blob的存在,允许我们可以通过JS直接操作二进制数据。

Blob对象作为一个装填二进制数据的基本对象, 其作用也仅仅是一个容器, 而真正的业务功能则需要通过FileReader、URL、Canvas等对象实现

一个Blob对象就是一个包含有只读原始数据的类文件对象。 Blob对象中的数据并不一定得是JavaScript中的原生形式。 File接口基于Blob,继承了Blob的功能,并且扩展支持了用户计算机上的本地文件。 www.cnblogs.com/hhhyaaon/p/… developer.mozilla.org/zh-CN/docs/…

formData

图片上传方式: 1,图片转化为dataURL(base64),这样就成为了一串字符串,再传到服务端, 缺点很多,数据量比转换之前增加1/3,增加了存储开销(如果存在数据库,就多了访问数据库;如果解析成图片再存储,就多了解析的开销)。所以这样方式不可取。

2、使用formData对象进行上传: FormData对象用以将数据编译成键值对,以便用XMLHttpRequest来发送数据,我们还可以使用XMLHttpRequest的send()方法来异步的提交"表单".比起普通的ajax,使用FormData的最大优点就是我们可以异步上传一个二进制文件 。

html2canvas

基于canvas

允许让我们直接在用户浏览器上拍摄网页或其部分的“截图”。 html2canvas 的作用就是根据 DOM 生成对应的图片,所以一个比较常见的应用场景就是我们可以使用它在 H5 端生成分享图。

注意问题:

(1)在线图片跨越问题。给img属性crossOrigin 设置为 anonymous 、再给图片的 url 中拼上一个随机字符串

< img crossorigin="anonymous" :src="`xxx?_=${Date.now()}`" >

或者后端在图片服务器上设置 Access-Control-Allow-Origin: *。

(2)截图样式异常:文字重叠,边距重叠等 使用html2canvas@1.0.0-alpha.5版本

www.cnblogs.com/padding1015…

代码实现

template

<template>
  <div id="app">
    <div class="buttonList flex">
      <input ref="input" type="file" @change="uploadF" multiple />
      <button @click="compress">压缩</button>
    </div>
    <!-- 压缩前图片 -->
    <div v-if="previewList.length > 0" class="preview flex" ref="preview">
      <img
        ref="preImg"
        v-for="(item, index) in previewList"
        class="new"
        :key="index"
        :src="item.src"
      />
    </div>
    <!-- 压缩后图片 -->
    <div v-if="finalList.length > 0" class="preview flex">
      <img
        ref="finalImg"
        v-for="(item, index) in finalList"
        class="new"
        :key="index"
        :src="item.src"
      />
    </div>
  </div>
</template>

script

<script>
export default {
  name: 'app',
  data() {
    return {
      msg: '图片压缩',
      previewList: [],//渲染预览图片
      finalList: [],//压缩后图片
      maxW: 80,//最大宽
      maxH: 80,//最大高
    }
  },
  methods: {
    /**
     * 上传图片信息
     */
    uploadF(e) {

      const imgArr = e.target.files
      if (imgArr.length) {
        [].forEach.call(imgArr, this.readAndPreview);
      }
    },

    /**
     * 读取和预览图片
     */
    readAndPreview(file) {
      let vm = this
      console.log('file', file)
      let reader = new FileReader();
      reader.readAsDataURL(file);
      reader.addEventListener("load", function () {
        vm.previewList.push({ file: file, src: this.result });
      }, false);
      //true 是捕获  默认是false冒泡
    },

    /**
     * 压缩
     */
    compress() {
      if (this.previewList.length == 0) {
        alert("请选择图片")
        return
      }
      [].forEach.call(this.$refs.preImg, this.compressF);
    },
    /**
     * 压缩逻辑
     */
    compressF(img) {
      let vm = this
      let naturalWidth = img.naturalWidth
      let naturalHeight = img.naturalHeight
      let targetW = null
      let targetH = null
      // 判断图片是否需要压缩
      if (naturalWidth > this.maxW || naturalHeight > this.maxH) {
        let scale = naturalWidth / naturalHeight;
        if (scale > (this.maxW / this.maxH)) {
          //Math 对象实例 round() 方法可把一个数字舍入为最接近的整数
          // 更宽 按最大宽度 等比例设置尺寸
          targetW = this.maxW
          targetH = Math.round(this.maxH * (naturalHeight / naturalWidth))
        } else {
          // 更高,按最大高度 等比例设置尺寸
          targetH = this.maxH
          targetW = Math.round(this.maxH * (naturalWidth / naturalHeight))
        }

      } else {
        alert('无需压缩')
        return false
      }

      let imgN = new Image()
      imgN.src = img['src'] // 图片的地址
      imgN['width'] = targetW
      imgN['height'] = targetH
      imgN.onload = function () {
        let canvas = document.createElement('canvas')
        canvas.width = targetW; /*设置新的图片的宽度*/
        canvas.height = targetH; /*设置新的图片的长度*/
        let ctx = canvas.getContext('2d')
        ctx.clearRect(0, 0, targetW, targetH)
        ctx.drawImage(this, 0, 0, targetW, targetH)
        let dataURL = canvas.toDataURL("image/png", 1);
        vm.finalList.push({ src: dataURL });
        vm.$nextTick(() => {
          console.log(vm.$refs.finalImg)
        })
        
        // blob格式上传
        canvas.toBlob(blob => {
          console.log(blob)
          // 后台接口交互
        })
      }
    }
  }
}
</script>

效果图

微信图片_20220403161805.png

仓库地址

项目地址 github.com/xuhongg/ima…

预览地址 htmlpreview.github.io/?https://gi…