前端JS壓縮圖片的原理詳解(附源碼)
前言
相信大家都做過(guò)圖片上傳相關(guān)的功能,在圖片上傳的過(guò)程中,不知道大家有沒(méi)有考慮過(guò)文件體積的問(wèn)題,如果我們直接將原圖片上傳,可以圖片體積比較大,一是上傳速度較慢,二是前端進(jìn)行渲染時(shí)速度也比較慢,比較影響客戶的體驗(yàn)感。所以在不影響清晰度的情況下,前端可以在上傳前對(duì)圖片的大小體積進(jìn)行壓縮,壓縮到一個(gè)比較合適的大小進(jìn)行上傳,本文就帶大家一起來(lái)看看前端 JS 如何實(shí)現(xiàn)圖片壓縮,有需要的小伙伴抓緊收藏一下吧!
原理(必看)
省流:主要使用 canvas的 drawImage 方法先繪制為 canvas 圖像,再結(jié)合 toDataURL 轉(zhuǎn)化為DataURl 進(jìn)行存儲(chǔ)圖片鏈接。
drawImage簡(jiǎn)單介紹
Canvas 2D API 中的 CanvasRenderingContext2D.drawImage() 方法提供了多種在畫布Canvas)上繪制圖像的方式。
用法如下:
CanvasRenderingContext2D.drawImage() - Web API 接口參考 | MDN (mozilla.org)
語(yǔ)法如下:
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)行縮放。如果不說(shuō)明,在繪制時(shí)image寬度不會(huì)縮放。 - dHeight:
image在目標(biāo)畫布上繪制的高度。允許對(duì)繪制的image進(jìn)行縮放。如果不說(shuō)明,在繪制時(shí)image高度不會(huì)縮放。
簡(jiǎn)單示例
注意:如果隨意的修改圖像的尺寸,會(huì)導(dǎo)致圖像失真,我們可以先獲取到圖像資源的原始尺寸,然后進(jìn)行等比縮放,意思就是當(dāng)我們確定設(shè)置寬度之后,高度要進(jìn)行等比調(diào)整。公式就是交叉相乘積相等。
// 如果寬度設(shè)置為 500, 那么高度也應(yīng)該進(jìn)行等比縮放
// naturalWidth => 500
// naturalHeight => X
// naturalWidth * X = naturalHeight * 500// 計(jì)算得出高度
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 () {
// 注意:圖像繪制時(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 之后可以顯示到我們的屏幕上面,也可以存放到后端服務(wù)器,使用 canvas 所提供的 toDataURL 實(shí)例方法即可。
? 官方解釋:HTMLCanvasElement.toDataURL() 方法返回一個(gè)包含圖片展示的 data URI ?
HTMLCanvasElement.toDataURL() - Web API 接口參考 | MDN (mozilla.org)
語(yǔ)法:canvas.toDataURL(type, encoderOptions);
type(可選):圖片格式,默認(rèn)為 image/png
encoderOptions(可選):在指定圖片格式為 image/jpeg 或 image/webp 的情況下,可以從 0 到 1 的區(qū)間內(nèi)選擇圖片的質(zhì)量。如果超出取值范圍,將會(huì)使用默認(rèn)值 0.92。其他參數(shù)會(huì)被忽略。
簡(jiǎn)單示例
// 獲取壓縮后的圖片數(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)行解釋!
<!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>我們看一下壓縮前后體積對(duì)比,壓縮前550290,壓縮后31523,縮小了十幾倍,這個(gè)壓縮還是很明顯的。

首先我們看這三行代碼

我們先初始化一個(gè) reader 是一個(gè) FileReader 對(duì)象的實(shí)例
reader.readAsDataURL(file),這行代碼的作用是將選擇的文件讀取為 Data URI 格式的字符串。
當(dāng)執(zhí)行 reader.readAsDataURL(file) 時(shí),會(huì)發(fā)生以下幾件事情:
FileReader對(duì)象開(kāi)始異步讀取file中的數(shù)據(jù)。一旦讀取完成,
FileReader的onload事件將被觸發(fā)。讀取的結(jié)果將存儲(chǔ)在
FileReader對(duì)象的result屬性中,格式為 Data URI 字符串。
看一下 FileReader 對(duì)象的 result 的打印結(jié)果,為 Data URL格式

最終我們將讀取出來(lái)的 Data URI 字符串賦值給 Image 的 src,也就是下面這行代碼,然后等待 img 加載完畢開(kāi)始對(duì) img 進(jìn)行壓縮,具體怎么壓縮上面已經(jīng)簡(jiǎn)單演示過(guò)。
const img = new Image()
img.src = reader.result
img.onload = function () {}接下來(lái)我們?cè)O(shè)置了一個(gè)最大寬度為800,然后判斷當(dāng)前圖片寬度是否大于該值,如果大于進(jìn)行縮放計(jì)算,小于就不進(jìn)行等比縮放計(jì)算。最后將計(jì)算出的值使用 drawImage 繪制到 canvas 上面。
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);
現(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。
// 獲取壓縮后的圖片數(shù)據(jù)
const compressedData = canvas.toDataURL('image/jpeg', 0.7); // 可調(diào)整我們看一下 compressedData,是一個(gè)Data URI 字符串,其實(shí)到這里就可以了,我們可以將 Data URI 傳到后端進(jìn)行存儲(chǔ)起來(lái),也可以轉(zhuǎn)化為文件格式進(jìn)行存儲(chǔ),我這里選擇使用文件格式進(jìn)行存儲(chǔ),不需要的可以不使用下面的方式。

接下來(lái)就是創(chuàng)建一個(gè)新的壓縮后的 File 對(duì)象
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 })
}新的文件對(duì)象

最后直接使用 FormData 進(jìn)行上傳即可,這一塊就不說(shuō)了。
總結(jié)
前端實(shí)現(xiàn)圖片壓縮主要是利用的 canvas 來(lái)實(shí)現(xiàn),實(shí)現(xiàn)思路為使用 canvas 的 drawImage 方法先繪制為 canvas 圖像,再結(jié)合 toDataURL 轉(zhuǎn)化為 DataURl 進(jìn)行存儲(chǔ)圖片鏈接以及壓縮圖像質(zhì)量。在toDataURL 中可以調(diào)整圖像質(zhì)量,需要注意的是我們?cè)趬嚎s圖像時(shí)要注意等寬高縮放,否則會(huì)導(dǎo)致圖像出現(xiàn)失真的情況。
到此這篇關(guān)于前端JS壓縮圖片的文章就介紹到這了,更多相關(guān)前端JS壓縮圖片內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
利用JS實(shí)現(xiàn)點(diǎn)擊按鈕后圖片自動(dòng)切換的簡(jiǎn)單方法
下面小編就為大家?guī)?lái)一篇利用JS實(shí)現(xiàn)點(diǎn)擊按鈕后圖片自動(dòng)切換的簡(jiǎn)單方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10
cocos2dx骨骼動(dòng)畫Armature源碼剖析(二)
本篇主要給大家介紹cocos2dx骨骼動(dòng)畫Armature源碼剖析之flash中數(shù)據(jù)與xml中數(shù)據(jù)關(guān)系,需要的朋友一起來(lái)學(xué)習(xí)吧2015-09-09
JavaScript 浮點(diǎn)數(shù)運(yùn)算 精度問(wèn)題
JavaScript小數(shù)在做四則運(yùn)算時(shí),精度會(huì)丟失,這會(huì)在項(xiàng)目中引起諸多不便,先請(qǐng)看下面腳本。2009-10-10
微信小程序數(shù)據(jù)分析之自定義分析的實(shí)現(xiàn)
這篇文章主要介紹了微信小程序數(shù)據(jù)分析之自定義分析的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
返回頁(yè)面頂部top按鈕通過(guò)錨點(diǎn)實(shí)現(xiàn)(自寫)
用戶在使用系統(tǒng)時(shí),會(huì)有很多表單的操作然而很多表單就會(huì)很長(zhǎng),所以就需要一個(gè)返回頁(yè)面頂部的top按鈕啦,于是自己寫了一個(gè),喜歡的朋友可以參考下2013-08-08
使用bootstrap莫名其妙出現(xiàn)橫向滾動(dòng)條的問(wèn)題及解決
這篇文章主要介紹了使用bootstrap莫名其妙出現(xiàn)橫向滾動(dòng)條的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11

