為什麼要前端來壓縮圖檔
最近在做一個移動端h5上傳圖檔的功能,本來這個功能并不複雜,隻需要将圖檔檔案通過axios傳到服務端即可,但是考慮到現在手機設配的拍照功能十分強大,随便一張照片都能動辄五六兆,而服務端的要求是上傳圖檔必須小于兩兆,而且直接傳這麼大圖檔,帶寬它也受不了,是以前端進行壓縮圖檔就成了一個必要的環節。
壓縮效果
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5CM0EDO0UWY2MDOjFzMwQWOzYTO3ADN5YmM4EjNklTN28CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
首先介紹下壓縮的大概流程
通過原生的input标簽拿到要上傳的圖檔檔案
将圖檔檔案轉化成img元素标簽
在canvas上壓縮繪制該HTMLImageElement
将canvas繪制的圖像轉成blob檔案
最後将該blob檔案傳到服務端
完成!
接下來看下詳細步驟
考慮到文章和步驟的完整性,是以我會把每個細節都寫出來,即使有些東西很基礎。
1. 使用Input标簽來擷取圖檔檔案資源
這一步大家應該最熟悉不過了吧,原生input标簽,通過設定 type 屬性為file來讓使用者可以選擇檔案,設定 accept限制選擇的檔案類型,綁定onchange事件,來擷取确認選擇後的檔案
點選控件,觸發焦點,打開檔案資料總管,選中檔案并确認後,會觸發change事件,是以可以在change事件的回調中擷取選中檔案,它長這個樣
2. 讀取檔案轉成img标簽元素
拿到圖檔檔案後,先将其轉成HTMLImageElement,也就是普通的img标簽,具體要使用 FileReader構造函數。
先new出來一個img和fileReader的執行個體,通過fileReader的 readAsDataURL這個api,來讀取圖檔檔案,其傳回值是一個編碼後的base64的字元串,然後将這個字元串指派給img的src屬性上,這樣就完成了圖檔檔案到 HTMLImageElement的轉化。
// 先new一個img和fileReader的執行個體
const img = new Image()
const reader = new FileReader()// 讀取檔案資源
reader.readAsDataURL(file)
reader.onload = function(e){
img.src = e.target.result
}
轉化的HTMLImageElement
3. canvas壓縮,核心步驟
拿到轉化後的img元素後,先取出該元素的寬高度,這個寬高度就是實際圖檔檔案的寬高度。
const { width: originWidth, height: originHeight } = img
然後定義一個最大限度的寬高度,如果超過這個限制寬高度,則進行等比例的縮放
// 最大尺寸限制
const maxWidth = 1000,maxHeihgt = 1000
// 需要壓縮的目标尺寸
let targetWidth = originWidth, targetHeight = originHeight
// 等比例計算超過最大限制時縮放後的圖檔尺寸
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > 1) {
// 寬圖檔
targetWidth = maxWidth
targetHeight = Math.round(maxWidth * (originHeight / originWidth))
} else {
// 高圖檔
targetHeight = maxHeight
targetWidth = Math.round(maxHeight * (originWidth / originHeight))
}
}
計算好将要壓縮的尺寸後,建立canvas執行個體,設定canvas的寬高度為壓縮計算後的尺寸,并将img繪制到上面
// 建立畫布
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
// 設定寬高度為等同于要壓縮圖檔的尺寸
canvas.width = targetWidth
canvas.height = targetHeight
context.clearRect(0, 0, targetWidth, targetHeight)
//将img繪制到畫布上
context.drawImage(img, 0, 0, targetWidth, targetHeight)
4. 轉成blob檔案
canvas繪制完成後,就可以使用 toBlob來将圖像轉成blob檔案了,這個api接受三個入參
canvas.toBlob(callback, type, encoderOptions);
回調函數中可以得到轉化後的blob檔案,type為要轉成的圖檔類型,預設png。
encoderOptions為當設定的圖檔格式為 image/jpeg 或者 image/webp 時用來指定圖檔展示品質。
是以如果我們隻是要壓縮jpg或者webp格式的圖檔的話,不需要進行第3部的操作,直接使用這個api,然後填入想要的品質參數就可以了。但實際上,我們還是要考慮多種的圖檔格式,是以很有必要使用第三部的過程。
轉成的blob長這個樣子
5. 将blob上傳,大功告成。
完整的代碼實作
因為整個過程中都存在着異步回調操作,是以我使用了async,實作異步代碼的同步執行
// 壓縮前将file轉換成img對象
function readImg(file) {
return new Promise((resolve, reject) => {
const img = new Image()
const reader = new FileReader()
reader.onload = function(e) {
img.src = e.target.result
}
reader.onerror = function(e) {
reject(e)
}
reader.readAsDataURL(file)
img.onload = function() {
resolve(img)
}
img.onerror = function(e) {
reject(e)
}
})
}
function compressImg(img, type, mx, mh) {
return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
const { width: originWidth, height: originHeight } = img
// 最大尺寸限制
const maxWidth = mx
const maxHeight = mh
// 目标尺寸
let targetWidth = originWidth
let targetHeight = originHeight
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > 1) {
// 寬圖檔
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(img, 0, 0, targetWidth, targetHeight)
canvas.toBlob(function(blob) {
resolve(blob)
}, type || 'image/png') })
}
大緻執行過程,具體可根據需求,自行改動
async function upload(file){
const img = await readImg(file)
const blob = await compressImg(img, file.type, 1000, 1000)
const formData = new FormData()
formData.append('file', blob, 'xxx.jpg')
axios.post('http://xxx.com/api',formData)
}
upload(file).catch(e => console.log(e))
到此這篇關于JavaScript前端實作壓縮圖檔功能的文章就介紹到這了,更多相關JavaScript 壓縮圖檔内容請搜尋腳本之家以前的文章或繼續浏覽下面的相關文章希望大家以後多多支援腳本之家!