前端JS壓縮圖片的原理詳解(附源碼)
前言
相信大家都做過圖片上傳相關(guān)的功能,在圖片上傳的過程中,不知道大家有沒有考慮過文件體積的問題,如果我們直接將原圖片上傳,可以圖片體積比較大,一是上傳速度較慢,二是前端進(jìn)行渲染時(shí)速度也比較慢,比較影響客戶的體驗(yàn)感。所以在不影響清晰度的情況下,前端可以在上傳前對圖片的大小體積進(jìn)行壓縮,壓縮到一個(gè)比較合適的大小進(jìn)行上傳,本文就帶大家一起來看看前端 JS 如何實(shí)現(xiàn)圖片壓縮,有需要的小伙伴抓緊收藏一下吧!
原理(必看)
省流:主要使用 canvas的 drawImage 方法先繪制為 canvas 圖像,再結(jié)合 toDataURL 轉(zhuǎn)化為DataURl 進(jìn)行存儲圖片鏈接。
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);
我們使用第二種進(jìn)行繪制,參數(shù)含義如下:
- image:繪制到上下文的元素。
- dx:
image
的左上角在目標(biāo)畫布上 X 軸坐標(biāo)。 - dy:
image
的左上角在目標(biāo)畫布上 Y 軸坐標(biāo)。 - dWidth:
image
在目標(biāo)畫布上繪制的寬度。允許對繪制的image
進(jìn)行縮放。如果不說明,在繪制時(shí)image
寬度不會縮放。 - dHeight:
image
在目標(biāo)畫布上繪制的高度。允許對繪制的image
進(jìn)行縮放。如果不說明,在繪制時(shí)image
高度不會縮放。
簡單示例
注意:如果隨意的修改圖像的尺寸,會導(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簡單介紹
我們將圖片繪制到 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)
語法: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ù)會被忽略。
簡單示例
// 獲取壓縮后的圖片數(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('請先選擇要上傳的圖片'); 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 對象 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ā)生錯誤:', error); }); } </script> </body> </html>
我們看一下壓縮前后體積對比,壓縮前550290,壓縮后31523,縮小了十幾倍,這個(gè)壓縮還是很明顯的。
首先我們看這三行代碼
我們先初始化一個(gè) reader
是一個(gè) FileReader
對象的實(shí)例
reader.readAsDataURL(file),
這行代碼的作用是將選擇的文件讀取為 Data URI 格式的字符串。
當(dāng)執(zhí)行 reader.readAsDataURL(file)
時(shí),會發(fā)生以下幾件事情:
FileReader
對象開始異步讀取file
中的數(shù)據(jù)。一旦讀取完成,
FileReader
的onload
事件將被觸發(fā)。讀取的結(jié)果將存儲在
FileReader
對象的result
屬性中,格式為 Data URI 字符串。
看一下 FileReader
對象的 result
的打印結(jié)果,為 Data URL格式
最終我們將讀取出來的 Data URI 字符串賦值給 Image 的 src,也就是下面這行代碼,然后等待 img 加載完畢開始對 img 進(jìn)行壓縮,具體怎么壓縮上面已經(jīng)簡單演示過。
const img = new Image() img.src = reader.result img.onload = function () {}
接下來我們設(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)行存儲起來,也可以轉(zhuǎn)化為文件格式進(jìn)行存儲,我這里選擇使用文件格式進(jìn)行存儲,不需要的可以不使用下面的方式。
接下來就是創(chuàng)建一個(gè)新的壓縮后的 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 進(jìn)行上傳即可,這一塊就不說了。
總結(jié)
前端實(shí)現(xiàn)圖片壓縮主要是利用的 canvas 來實(shí)現(xiàn),實(shí)現(xiàn)思路為使用 canvas 的 drawImage 方法先繪制為 canvas 圖像,再結(jié)合 toDataURL 轉(zhuǎn)化為 DataURl 進(jìn)行存儲圖片鏈接以及壓縮圖像質(zhì)量。在toDataURL 中可以調(diào)整圖像質(zhì)量,需要注意的是我們在壓縮圖像時(shí)要注意等寬高縮放,否則會導(dǎo)致圖像出現(xiàn)失真的情況。
到此這篇關(guān)于前端JS壓縮圖片的文章就介紹到這了,更多相關(guān)前端JS壓縮圖片內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用 JavaScript 在沒有插件的情況下輸入文本掩碼的示例詳解
這篇文章主要介紹了使用 JavaScript 在沒有插件的情況下輸入文本掩碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06Bootstrap網(wǎng)頁布局網(wǎng)格的實(shí)現(xiàn)
柵格就是網(wǎng)格,本文詳細(xì)的介紹了Bootstrap網(wǎng)頁布局網(wǎng)格的原理和實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-07-07JS實(shí)現(xiàn)Tab欄切換的兩種方式案例詳解
這篇文章主要介紹了JS實(shí)現(xiàn)Tab欄切換的兩種方式,一種是面向過程的寫法,一種是面向?qū)ο蟮膶懛ǎ疚慕o大家分享詳細(xì)案例代碼,需要的朋友可以參考下2022-08-08js 數(shù)組實(shí)現(xiàn)一個(gè)類似ruby的迭代器
今天突然發(fā)現(xiàn)js的數(shù)組處理起來真是麻煩,代碼一些就是一大堆,相比起ruby的迭代器來真是遜色不少。2009-10-10js的map、flatMap和find、filter的使用詳解
map和flatMap都是返回一個(gè)新數(shù)組,map不會改變數(shù)組長度,flatMap可以改變長度,find和filter都是過濾操作,find只會返回第一個(gè)找到的值,而filter會返回全部符合要求的對象2024-12-12js實(shí)現(xiàn)獲取鼠標(biāo)當(dāng)前的位置
本文主要介紹了利用javascript實(shí)現(xiàn)獲取鼠標(biāo)當(dāng)前的位置的具體方法,具有很好的參考作用,需要的朋友可以看看2016-12-12