Appearance
图片压缩
图片压缩核心:
- 通过 缩放比例,然后通过
ctx.drawImage绘制 - 通过
canvas.toBlob调整 quality 达到效果
压缩格式:image/jpeg
压缩质量:1
压缩大小:0 ( 0 kb)
drawImage 示例代码
警告
示例代码仅完成功能,考虑并不完善!
js
function showImageOnCanvas() {
if (!canvas.value) {
nextTick(showImageOnCanvas)
return
}
const image = state.image
if (!image) return
const ctx = canvas.value.getContext('2d')
if (!ctx) return
// 图片 尺寸
const imgWidth = image.width
const imgHeight = image.height
// 获取 canvas 尺寸
const canvasClientRect = canvas.value.getBoundingClientRect()
// 图片 与 canvas 尺寸的比例
const ratio = canvasClientRect.width / imgWidth
// 将 canvas 与 图片比例设置一致
const canvasWidth = canvasClientRect.width
const canvasHeight = imgHeight * ratio
// 设置 canvas 的尺寸
canvas.value.width = canvasWidth
canvas.value.height = canvasHeight
// 计算合适的缩放比例
const scale = Math.min(canvasWidth / imgWidth, canvasHeight / imgHeight)
const drawWidth = imgWidth * scale
const drawHeight = imgHeight * scale
// 计算 x,y,使得图片居中
const x = (canvasWidth - drawWidth) / 2
const y = (canvasHeight - drawHeight) / 2
ctx.drawImage(image, x, y, drawWidth, drawHeight)
}toBlob 示例
js
async function yasuo(type: string, quality: number) {
if (!canvas.value) {
nextTick(() => yasuo(type, quality))
return
}
const blob = await new Promise<Blob | null>((resolve, reject) => {
if (!canvas.value) return reject()
canvas.value.toBlob(resolve, type, quality)
})
if (!blob) throw new Error('blob is null')
state.yasuo.quality = quality
state.yasuo.type = type
state.yasuo.size = blob.size
if (blob.size > state.yasuo.target) {
setTimeout(() => { yasuo('image/jpeg', quality - 0.05) }, 2000)
}
}完整示例代码
vue
<template>
<div class="yasuo-demo">
<button style="border: 1px solid #333;" @click="onSelectImg">选择图片</button>
<button style="border: 1px solid #333;" @click="downloadImg">下载图片</button>
<div class="info-div">
<div>压缩格式:{{ state.yasuo.type }}</div>
<div>压缩质量:{{ state.yasuo.quality }}</div>
<div>压缩大小:{{ state.yasuo.size }} ( {{ state.yasuo.size / 1024 }} kb)</div>
</div>
<canvas ref="canvas" class="the-canvas"></canvas>
</div>
</template>
<script setup lang="ts">
import { nextTick, reactive, useTemplateRef } from 'vue'
const state = reactive({
file: null as File | null,
image: null as HTMLImageElement | null,
yasuo: {
target: 200000,
type: 'image/jpeg',
quality: 1,
size: 0,
},
})
const canvas = useTemplateRef<HTMLCanvasElement>('canvas')
function showImageOnCanvas() {
if (!canvas.value) {
nextTick(showImageOnCanvas)
return
}
const image = state.image
if (!image) return
const ctx = canvas.value.getContext('2d')
if (!ctx) return
// 图片 尺寸
const imgWidth = image.width
const imgHeight = image.height
// 获取 canvas 尺寸
const canvasClientRect = canvas.value.getBoundingClientRect()
// 图片 与 canvas 尺寸的比例
const ratio = canvasClientRect.width / imgWidth
// 将 canvas 与 图片比例设置一致
const canvasWidth = canvasClientRect.width
const canvasHeight = imgHeight * ratio
// 设置 canvas 的尺寸
canvas.value.width = canvasWidth
canvas.value.height = canvasHeight
// 计算合适的缩放比例
const scale = Math.min(canvasWidth / imgWidth, canvasHeight / imgHeight)
const drawWidth = imgWidth * scale
const drawHeight = imgHeight * scale
// 计算 x,y,使得图片居中
const x = (canvasWidth - drawWidth) / 2
const y = (canvasHeight - drawHeight) / 2
ctx.drawImage(image, x, y, drawWidth, drawHeight)
}
async function yasuo(type: string, quality: number) {
if (!canvas.value) {
nextTick(() => yasuo(type, quality))
return
}
const blob = await new Promise<Blob | null>((resolve, reject) => {
if (!canvas.value) return reject()
canvas.value.toBlob(resolve, type, quality)
})
if (!blob) throw new Error('blob is null')
state.yasuo.quality = quality
state.yasuo.type = type
state.yasuo.size = blob.size
if (blob.size > state.yasuo.target) {
setTimeout(() => { yasuo('image/jpeg', quality - 0.05) }, 2000)
}
}
function onSelectImg() {
openFileSelector({ accept: 'image/*' }).then((res) => {
console.log('is > ', res)
state.file = res[0]
fileToImage(state.file).then((img) => {
state.image = img
// dev01.value?.appendChild(img)
showImageOnCanvas()
setTimeout(() => yasuo('image/jpeg', 1), 1000)
})
})
}
function downloadImg() {
if (!canvas.value) {
nextTick(downloadImg)
return
}
canvas.value.toBlob(
(blob) => {
if (!blob) return
const url = URL.createObjectURL(blob)
downloadFromUrl(url, Date.now() + '.'+ state.yasuo.type.split('/')[1])
},
state.yasuo.type,
state.yasuo.quality,
)
}
interface IFileSelectorOptions {
accept?: string
multiple?: boolean
}
function openFileSelector(options: IFileSelectorOptions = {}) {
return new Promise<FileList>((resolve, reject) => {
const input = document.createElement('input')
input.type = 'file'
input.style.opacity = '0'
input.style.position = 'absolute'
input.style.top = '-1000px'
document.body.appendChild(input)
if (options.accept) {
input.accept = options.accept
}
if (options.multiple) {
input.multiple = options.multiple
}
input.onchange = (e: any) => {
// console.log(e)
const file = e.target?.files
if (file) {
resolve(file)
} else {
reject('')
}
document.body.removeChild(input)
}
input.click()
})
}
function fileToImage(file: File) {
return new Promise<HTMLImageElement>((resolve, reject) => {
const img = new Image()
img.src = URL.createObjectURL(file)
img.onload = () => {
resolve(img)
}
img.onerror = () => {
reject(new Error('加载图片失败'))
}
})
}
function downloadFromUrl(url: string, filename: string) {
if (!url) {
throw new Error('url 不能为空')
}
if (!filename) {
throw new Error('filename 不能为空')
}
const link = document.createElement('a')
link.download = filename
link.href = url
// 触发点击下载
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
</script>
<style scoped lang="less">
.yasuo-demo{
border: 1px solid #333;
border-radius: 12px;
padding: 12px;
}
.info-div {
margin: 12px 0;
}
.the-canvas {
width: 300px;
height: 400px;
border: 1px solid #333;
border-radius: 12px;
}
</style>