基本原理
重点就是借助 canvas 提供的 API 进行压缩:参考文档
canvas.toDataURL(type, encoderOptions)
压缩图片基本步骤
-
获取图片
-
将图片 “画” 在
canvas上面 -
调用
canvas.toDataURL对图片进行压缩 -
得到压缩后的图片代码
base64格式
具体实现可根据需要,自己自定义。
压缩图片上传小案例
前端代码
<input type="file" id="upload" onchange="compressPic(this.files[0])" >
<p>图片源大小:<span id="source"></span></p>
<p>压缩图片大小:<span id="dest"></span></p>
<img id="pic" />
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.26.1/axios.min.js"></script>
<script>
const ori = document.getElementById('source')
const compress = document.getElementById('dest')
function compressPic(file) {
const reader = new FileReader()
reader.onload = function (e) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext("2d")
const image = new Image()
image.src = e.target.result
setTimeout(() => { // image.src 赋值过程是异步的(猜测)
canvas.width = image.width
canvas.height = image.height
ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
const data = canvas.toDataURL('image/jpeg', 0.5) // 第二个参数为 1 代表不压缩
uploadPic('compress-pic', data)
// 显示图片和始末文件大小便于观察
document.getElementById('pic').src = data
ori.innerText = returnFileSize(baseToFile('ori',image.src).size)
compress.innerText = returnFileSize(baseToFile('com', data).size)
}, 0)
}
reader.readAsDataURL(file)
}
// 上传
function uploadPic (fileName, data) {
const file = baseToFile(fileName, data)
const formData = new FormData()
fileName = 'singleImage' // 要与后台规定的名称一致
formData.append(fileName, file)
axios({
method: 'post',
url: 'http://127.0.0.1:3030/upload',
headers: {'Content-Type': 'multipart/form-data'},
data: formData
})
}
//将 base64 转换为 file
function baseToFile (fileName, data) {
const arr = data.split(',') // 分开类型和代码
const type = arr[0].match(/:(.*?);/)[1] // 取出类型
const b = [...window.atob(arr[1])] // 解码 base 64
const u8a = new Uint8Array(b.length)
b.forEach((v,i) => { u8a[i] = v.charCodeAt(0) }) // 解码内容变成 unit8 内容
fileName = 'demo.jpg' // 在前端声明是什么文件, 方便后端识别
return new File([u8a], fileName, { type })
}
// 计算单位
function returnFileSize(number) {
if(number < 1024) {
return number + 'bytes';
} else if(number >= 1024 && number < 1048576) {
return (number/1024).toFixed(1) + 'KB';
} else if(number >= 1048576) {
return (number/1048576).toFixed(1) + 'MB';
}
}
</script>
express 搭建的后端代码
const express = require('express')
const multer = require('multer')
const cors = require('cors')
const app = express()
const port = 3030
app.listen(port, () => {console.log(port)})
app.use(cors())
const storage = multer.diskStorage({
destination: './files',
filename: (req, file, callback) => {
callback(null, `${Date.now()}-${file.originalname}`)
}
})
const fileFilter = (req, file, callback) => {
const reg = /^image\/.+/
if (reg.test(file.mimetype))
callback(null, true)
else
callback(new Error('only upload image'), false)
}
const multerUpload = multer({storage, fileFilter}).single('singleImage')
app.post('/upload', multerUpload, (req, res) => {
res.send('ok')
})
纪录学习过程中遇到的知识点
获取用户上传的图片
主要利用了 <input type="file"> 和 File 对象。input 参考文档、File 参考文档
基本使用
<input type="file" id="upload" onchange="compressPic(this.files[0])" >
<script>
function compressPic(file) {} // file 类型是 File 对象
</script>
注意一点就是,如果 onchange 写在 <script> 中的话,是这样获取 file 的:
<input type="file" id="upload" >
<script>
document.querySelector('#upload').onchange = (e) => {
compressPic(e.target.files[0])
}
function compressPic(file) {}
</script>
获取用户选择的图片数据 base64 形式
方法就是利用 FileReader 对象。参考文档
基本使用
const reader = new FileReader()
reader.onload = function (e) {
// e.target.result 就是文件数据: base64 格式
}
reader.readAsDataURL(file) // 此 file 为 File 对象
简单说明,onload 时间是在 reader.readAsDataURL(file) 完成后才执行的,因此才能够获取到图片的数据。
图片对象
可以有两种创建方式创建一个图片节点对象,两者是等效的。参考文档
const img = document.createElement("img")
var img = new Image()
前面得到的 base64 数据可以直接赋值给 img.src 属性。需要注意的是,该赋值过程应该是异步的,所以无法直接在赋值后就获取到,所以想要获取它的于是,可以写在 setTimeout(() => {}, 0) 中(此处无参考文献,属于个人猜测)
闭包:向事件回调函数传值
这是刚开始看到的闭包代码,第一眼居然没立马反应过来是闭包,故记录一下。参考文件
const reader = new FileReader()
reader.onload = ((aImg) => {
console.log('1')
return function (e) {
aImg.src = e.target.result
console.log('3')
}
})(img)
console.log('2')
简单解释:先执行了匿名函数,并传入一个 img 参数,然后返回了一个函数,作为 onload 回调函数,秒就秒在该回调函数中可以访问到 aImg 变量。
canvas 压缩图片
此处用到的关于 canvas 的 API 有 :
getContext() 参考文档
drawImage() 参考文档
toDataURL() 参考文档
主要代码是如下
// 创建 canvas 节点元素
const canvas = document.createElement('canvas')
// 创建 canvas 上下文,用于绘制图片
const ctx = canvas.getContext("2d")
// image.width 和 image.height 是图片本身宽高
// canvas.width 和 canvas.height 设置的是 canvas 元素展示区域的大小
canvas.width = image.width
canvas.height = image.height
// 文档中的 dx dy dHeight dWidth 控制的就是 canvas 区域的上下边界和 显示的图片的宽高
// 文档中的 sx sy dHeight sWidth 修改的 “原图” 的上下边界和宽度
// “原图” 加双引号表示的是抽象的原图,真正的原图(前获取到的数据)并不会被修改。
ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
// toDataURL 是对 drawImage 画出来后的图进行压缩,想要压缩,第一个参数只有 'image/jpeg' 和 'image/webp' 可选
const data = canvas.toDataURL('image/jpeg', 0.5) // 第二个参数为 1 代表不压缩
base64 转换为 file
使用的是 new File() 来转换。参考文档
在此之前,因为 File() 接收的是 DOMString 对象的 Array 所以还会用到下面的 API
split() 参考文档
match() 参考文档
atob() 参考文档
Uint8Array() 参考文档
charCodeAt() 参考文档
具体代码:
const arr = data.split(',') // 分开类型和代码
const type = arr[0].match(/:(.*?);/)[1] // 取出类型
const b = [...window.atob(arr[1])] // 解码 base 64
const u8a = new Uint8Array(b.length)
b.forEach((v,i) => { u8a[i] = v.charCodeAt(0) }) // 解码内容变成 unit8 内容
fileName = 'demo.jpg' // 在前端声明是什么文件, 方便后端识别
return new File([u8a], fileName, { type })