背景
压缩原因
图片上传一般不上传大体积图片,因为一些清晰度要求不高的场景,上传大体积图片意义不大且消耗资源。一是上传耗时比较长,二是增加了存储的开销,三是展示时消耗下载带宽,影响加载效率。 影响用户体验,所以有了前端进行图片压缩的需求
压缩方案
前端图片压缩主要思路,将图片绘制到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版本
代码实现
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>