canvas 压缩图片
拖动 range, 查看压缩效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
padding: 20px;
}
.img-preview {
--clip-width: 0px;
display: none;
/* display: grid; */
position: relative;
grid-template-columns: 1fr 1fr;
margin-top: 20px;
gap: 10px;
width: 500px;
}
img {
position: absolute;
inset: 0;
width: 100%;
height: auto;
padding: 10px;
border: 1px solid #ccc;
border-radius: 6px;
user-select: none;
}
#source-img {
clip: rect(auto, var(--clip-width), auto, auto);
z-index: 2;
}
.line {
--line-h: 0px;
--line-x: 0px;
position: absolute;
top: 0;
left: 0;
width: 10px;
height: var(--line-h);
translate: var(--line-x) 0;
background-color: rgba(153, 153, 153, 0.5);
z-index: 3;
cursor: move;
user-select: none;
}
</style>
</head>
<body>
<input type="file" id="file" />
<br /><br />
<input type="range" id="quality" min="0" max="1" step="0.1" value="0.9" />
<br />
<div class="img-preview">
<img alt="" id="source-img" />
<img alt="" id="compressed-img" />
<div class="line"></div>
</div>
<script>
const imgPreview = document.querySelector('.img-preview');
const line = imgPreview.querySelector('.line');
init();
function init() {
const file = document.getElementById('file');
file.addEventListener('change', handleImageCompress);
line.addEventListener('mousedown', handleLineMovedown);
}
function handleLineMovedown(e) {
imgPreview.addEventListener('mousemove', handleImgPreviewMove);
line.addEventListener('mouseup', handleLineMouseup);
}
function handleImgPreviewMove(e) {
let x = e.clientX - imgPreview.offsetLeft - line.offsetWidth / 2;
x = Math.max(x, 0);
x = Math.min(x, imgPreview.offsetWidth - line.offsetWidth);
line.style.setProperty('--line-x', `${x}px`);
imgPreview.style.setProperty('--clip-width', `${x}px`);
}
function handleLineMouseup() {
imgPreview.removeEventListener('mousemove', handleImgPreviewMove);
line.removeEventListener('mouseup', handleLineMouseup);
}
async function handleImageCompress(e) {
/** @type {File} */
const file = e.target.files[0];
if (!file) return;
const imgPreview = document.querySelector('.img-preview');
imgPreview.style.display = 'none';
// 读取图片的base64数据
const sourceSrc = await readFileDataUrl(file);
// 显示图片
const sourceImg = document.getElementById('source-img');
sourceImg.onload = () => {
const compressedImg = document.getElementById('compressed-img');
const qualityRange = document.getElementById('quality');
const line = document.querySelector('.line');
qualityRange.value = 0.9;
const canvas = createCanvas(sourceImg);
queueMicrotask(() => {
line.style.setProperty('--line-h', `${sourceImg.offsetHeight}px`);
const x = (sourceImg.offsetWidth - line.offsetWidth) / 2;
line.style.setProperty('--line-x', `${x}px`);
imgPreview.style.setProperty('--clip-width', `${x}px`);
});
qualityRange.addEventListener('change', (e) => {
const quality = +e.target.value;
// 压缩图片
const compressedSrc = doCompression2(canvas, sourceSrc, file.type, quality);
// 显示压缩后的图片
compressedImg.src = compressedSrc;
});
// 压缩图片
const compressedSrc = doCompression(canvas, sourceSrc, file.type);
// 显示压缩后的图片
compressedImg.src = compressedSrc;
imgPreview.style.display = 'grid';
};
sourceImg.src = sourceSrc;
}
/**
* 创建 canvas 并绘制图片
* @param {HTMLImageElement} img
*/
function createCanvas(img) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
return canvas;
}
function doCompression2(canvas, imgSrc, type, quality = 0.9) {
const compressedSrc = canvas.toDataURL(type, quality);
console.log('doCompression2 => ', compressedSrc.length, imgSrc.length, type, quality);
return compressedSrc;
}
/**
* 压缩处理
* @param {HTMLCanvasElement} canvas
* @param {HTMLImageElement} img
* @param {String} filetype
* @param {Number} quality
*/
function doCompression(canvas, imgSrc, type, quality = 0.9) {
const compressedSrc = canvas.toDataURL(type, quality);
// 判断压缩后的base64数据是否大于原图的base64数据
if (compressedSrc.length >= imgSrc.length) {
if (quality <= 0) {
return doCompression(canvas, imgSrc, 'image/webp');
}
return doCompression(canvas, imgSrc, type, Number((quality - 0.1).toFixed(1)));
}
console.log('doCompression => ', compressedSrc.length, imgSrc.length, type, quality);
return compressedSrc;
}
function readFileDataUrl(file) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.addEventListener('load', () => {
resolve(reader.result);
});
reader.readAsDataURL(file);
});
}
</script>
</body>
</html>