Skip to main content

图片压缩的实现

一、压缩原理

前端实现图片压缩,主要是通过将原图绘制成 Canvas,然后利用 Canvas 提供的 toDataURL()toBlob() 方法,传入质量参数,进行图片压缩。

1、toDataURL()

浏览器提供的 toDataURL 方法可以将 Canvas 图像对应的 data URI(即 base64 地址)

HTMLCanvasElement.toDataURL(mimeType, quality);

参数:

  • mimeType(可选)String:表示需要转换的图像的 mimeType 类型,默认为 image/png,可以是 image/jpegimage/webp 等;
  • quality(可选)Number:表示转换的图片质量,默认为 0.92,此参数需要图片类型为 image/jpegimage/webp

返回值:base64 data 图片数据。

2、toBlob()

浏览器提供的 toBlob 方法可以将 Canvas 图像对应的 Blob 对象。

HTMLCanvasElement.toBlob(callback, mimeType, quality);

参数:

  • callback(必须)Function:执行成功后的回调方法,参数表示转换的 Blob 对象;
  • mimeType(可选)String:表示需要转换的图像的 mimeType 类型,默认为 image/png,可以是 image/jpegimage/webp 等;
  • quality(可选)Number:表示转换的图片质量,默认为 0.92,此参数需要图片类型为 image/jpegimage/webp

返回值:

二、通过手写实现图片压缩

这里以 React 上传图片并自动压缩为例。

1、实现过程

  • 图片上传并进行回显
  • 上传图片后创建 Canvas 根据原图绘制图片,注意 Canvas 像素要与原图保持同步
  • 通过 toDataURL 或 toBlob 方法对图片进行压缩,并拿到压缩后图片的 url

2、实现源码

import React, { useState, useRef } from 'react'
import './App.css'

const App = () => {
// 获取原图及 Canvas DOM
const curImg = useRef(null)

// 原图 url 及大小
const [imgSrc, setImgSrc] = useState('')
const [imgSize, setImgSize] = useState(0)

// 压缩图的 url 及大小
const [compressImgSrc, setCompressImgSrc] = useState('')
const [compressImgSize, setCompressImgSize] = useState(0)

// 绘制 Canvas 函数
const draw = () => {
const image = curImg.current

// 创建 canvas 绘制上下文
const cvs = document.createElement('canvas')
const context = cvs.getContext('2d')

// 原图成功加载后绘制到 Canvas,并转为压缩后的图片
image.onload = () => {

// 计算拿到原图原始宽高
cvs.width = image.naturalWidth
cvs.height = image.naturalHeight

// 绘制原图至 Canvas,像素与原图保持同步
context.drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight)

// 将 canvas 转为图片,压缩质量参数为 0.8
cvs.toBlob(
(blob) => {
setCompressImgSrc(URL.createObjectURL(blob))
setCompressImgSize((blob.size / 1024 / 1024).toFixed(1))
},
'image/jpeg',
0.8
)
}
}

// 点击上传图片并进行图片回显
const uploadImage = (event) => {
let curFile = event.currentTarget.files[0]

// 限制图片大小
if (curFile.size > 10 * 1024 * 1024) {
alert('图片大于 10M 请重新上传')
} else {
setImgSize((curFile.size / 1024 / 1024).toFixed(1))
// 使用 FileReader 进行已上传文件的读取
let reader = new FileReader()
reader.onload = (event) => {
let curImgUrl = event.currentTarget.result
setImgSrc(curImgUrl)

// 根据上传的图片绘制 canvas
draw()
}
reader.readAsDataURL(curFile)
}
}

return (
<>
<input type="file" accept="image/*" onChange={uploadImage} />
<div className="container">
<div className="item">
<section className="img-wrap">
<img
ref={curImg}
src={imgSrc}
width="200"
height="120"
alt="图片"
/>
</section>
<p>原图:{imgSize}MB</p>
</div>
<div className="item">
<section className="img-wrap">
<img
src={compressImgSrc}
width="200"
height="120"
alt="图片"
/>
</section>
<p>压缩后:{compressImgSize}MB</p>
</div>
</div>
</>
)
}

export default App

3、实现效果

上传前:

上传后:

三、通过 Compressor.js 库实现

Compressor.js 是用 HTMLCanvasElement.toBlob 进行压缩的 JavaScript 库,可用在上传之前预压缩客户端的图像文件。

1、安装

npm install compressorjs

2、使用方式

new Compressor(file[, options])

参数:

  • file(必须):需要压缩的 File/Blob 格式图像文件;
  • options(可选):压缩参数。

点击查看压缩参数使用详情

3、示例

与上面手写实现压缩的代码一样,但使用 Compressor 可以替代 Canvas 转换手写的代码:

import React, { useState, useRef } from 'react'
import './App.css'
import Compressor from 'compressorjs'

const App = () => {
// 获取原图及 Canvas DOM
const curImg = useRef(null)

// 原图 url 及大小
const [imgSrc, setImgSrc] = useState('')
const [imgSize, setImgSize] = useState(0)

// 压缩图的 url 及大小
const [compressImgSrc, setCompressImgSrc] = useState('')
const [compressImgSize, setCompressImgSize] = useState(0)

// 设置回显压缩图
const setCompressImg = (blob) => {
setCompressImgSrc(URL.createObjectURL(blob))
setCompressImgSize((blob.size / 1024 / 1024).toFixed(1))
}

// 上传显示图片操作
const uploadImage = (event) => {
let curFile = event.currentTarget.files[0]

// 限制图片大小
if (curFile.size > 10 * 1024 * 1024) {
alert('图片大于 10M 请重新上传')
} else {
setImgSize((curFile.size / 1024 / 1024).toFixed(1))

// 回显原图
let reader = new FileReader()
reader.onload = (event) => {
let curImgUrl = event.currentTarget.result
setImgSrc(curImgUrl)
}
reader.readAsDataURL(curFile)

// 直接使用 Compressor 进行图片压缩
new Compressor(curFile, {
quality: 0.8,
success(result) {
setCompressImg(result)
},
error(err) {
setCompressImg(curFile)
console.log(err.message)
},
})
}
}

return (
<>
<input type="file" accept="image/*" onChange={uploadImage} />
<div className="container">
<div className="item">
<section className="img-wrap">
<img
ref={curImg}
src={imgSrc}
alt="图片"
/>
</section>
<p>原图:{imgSize}MB</p>
</div>
<div className="item">
<section className="img-wrap">
<img
src={compressImgSrc}
alt="图片"
/>
</section>
<p>压缩后:{compressImgSize}MB</p>
</div>
</div>
</>
)
}

export default App

上传前:

上传后: