<div id="content_views" class="htmledit_views">
<h2><a name="t0"></a>前言</h2>
相信大家都做过图片上传相关的功能,在图片上传的过程中,不知道大家有没有考虑过文件体积的问题,如果我们直接将原图片上传,可以图片体积比较大,一是上传速度较慢,二是前端进行渲染时速度也比较慢,比较影响客户的体验感。所以在不影响清晰度的情况下,前端可以在上传前对图片的大小体积进行压缩,压缩到一个比较合适的大小进行上传,本文就带大家一起来看看前端 JS 如何实现图片压缩,有需要的小伙伴抓紧收藏一下吧!
原理(必看)
省流:主要使用 canvas的 drawImage 方法先绘制为 canvas 图像,再结合 toDataURL 转化为DataURl 进行存储图片链接。
drawImage简单介绍
Canvas 2D API 中的
CanvasRenderingContext2D.drawImage()
方法提供了多种在画布Canvas)上绘制图像的方式。
用法如下:
CanvasRenderingContext2D.drawImage() - Web API 接口参考 | MDN (mozilla.org)
语法如下:
-
drawImage(image, dx, dy);
-
drawImage(image, dx, dy, dWidth, dHeight);
-
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
我们使用第二种进行绘制,参数含义如下:
image:绘制到上下文的元素。
dx:image
的左上角在目标画布上 X 轴坐标。
dy:image
的左上角在目标画布上 Y 轴坐标。
dWidth:image
在目标画布上绘制的宽度。允许对绘制的 image
进行缩放。如果不说明,在绘制时 image
宽度不会缩放。
dHeight:image
在目标画布上绘制的高度。允许对绘制的 image
进行缩放。如果不说明,在绘制时 image
高度不会缩放。
简单示例
注意:如果随意的修改图像的尺寸,会导致图像失真,我们可以先获取到图像资源的原始尺寸,然后进行等比缩放,意思就是当我们确定设置宽度之后,高度要进行等比调整。公式就是交叉相乘积相等。
// 如果宽度设置为 500, 那么高度也应该进行等比缩放
// naturalWidth => 500
// naturalHeight => X
// naturalWidth * X = naturalHeight * 500// 计算得出高度
X = naturalHeight * 500 / naturalWidth
-
var can =
document.
querySelector(
'canvas')
-
var context = can.
getContext(
'2d')
-
var imgDom =
new
Image();
-
imgDom.
src =
'./img.jpg';
-
imgDom.
onload =
function (
) {
-
// 注意:图像绘制时,必须保证资源已经加载完成
-
console.
log(
'图片的原始宽度', imgDom.
naturalWidth);
-
console.
log(
'图片的原始高度', imgDom.
naturalHeight);
-
-
context.
drawImage(
-
imgDom,
-
0,
0,
-
500, imgDom.
naturalHeight *
500 / imgDom.
naturalWidth
-
);
-
}
toDataURL简单介绍
我们将图片绘制到 canvas 之后,还需要将 canvas 转化为 Data URl,转化为 DataURl 之后可以显示到我们的屏幕上面,也可以存放到后端服务器,使用 canvas 所提供的 toDataURL 实例方法即可。
官方解释:HTMLCanvasElement.toDataURL() 方法返回一个包含图片展示的 data URI
HTMLCanvasElement.toDataURL() - Web API 接口参考 | MDN (mozilla.org)
语法:canvas.toDataURL(type, encoderOptions);
type(可选):图片格式,默认为 image/png
encoderOptions(可选):在指定图片格式为 image/jpeg
或 image/webp
的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92
。其他参数会被忽略。
简单示例
-
-
// 获取压缩后的图片数据
-
can.
width = imgDom.
naturalWidth
-
can.
height = imgDom.
naturalHeight
-
-
const compressedData = can.
toDataURL(
'image/jpeg',
0.6)
// 可调整质量参数
-
console.
log(
'compressedData: ', compressedData)
转化后 DataURL 结果如下
实现
先奉上全部代码,方便大家看,下面进行解释!
-
<!DOCTYPE html>
-
<html>
-
<head>
-
<title>图片压缩上传
</title>
-
<meta charset="UTF-8">
-
</head>
-
<body>
-
<input type="file" id="fileInput" accept="image/*">
-
<button onclick="compressAndUpload()">压缩并上传图片
</button>
-
<canvas id="canvas" style="display: none;">
</canvas>
-
<script>
-
function
compressAndUpload(
) {
-
const fileInput =
document.
getElementById(
'fileInput');
-
const file = fileInput.
files[
0];
-
if (!file) {
-
alert(
'请先选择要上传的图片');
-
return;
-
}
-
const reader =
new
FileReader();
-
reader.
onload =
function (
) {
-
const img =
new
Image();
-
img.
src = reader.
result;
-
img.
onload =
function (
) {
-
const canvas =
document.
getElementById(
'canvas');
-
const ctx = canvas.
getContext(
'2d');
-
const maxWidth =
800;
// 设置最大宽度为800像素
-
let width = img.
width;
-
let height = img.
height;
-
-
// 判断是否需要缩放
-
if (width > maxWidth) {
-
height *= maxWidth / width;
-
width = maxWidth;
-
}
-
// 设置 canvas 的宽高
-
canvas.
width = width;
-
canvas.
height = height;
-
-
// 将图片绘制到 canvas 上
-
ctx.
drawImage(img,
0,
0, width, height);
-
// 获取压缩后的图片数据
-
const compressedData = canvas.
toDataURL(
'image/jpeg',
0.7);
// 可调整质量参数
-
-
// 创建一个新的压缩后的 File 对象
-
const compressedFile =
dataURItoBlob(compressedData, file.
type);
-
compressedFile.
lastModifiedDate = file.
lastModifiedDate;
-
compressedFile.
name = file.
name;
-
-
// 上传压缩后的图片文件
-
uploadImage(compressedFile);
-
};
-
};
-
reader.
readAsDataURL(file);
-
}
-
-
function
dataURItoBlob(
dataURI, mimeType) {
-
const binary =
atob(dataURI.
split(
',')[
1]);
-
const array = [];
-
for (
let i =
0; i < binary.
length; i++) {
-
array.
push(binary.
charCodeAt(i));
-
}
-
return
new
Blob([
new
Uint8Array(array)], {
type: mimeType });
-
}
-
-
function
uploadImage(
compressedFile) {
-
const formData =
new
FormData();
-
formData.
append(
'image', compressedFile);
-
-
fetch(
'/upload', {
-
method:
'POST',
-
body: formData
-
})
-
.
then(
response => {
-
if (response.
ok) {
-
console.
log(
'图片上传成功');
-
}
else {
-
console.
error(
'图片上传失败');
-
}
-
})
-
.
catch(
error => {
-
console.
error(
'发生错误:', error);
-
});
-
}
-
</script>
-
</body>
-
</html>
我们看一下压缩前后体积对比,压缩前550290,压缩后31523,缩小了十几倍,这个压缩还是很明显的。
首先我们看这三行代码
我们先初始化一个 reader
是一个 FileReader
对象的实例
reader.readAsDataURL(file),
这行代码的作用是将选择的文件读取为 Data URI 格式的字符串。
当执行 reader.readAsDataURL(file)
时,会发生以下几件事情:
FileReader
对象开始异步读取file
中的数据。- 一旦读取完成,
FileReader
的onload
事件将被触发。- 读取的结果将存储在
FileReader
对象的result
属性中,格式为 Data URI 字符串。
看一下 FileReader
对象的 result
的打印结果,为 Data URL格式
最终我们将读取出来的 Data URI 字符串赋值给 Image 的 src,也就是下面这行代码,然后等待 img 加载完毕开始对 img 进行压缩,具体怎么压缩上面已经简单演示过。
-
const img =
new
Image()
-
img.
src = reader.
result
-
img.
onload =
function (
) {}
接下来我们设置了一个最大宽度为800,然后判断当前图片宽度是否大于该值,如果大于进行缩放计算,小于就不进行等比缩放计算。最后将计算出的值使用 drawImage 绘制到 canvas 上面。
-
const maxWidth =
800;
// 设置最大宽度为800像素
-
let width = img.
width;
-
let height = img.
height;
-
-
// 判断是否需要缩放
-
if (width > maxWidth) {
-
height *= maxWidth / width;
-
width = maxWidth;
-
}
-
-
// 设置 canvas 的宽高
-
canvas.
width = width;
-
canvas.
height = height;
-
-
// 将图片绘制到 canvas 上
-
ctx.
drawImage(img,
0,
0, width, height);
现在我们将 canvas 转化成Data URI 字符串,canvas.toDataURL('image/jpeg', 0.7),
这行代码的作用是将 canvas 上绘制的图像数据导出为 JPEG 格式的 Data URI 字符串,并设置图像质量为 0.7。
-
// 获取压缩后的图片数据
-
const compressedData = canvas.
toDataURL(
'image/jpeg',
0.7);
// 可调整
我们看一下 compressedData,是一个Data URI 字符串,其实到这里就可以了,我们可以将 Data URI 传到后端进行存储起来,也可以转化为文件格式进行存储,我这里选择使用文件格式进行存储,不需要的可以不使用下面的方式。
接下来就是创建一个新的压缩后的 File 对象
-
-
const compressedFile =
dataURItoBlob(compressedData, file.
type)
-
compressedFile.
lastModifiedDate = file.
lastModifiedDate
-
compressedFile.
name = file.
name
-
-
function
dataURItoBlob(
dataURI, mimeType) {
-
const binary =
atob(dataURI.
split(
',')[
1])
-
const array = []
-
for (
let i =
0; i < binary.
length; i++) {
-
array.
push(binary.
charCodeAt(i))
-
}
-
return
new
Blob([
new
Uint8Array(array)], {
type: mimeType })
-
}
新的文件对象
最后直接使用 FormData 进行上传即可,这一块就不说了。
总结
前端实现图片压缩主要是利用的 canvas 来实现,实现思路为使用 canvas 的 drawImage 方法先绘制为 canvas 图像,再结合 toDataURL 转化为 DataURl 进行存储图片链接以及压缩图像质量。在toDataURL 中可以调整图像质量,需要注意的是我们在压缩图像时要注意等宽高缩放,否则会导致图像出现失真的情况。