最新文章
【前端技術(shù)】JS壓縮圖片的思路
在開發(fā)中,相信很多人都做過圖片上傳相關(guān)功能。然而,在圖片上傳過程中,文件體積問題常常被忽視。若直接上傳原圖片,可能因體積較大導(dǎo)致上傳速度慢,且前端渲染時(shí)也會(huì)比較遲緩,極大地影響用戶體驗(yàn)。因此,在不影響清晰度的前提下,前端可以在上傳前對(duì)圖片大小體積進(jìn)行壓縮,調(diào)整到合適大小后再進(jìn)行上傳。本文將詳細(xì)介紹前端 JS 如何實(shí)現(xiàn)圖片壓縮,有需要的小伙伴趕緊收藏起來吧!
原理(必看)
簡(jiǎn)而言之:主要利用 canvas 的 drawImage 方法先將圖片繪制為 canvas 圖像,再通過 toDataURL 轉(zhuǎn)化為 DataURL 來存儲(chǔ)圖片鏈接。
drawImage 簡(jiǎn)單介紹
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);
我們使用第二種語法進(jìn)行繪制,參數(shù)含義如下:
image:要繪制到上下文的元素。
dx:image 的左上角在目標(biāo)畫布上的 X 軸坐標(biāo)。
dy:image 的左上角在目標(biāo)畫布上的 Y 軸坐標(biāo)。
dWidth:image 在目標(biāo)畫布上繪制的寬度,可對(duì)繪制的 image 進(jìn)行縮放。若不指定,繪制時(shí) image 寬度不會(huì)縮放。
dHeight:image 在目標(biāo)畫布上繪制的高度,可對(duì)繪制的 image 進(jìn)行縮放。若不指定,繪制時(shí) image 高度不會(huì)縮放。
簡(jiǎn)單示例
注意:隨意修改圖像尺寸可能導(dǎo)致圖像失真。我們可以先獲取圖像資源的原始尺寸,然后進(jìn)行等比縮放。即當(dāng)確定設(shè)置寬度后,高度要進(jìn)行等比調(diào)整,可依據(jù)交叉相乘積相等的公式進(jìn)行計(jì)算。
例如,如果寬度設(shè)置為 500,那么高度也應(yīng)進(jìn)行等比縮放。公式為:naturalWidth * X = naturalHeight * 500,由此可計(jì)算出高度 X = naturalHeight * 500 /naturalWidth。
以下是代碼示例:
javascript
復(fù)制
var can = document.querySelector('canvas');
var context = can.getContext('2d');
var imgDom = new Image();
imgDom.src = './img.jpg';
imgDom.onload = function () {
// 注意:圖像繪制時(shí),必須保證資源已經(jīng)加載完成
console.log('圖片的原始寬度', imgDom.naturalWidth);
console.log('圖片的原始高度', imgDom.naturalHeight);
context.drawImage(
imgDom,
0, 0,
500, imgDom.naturalHeight * 500 / imgDom.naturalWidth
);
};
toDataURL 簡(jiǎn)單介紹
將圖片繪制到 canvas 后,還需將 canvas 轉(zhuǎn)化為 Data URL。轉(zhuǎn)化為 DataURL 后,可以在屏幕上顯示,也可存儲(chǔ)到后端服務(wù)器。使用 canvas 提供的 toDataURL 實(shí)例方法即可。
官方解釋:HTMLCanvasElement.toDataURL () 方法返回一個(gè)包含圖片展示的 data URI。
參考:HTMLCanvasElement.toDataURL () - Web API 接口參考 | MDN (mozilla.org)。
語法:canvas.toDataURL (type, encoderOptions);
其中,type(可選)為圖片格式,默認(rèn)為 image/png;encoderOptions(可選)在指定圖片格式為 image/jpeg 或 image/webp 的情況下,可以從 0 到 1 的區(qū)間內(nèi)選擇圖片的質(zhì)量。若超出取值范圍,將使用默認(rèn)值 0.92,其他參數(shù)會(huì)被忽略。
簡(jiǎn)單示例
javascript
復(fù)制
// 獲取壓縮后的圖片數(shù)據(jù)
can.width = imgDom.naturalWidth;
can.height = imgDom.naturalHeight;
const compressedData = can.toDataURL('image/jpeg', 0.6); // 可調(diào)整質(zhì)量參數(shù)
console.log('compressedData: ', compressedData);
轉(zhuǎn)化后的 DataURL 結(jié)果如下。
實(shí)現(xiàn)
先給出全部代碼,再進(jìn)行解釋。
html
復(fù)制
<!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('請(qǐng)先選擇要上傳的圖片');
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; // 設(shè)置最大寬度為 800 像素
let width = img.width;
let height = img.height;
// 判斷是否需要縮放
if (width > maxWidth) {
height *= maxWidth / width;
width = maxWidth;
}
// 設(shè)置 canvas 的寬高
canvas.width = width;
canvas.height = height;
// 將圖片繪制到 canvas 上
ctx.drawImage(img, 0, 0, width, height);
// 獲取壓縮后的圖片數(shù)據(jù)
const compressedData = canvas.toDataURL('image/jpeg', 0.7); // 可調(diào)整質(zhì)量參數(shù)
// 創(chuàng)建一個(gè)新的壓縮后的 File 對(duì)象
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('發(fā)生錯(cuò)誤:', error);
});
}
</script>
</body>
</html>
下面分析關(guān)鍵代碼部分。
首先,初始化一個(gè) reader,它是 FileReader 對(duì)象的實(shí)例。reader.readAsDataURL(file)這行代碼將選擇的文件讀取為 Data URI 格式的字符串。執(zhí)行這行代碼時(shí),F(xiàn)ileReader 對(duì)象會(huì)異步讀取文件數(shù)據(jù),讀取完成后觸發(fā) onload 事件,讀取結(jié)果存儲(chǔ)在 FileReader 對(duì)象的 result 屬性中,格式為 Data URI 字符串。
接著,將讀取出來的 Data URI 字符串賦值給 Image 的 src,等待 img 加載完畢后開始對(duì) img 進(jìn)行壓縮,壓縮方法如上文所示。
然后,設(shè)置最大寬度為 800,判斷當(dāng)前圖片寬度是否大于該值,若大于則進(jìn)行縮放計(jì)算,小于則不進(jìn)行等比縮放計(jì)算。最后將計(jì)算出的值使用 drawImage 繪制到 canvas 上面。
現(xiàn)在,將 canvas 轉(zhuǎn)化成 Data URI 字符串,canvas.toDataURL('image/jpeg', 0.7)這行代碼將 canvas 上繪制的圖像數(shù)據(jù)導(dǎo)出為 JPEG 格式的 Data URI 字符串,并設(shè)置圖像質(zhì)量為 0.7。
之后,創(chuàng)建一個(gè)新的壓縮后的 File 對(duì)象,通過將 Data URI 字符串轉(zhuǎn)化為 Blob 對(duì)象來實(shí)現(xiàn)。
最后,使用 FormData 進(jìn)行上傳即可。
壓縮前后體積對(duì)比
我們看一下壓縮前后體積對(duì)比,壓縮前為 550290,壓縮后為 31523,縮小了十幾倍,壓縮效果非常明顯。
總結(jié)
前端實(shí)現(xiàn)圖片壓縮主要借助 canvas 來完成。實(shí)現(xiàn)思路是先使用 canvas 的 drawImage 方法將圖片繪制為 canvas 圖像,再結(jié)合 toDataURL 轉(zhuǎn)化為 DataURL 進(jìn)行存儲(chǔ)圖片鏈接以及壓縮圖像質(zhì)量。在 toDataURL 中可以調(diào)整圖像質(zhì)量,同時(shí),在壓縮圖像時(shí)要注意等寬高縮放,否則會(huì)導(dǎo)致圖像失真。