圖片壓縮保證讓你看的明明白白

南风晚来晚相识發表於2024-08-10

場景

很多時候,都會遇見圖片上傳的場景。
在上傳給伺服器之前。
前端為了節省伺服器的儲存空間。
會對圖片進行壓縮。
下面我們來一起學習一下圖片壓縮。
圖片壓縮的步驟: 
1.選擇圖片。使用 <input type="file">來實現
2.將選擇的圖片顯示出來。 獲取到圖片的base64,然後進行賦值
3.對圖片進行進行壓縮,如果壓縮後的圖比原圖大,繼續呼叫壓縮函式,直到比原圖小(如小30%)

選擇圖片並把圖片顯示出來

我們在讀取檔案的時候。
需要對圖片的型別進行限制。
目前我們支援 'image/png', 'image/jpeg', 'image/gif', 'image/bmp'這些格式。
然後我們把圖片轉化為base64編碼。
<style>
  #originPic{
    display: none;
  }
</style>

<div>
  <input type="file" id="file" value="請選擇圖片" accept="image/*">
</div>
<div id="originPic">
  <span>原圖:</span>
  <img src="" id="originImg">
</div>

<script>
  let fileNode = document.getElementById('file')
  // 當使用者選擇檔案的時候,就會觸發readFile方法
  fileNode.addEventListener('change', readFile, false)
  // 裝圖片的盒子
  let originPicNode = document.getElementById('originPic')
  // 圖片DOM節點
  let originImg = document.getElementById('originImg')
  // 常見的圖片型別格式
  let canSelectPicTypeArr = ['image/png', 'image/jpeg', 'image/gif', 'image/bmp']
  function readFile(e){
    // 獲取使用者選擇的檔案
    let file = e.target.files[0]
    // 檢查選擇的圖片是否符合要求
    if(canSelectPicTypeArr.includes(file.type)){
      // 建立一個檔案物件
      let reader = new FileReader()
      reader.readAsDataURL(file)
      // 圖片進行讀取完成後,將圖片的src賦值給img標籤
      reader.onload = function(){
        originImg.src = reader.result
        originPicNode.style.display = 'block'
      }
    }else{
      let str = canSelectPicTypeArr.join(',')
      alert('目前只支援圖片格式' + str)
    }
  }
</script>

對圖片進行壓縮

要對圖片進行壓縮。
我們需要建立canvas標籤,img標籤。
將選擇的圖片繪製在canvas上,可以藉助 ctx.drawImage函式來實現
最後透過 canvas.toDataURL來實現壓縮。
canvas.toDataURL(picType, quality)
picType:表示的是圖片型別
quality:表示的是壓縮質量,取值範圍0-1。預設是 0.92。值越小圖壓縮越大。
// 我們現在開始壓縮圖片
function assetImg(originPicInfo){
  // 建立canvas標籤
  let canvasEle = document.createElement('canvas')
  // 建立img標籤
  let imgEle = document.createElement('img')
  // 建立img標籤
  imgEle.src = originPicInfo.originImgSrc
  // canvas的上下文
  let ctx= canvasEle.getContext('2d')
  // 讀取圖片
  imgEle.onload = ()=>{ 
    // 獲取圖片的寬高
    const imgWidth = imgEle.width
    const imgHeight = imgEle.height
    // 將圖片的寬高設定給canvas
    canvasEle.width = imgWidth
    canvasEle.height = imgHeight
    // 把圖片繪製在canvas上
    ctx.drawImage(imgEle, 0,0,imgWidth, imgHeight)
    // 生成壓縮圖片
    const assetPicBase64 = canvasEle.toDataURL(originPicInfo.imgType, 0.7)
    console.log('壓縮後的圖片', assetPicBase64)
  }
}

圖片編碼為base64後為啥還比原圖要大?

要回答上面這個問題,就需要去了解base64的編碼基本原理。
它的基本原理是:採用64個基本的 ASCII 碼字元對資料進行重新編碼。
首先他將原始碼資料拆分位元組陣列。
以3位元組為一組,按順序排列 24 位的資料。
然後再將24位資料分成4組,即每組6位。
在每組前面加兩個00, 湊足一個位元組。
這樣就把一個 3 位元組為一組的資料重新編碼成了 4 個位元組。
[讀到這裡,也許你應該猜到為啥圖片透過base64編碼後比原圖要大的原因了]
當要編碼的資料的位元組數不是3的整倍數,
也就是說在分組時最後一組不 3個位元組時。
會在最後一組填充1到2個0 位元組。
並在最後編碼完成時結尾新增1到2個 "="
我們透過編碼規則可以得知,使用 Base64 編碼。
原來的 3 個位元組編碼後將成為 4 個位元組。
即位元組增加了 33.3%,資料量相應變大。
所以 31KB 的資料透過 Base64 編碼後大小大概為 31M*133.3%=42KM左右。
這一部分的參考相關連結:
https://www.ruanyifeng.com/blog/2008/06/base64.html
https://my.oschina.net/u/1422143/blog/702602

如何處理壓縮後的圖片比原圖小?

我們剛剛知道了為啥base64編碼後圖片的大小比原圖要大。
是因為: 3 個位元組編碼後將成為 4 個位元組。所以比原圖要大了。
現在我們只需要對圖片的大小進行判斷。
如果壓縮後的圖片比大,繼續呼叫壓縮函式。
如果比原圖小,則停止下來。返回壓縮後的圖片的base64格式。

實現圖片壓縮功能

<style>
  #originPic{
    display: none;
  }
  #assetsBox{
    display: none;
  }
</style>

<div>
  <input type="file" id="file" value="請選擇圖片" accept="image/*">
</div>
<div id="originPic">
  <p>原圖:</p>
  <img src="" id="originImg">
</div>
<div id="assetsBox">
  <p>壓縮後的圖:</p>
  <img src="" id="assetsImg">
</div>
// 圖片的型別
let fileType = ''
// 壓縮後的圖片(base64)
let compressImgSrc = ''
// 壓縮質量值
let qualityValue =0.9
imgEle.onload = ()=>{ 
  // ... 其他核心程式碼...
  // 呼叫圖片壓縮
  doPicCompress(canvasEle, originPicInfo.originImgSrc,fileType)
  console.log('壓縮',compressImgSrc, qualityValue)
  // 將壓縮的圖片顯示在頁面上
  if(assetsBox){
    assetsBox.style.display = 'block'
    assetsImg.src = compressImgSrc
  }
}

// 實現圖片壓縮
function doPicCompress(canvas, imgSrc, type){
  // 將圖片轉化為base64
  compressImgSrc = canvas.toDataURL(type, qualityValue)
  // 如果壓縮後的圖片大於原圖,且圖片質量還可以繼續下調,則繼續壓縮
  if(compressImgSrc.length>=imgSrc.length && qualityValue>0.1){
    // 這裡壓縮的核心是下調圖片的質量
    qualityValue = qualityValue - 0.1
    // 繼續壓縮
    doPicCompress(canvas, imgSrc, type)
  }
}


會不會出現多次壓縮後,圖片仍然比原圖要大

有的小夥伴可能會說:
剛剛你說圖片編碼為base64後會比原圖大。
如果是一張比較小的圖片(30KB)左右。
會出現經過多次壓縮後,圖片仍然比原圖要大的這種情況嗎?
其實這一種是可能出現的。
原因的話是:圖片本來就是較小的。編碼base64後會比原圖大。
藉助toDataURL的壓縮,並不一定會比原圖小。
所以,圖片較小,我們這種透過toDataURL進行壓縮的方式就不好了。
當然處理前端進行圖片壓縮之後,後端也可以進行圖片壓縮。
下面我們透過node外掛來簡單看下

使用sharp來進行圖片壓縮

sharp:這個外掛主要用於將常見格式的大影像轉換為更小,
更適合網頁使用的JPEG、PNG、WebP、GIF和AVIF格式的影像。
並且可以調整影像的尺寸。
安裝: npm install sharp -S
具體使用的地址:https://www.npmjs.com/package/shap
// 引入依賴
const sharp = require('sharp');
// 傳入圖片路徑為./js.png,進行壓縮,然後輸出新的yaSuoJS.png儲存
sharp('./js.png').png({ quality: 50 }).toFile('yaSuoJS.png', (err, info) => {
  if (err) throw err;
  if(info){
    console.log(info);
  }
 });

全部程式碼

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #originPic{
      display: none;
    }
    #assetsBox{
      display: none;
    }
  </style>
</head>
<body>
  <div>
    <input type="file" id="file" value="請選擇圖片" accept="image/*">
  </div>
  <div id="originPic">
    <p>原圖:</p>
    <img src="" id="originImg">
  </div>

  <div id="assetsBox">
    <p>壓縮後的圖:</p>
    <img src="" id="assetsImg">
  </div>
</body>
</html>
<script>
  let fileNode = document.getElementById('file')
  // 當使用者選擇檔案的時候,就會觸發readFile方法
  fileNode.addEventListener('change', readFile, false)
  // 裝圖片的盒子
  let originPicNode = document.getElementById('originPic')
  // 圖片DOM節點
  let originImg = document.getElementById('originImg')
  // 壓縮後裝圖片的DOM節點
  let assetsBox = document.getElementById('assetsBox')
  //  壓縮後圖片的DOM節點
  let assetsImg = document.getElementById('assetsImg')
  // 常見的圖片型別格式
  let canSelectPicTypeArr = ['image/png', 'image/jpeg', 'image/gif', 'image/bmp']
  // 圖片的型別
  let fileType = ''
  // 壓縮後的圖片(base64)
  let compressImgSrc = ''
  // 壓縮質量值
  let qualityValue =0.2
  function readFile(e){
    // 獲取使用者選擇的檔案
    let file = e.target.files[0]
    fileType = file.type
    // 檢查是否選擇了符合要求的圖片
    if(canSelectPicTypeArr.includes(fileType)){
      // 建立一個檔案物件
      let reader = new FileReader()
      reader.readAsDataURL(file)
      reader.onload = function(){
        originImg.src = reader.result
        console.log(reader.result, '壓縮後的大小', )
        originPicNode.style.display = 'block'
        // 呼叫圖片壓縮這個函式
        assetImg({originImgSrc: originImg.src, quality:0.9, imgType: fileType})
      }
    }else{
      let str = canSelectPicTypeArr.join(',')
      alert('目前只支援圖片格式' + str)
    }
  }

  // 我們現在開始壓縮圖片
  function assetImg(originPicInfo){
    // 建立canvas標籤
    let canvasEle = document.createElement('canvas')
    // 建立img標籤
    let imgEle = document.createElement('img')
    imgEle.src = originPicInfo.originImgSrc
    let ctx= canvasEle.getContext('2d')
    // 讀取圖片
    imgEle.onload = ()=>{ 
      // 獲取圖片的寬高
      const imgWidth = imgEle.width
      const imgHeight = imgEle.height
      // 將圖片的寬高設定給canvas
      canvasEle.width = imgWidth
      canvasEle.height = imgHeight
      // 把圖片繪製在canvas上
      ctx.drawImage(imgEle, 0,0,imgWidth, imgHeight)
      // 呼叫圖片壓縮
      doPicCompress(canvasEle, originPicInfo.originImgSrc,fileType)
      console.log(compressImgSrc, qualityValue, '壓縮之後')
      // 將壓縮的圖片顯示在頁面上
      if(assetsBox){
        assetsBox.style.display = 'block'
        assetsImg.src = compressImgSrc
      }
    }
  }

  // 實現圖片壓縮
  function doPicCompress(canvas, imgSrc, type){
    // 將圖片轉化為base64
    compressImgSrc = canvas.toDataURL(type, qualityValue)
    // 如果壓縮後的圖片大於原圖,且圖片質量還可以繼續下調,則繼續壓縮
    if(compressImgSrc.length>=imgSrc.length && qualityValue>0.1){
      // 這裡壓縮的核心是下調圖片的質量
      qualityValue = qualityValue - 0.1
      // 繼續壓縮
      doPicCompress(canvas, imgSrc, type)
    }
  }
</script>

尾聲

如果小夥伴覺得我寫的不錯的話,
可以給我點個贊嗎?感謝了。
不說了,今天又是修改bug的一天

相關文章