JS前端圖片最優(yōu)化壓縮方案
前言
上傳圖片/視頻/文件是我們經(jīng)常會遇到的問題,但是一旦圖片過大就會導(dǎo)致不好的操作體驗。 圖片上傳是前端中常見的的業(yè)務(wù)場景。無論是前臺還是后臺,適當(dāng)?shù)膶D片進(jìn)行壓縮處理, 可以顯著的提升用戶體驗。
而在后臺管理系統(tǒng)中,圖片壓縮不僅僅能夠提升后臺管理員操作體驗,更是可以防止后臺設(shè)置過大的圖片導(dǎo)致前臺圖片加載過久,從而影響用戶體驗。
壓縮圖片思考
壓縮圖片基本流程
- input 讀取到 文件 ,使用 FileReader 將其轉(zhuǎn)換為 base64 編碼
- 新建 img ,使其 src 指向剛剛的 base64
- 新建 canvas ,將 img 畫到 canvas 上
- 利用 canvas.toDataURL/toBlob 將 canvas 導(dǎo)出為 base64 或 Blob
- 將 base64 或 Blob 轉(zhuǎn)化為 File 將這些步驟逐個拆解,我們會發(fā)現(xiàn)似乎在canvas.toDataURL時涉及到圖片質(zhì)量,那咱們就從這里下手。
準(zhǔn)備
HTMLCanvasElement.toDataURL()
HTMLCanvasElement.toDataURL() 方法返回一個包含圖片展示的 data URI 。可以使用 type 參數(shù)其類型,默認(rèn)為 PNG 格式。圖片的分辨率為96dpi。
- 如果畫布的高度或?qū)挾仁?,那么會返回字符串“data:,”。
- 如果傳入的類型非“image/png”,但是返回的值以“data:image/png”開頭,那么該傳入的類型是不支持的。
語法
canvas.toDataURL(type, encoderOptions);
參數(shù)
- type 可選 圖片格式,默認(rèn)為 image/png
- encoderOptions 可選 在指定圖片格式為 image/jpeg 或 image/webp的情況下,可以從 0 到 1 的區(qū)間內(nèi)選擇圖片的質(zhì)量。如果超出取值范圍,將會使用默認(rèn)值 0.92。其他參數(shù)會被忽略。 Chrome支持“image/webp”類型。
猜想
可能toDataURL(type,quality)的第二個參數(shù)(quality)越小,文件體積越小
實踐
先看下原圖信息大小是7.31kb

toDataURL(type,quality)quality默認(rèn)0.92看看壓縮結(jié)果如何
<input id="fileInput" type="file" /> <img id="img" src="" alt="">
let fileId = document.getElementById('fileInput')
let img = document.getElementById('img')
fileId.onchange = function (e) {
let file = e.target.files[0]
compressImg(file, 0.92).then(res => {//compressImg方法見附錄
console.log(res)
img.src = window.URL.createObjectURL(res.file);
})
}
compressImg方法見附錄

可以看到圖片大小為8.83kb壓縮后圖片反而變大了,這是怎么回事?
看來起初的猜想不完全正確,咱們繼續(xù)看看。
fileId.onchange = function (e) {
let file = e.target.files[0]
compressImg(file, 0.1).then(res => {//compressImg方法見附錄
console.log(res)
img.src = window.URL.createObjectURL(res.file);
})
}

當(dāng)quality為0.1的時候,圖片僅有1.63kb,同樣的質(zhì)量也下降了
繼續(xù)......A long time later
咱們用折線圖來看吧更直觀

我又拿了幾個圖片讓他們使用默認(rèn)值0.92,結(jié)果都比原圖大
所以說默認(rèn)值得到的圖片往往比原圖大
下面看看當(dāng)quality為多少時對圖片的壓縮效率可以最大化
壓縮效率最大化,即:在不影響圖片質(zhì)量的情況下最大化壓縮 嘗試了一系列的圖片之后我發(fā)現(xiàn) 當(dāng)quality在0.2~0.5之間,圖片質(zhì)量變化并不大,quality的值越小,壓縮效率越可觀(也就是在0.2左右時,壓縮圖片可以最大化,同時并不對圖片質(zhì)量造成太大影響)
結(jié)論
經(jīng)過實踐,可以得出結(jié)論這個默認(rèn)值得到的圖片往往比原圖的圖片質(zhì)量要高。
當(dāng)quality在0.2~0.5之間,圖片質(zhì)量變化并不大,quality的值越小,壓縮效率越可觀(也就是在0.2左右時,壓縮圖片可以最大化,同時并不對圖片質(zhì)量造成太大影響)
附錄
/**
* 壓縮方法
* @param {string} file 文件
* @param {Number} quality 0~1之間
*/
function compressImg(file, quality) {
if (file[0]) {
return Promise.all(Array.from(file).map(e => compressImg(e,
quality))) // 如果是 file 數(shù)組返回 Promise 數(shù)組
} else {
return new Promise((resolve) => {
const reader = new FileReader() // 創(chuàng)建 FileReader
reader.onload = ({
target: {
result: src
}
}) => {
const image = new Image() // 創(chuàng)建 img 元素
image.onload = async () => {
const canvas = document.createElement('canvas') // 創(chuàng)建 canvas 元素
canvas.width = image.width
canvas.height = image.height
canvas.getContext('2d').drawImage(image, 0, 0, image.width, image.height) // 繪制 canvas
const canvasURL = canvas.toDataURL('image/jpeg', quality)
const buffer = atob(canvasURL.split(',')[1])
let length = buffer.length
const bufferArray = new Uint8Array(new ArrayBuffer(length))
while (length--) {
bufferArray[length] = buffer.charCodeAt(length)
}
const miniFile = new File([bufferArray], file.name, {
type: 'image/jpeg'
})
resolve({
file: miniFile,
origin: file,
beforeSrc: src,
afterSrc: canvasURL,
beforeKB: Number((file.size / 1024).toFixed(2)),
afterKB: Number((miniFile.size / 1024).toFixed(2))
})
}
image.src = src
}
reader.readAsDataURL(file)
})
}
}
優(yōu)化升級
使用過程中發(fā)現(xiàn)邊界問題,即圖片尺寸過大、 IOS 尺寸限制,png 透明圖變黑等問題
所以優(yōu)化了大尺寸圖片等比例縮小,這極大的提高了壓縮效率
舉個栗子,以一張大小14M,尺寸6016X4016的圖片為例

一個14M的原圖(6016X4016)不改變質(zhì)量只改變尺寸的前提下壓縮后(1400X935)就剩139.62KB
可想而知,尺寸的限制能最大化提高壓縮效率

/**
* 壓縮圖片方法
* @param {file} file 文件
* @param {Number} quality 圖片質(zhì)量(取值0-1之間默認(rèn)0.92)
*/
compressImg(file, quality) {
var qualitys = 0.52
console.log(parseInt((file.size / 1024).toFixed(2)))
if (parseInt((file.size / 1024).toFixed(2)) < 1024) {
qualitys = 0.85
}
if (5 * 1024 < parseInt((file.size / 1024).toFixed(2))) {
qualitys = 0.92
}
if (quality) {
qualitys = quality
}
if (file[0]) {
return Promise.all(Array.from(file).map(e => this.compressImg(e,
qualitys))) // 如果是 file 數(shù)組返回 Promise 數(shù)組
} else {
return new Promise((resolve) => {
console.log(file)
if ((file.size / 1024).toFixed(2) < 300) {
resolve({
file: file
})
} else {
const reader = new FileReader() // 創(chuàng)建 FileReader
reader.onload = ({
target: {
result: src
}
}) => {
const image = new Image() // 創(chuàng)建 img 元素
image.onload = async() => {
const canvas = document.createElement('canvas') // 創(chuàng)建 canvas 元素
const context = canvas.getContext('2d')
var targetWidth = image.width
var targetHeight = image.height
var originWidth = image.width
var originHeight = image.height
if (1 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 10 * 1024) {
var maxWidth = 1600
var maxHeight = 1600
targetWidth = originWidth
targetHeight = originHeight
// 圖片尺寸超過的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更寬,按照寬度限定尺寸
targetWidth = maxWidth
targetHeight = Math.round(maxWidth * (originHeight / originWidth))
} else {
targetHeight = maxHeight
targetWidth = Math.round(maxHeight * (originWidth / originHeight))
}
}
}
if (10 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 20 * 1024) {
maxWidth = 1400
maxHeight = 1400
targetWidth = originWidth
targetHeight = originHeight
// 圖片尺寸超過的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更寬,按照寬度限定尺寸
targetWidth = maxWidth
targetHeight = Math.round(maxWidth * (originHeight / originWidth))
} else {
targetHeight = maxHeight
targetWidth = Math.round(maxHeight * (originWidth / originHeight))
}
}
}
canvas.width = targetWidth
canvas.height = targetHeight
context.clearRect(0, 0, targetWidth, targetHeight)
context.drawImage(image, 0, 0, targetWidth, targetHeight) // 繪制 canvas
const canvasURL = canvas.toDataURL('image/jpeg', qualitys)
const buffer = atob(canvasURL.split(',')[1])
let length = buffer.length
const bufferArray = new Uint8Array(new ArrayBuffer(length))
while (length--) {
bufferArray[length] = buffer.charCodeAt(length)
}
const miniFile = new File([bufferArray], file.name, {
type: 'image/jpeg'
})
console.log({
file: miniFile,
origin: file,
beforeSrc: src,
afterSrc: canvasURL,
beforeKB: Number((file.size / 1024).toFixed(2)),
afterKB: Number((miniFile.size / 1024).toFixed(2)),
qualitys: qualitys
})
resolve({
file: miniFile,
origin: file,
beforeSrc: src,
afterSrc: canvasURL,
beforeKB: Number((file.size / 1024).toFixed(2)),
afterKB: Number((miniFile.size / 1024).toFixed(2))
})
}
image.src = src
}
reader.readAsDataURL(file)
}
})
}
},
優(yōu)化升級 TS新寫法
最近很多小伙伴在問我有沒有TS版本的,今天剛好弄了下這一塊,稍微調(diào)整了下,相較于之前更加簡潔,性能也快了不少;
如果需要對尺寸壓縮,請自行添加尺寸處理方法,也很簡單,自行添加吧,方法寫的很簡潔明了了,就不多加贅述了,大家自取吧。
const fileToDataURL = (file: Blob): Promise<any> => {
return new Promise((resolve) => {
const reader = new FileReader()
reader.onloadend = (e) => resolve((e.target as FileReader).result)
reader.readAsDataURL(file)
})
}
const dataURLToImage = (dataURL: string): Promise<HTMLImageElement> => {
return new Promise((resolve) => {
const img = new Image()
img.onload = () => resolve(img)
img.src = dataURL
})
}
const canvastoFile = (canvas: HTMLCanvasElement, type: string, quality: number): Promise<Blob | null> => {
return new Promise((resolve) => canvas.toBlob((blob) => resolve(blob), type, quality))
}
/**
* 圖片壓縮方法
* @param {Object} file 圖片文件
* @param {String} type 想壓縮成的文件類型
* @param {Nubmber} quality 壓縮質(zhì)量參數(shù)
* @returns 壓縮后的新圖片
*/
export const compressionFile = async(file, type = 'image/jpeg', quality = 0.5) => {
const fileName = file.name
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d') as CanvasRenderingContext2D
const base64 = await fileToDataURL(file)
const img = await dataURLToImage(base64)
canvas.width = img.width
canvas.height = img.height
context.clearRect(0, 0, img.width, img.height)
context.drawImage(img, 0, 0, img.width, img.height)
const blob = (await canvastoFile(canvas, type, quality)) as Blob // quality:0.5可根據(jù)實際情況計算
const newFile = await new File([blob], fileName, {
type: type
})
return newFile
}
以上就是JS前端圖片最優(yōu)化壓縮方案的詳細(xì)內(nèi)容,更多關(guān)于JS前端圖片壓縮的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
bootstrap confirmation按鈕提示組件使用詳解
這篇文章主要為大家詳細(xì)介紹了bootstrap confirmation按鈕提示組件的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08
關(guān)于Javascript作用域鏈的八點總結(jié)
其實吧,關(guān)于作用域鏈相關(guān)的文章我也看了不少,但是我一直也沒能做一個詳細(xì)的總結(jié),今天把我看到的一些東西,結(jié)合自己的想法,總結(jié)成以下8個點2013-12-12
js實現(xiàn)界面向原生界面發(fā)消息并跳轉(zhuǎn)功能
這篇文章主要為大家詳細(xì)介紹了js實現(xiàn)界面向原生界面發(fā)消息并跳轉(zhuǎn)功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11
Ajax+FormData+javascript實現(xiàn)無刷新表單信息提交
在前端開發(fā)中ajax,formdata和js實現(xiàn)無刷新表單信息提交非常棒,接下來通過本文給大家介紹Ajax+FormData+javascript實現(xiàn)無刷新表單信息提交的相關(guān)資料,需要的朋友可以參考下2016-10-10
基于百度地圖實現(xiàn)產(chǎn)品銷售的單位位置查看功能設(shè)計與實現(xiàn)
這篇文章主要介紹了基于百度地圖實現(xiàn)產(chǎn)品銷售的單位位置查看功能設(shè)計與實現(xiàn)的相關(guān)資料,非常不錯具有參考借鑒價值,需要的朋友可以參考下2016-10-10

