前端實現壓縮圖片功能

爲何要前端來壓縮圖片

最近在作一個移動端h5上傳圖片的功能,原本這個功能並不複雜,只須要將圖片文件經過axios傳到服務端便可,可是考慮到如今手機設配的拍照功能十分強大,隨便一張照片都能動輒五六兆,而服務端的要求是上傳圖片必須小於兩兆,並且直接傳這麼大圖片,帶寬它也受不了,因此前端進行壓縮圖片就成了一個必要的環節。javascript

壓縮效果


首先介紹下壓縮的大概流程

  1. 經過原生的input標籤拿到要上傳的圖片文件
  2. 將圖片文件轉化成img元素標籤
  3. 在canvas上壓縮繪製該HTMLImageElement
  4. 將canvas繪製的圖像轉成blob文件
  5. 最後將該blob文件傳到服務端
  6. 完成!

接下來看下詳細步驟

考慮到文章和步驟的完整性,因此我會把每一個細節都寫出來,即便有些東西很基礎。前端

1.  使用Input標籤來獲取圖片文件資源

這一步你們應該最熟悉不過了吧,原生input標籤,經過設置type屬性爲file來讓用戶能夠選擇文件,設置accept限制選擇的文件類型,綁定onchange事件,來獲取確認選擇後的文件java

<input type="file" accept="image/*" />複製代碼

點擊控件,觸發焦點,打開文件資源管理器,選中文件並確認後,會觸發change事件,因此能夠在change事件的回調中獲取選中文件,它長這個樣ios


2. 讀取文件轉成img標籤元素

拿到圖片文件後,先將其轉成HTMLImageElement,也就是普通的img標籤,具體要使用FileReader構造函數。web

先new出來一個img和fileReader的實例,經過fileReader的readAsDataURL這個api,來讀取圖片文件,其返回值是一個編碼後的base64的字符串,而後將這個字符串賦值給img的src屬性上,這樣就完成了圖片文件到HTMLImageElement的轉化。json

// 先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元素後,先取出該元素的寬高度,這個寬高度就是實際圖片文件的寬高度。canvas

const { width: originWidth, height: originHeight } = img複製代碼

而後定義一個最大限度的寬高度,若是超過這個限制寬高度,則進行等比例的縮放axios

// 最大尺寸限制
 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繪製到上面api

// 建立畫布
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接受三個入參bash

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)
    }
  })
}複製代碼

/**
 * 壓縮圖片
 *@param img 被壓縮的img對象
 * @param type 壓縮後轉換的文件類型
 * @param mx 觸發壓縮的圖片最大寬度限制
 * @param mh 觸發壓縮的圖片最大高度限制
 */
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))複製代碼


最後

感謝您的閱讀,但願能和您共同進步,覺的有幫助的話,點個贊哦😁😁

相關文章
相關標籤/搜索