你知道前端對圖片的處理方式嗎?

付出發表於2019-02-26

前言

作為前端工程師 de 我們,日常少不了會跟圖片打交道。在各大電商平臺工作的前端工程師們,感受可能會更加的明顯。

以下是我之前跟圖片打交道踩到的坑,跟大家分享一下經驗。

一、情景再現

用 Postman 請求介面的時候,返回的是這個圖片(二進位制)

postman

在 chrome 的 network 檢視的時候,返回的也是這個圖片(二進位制)

network

可是,在 debug 列印的時候,返回的卻是亂碼

debug

很明顯,資料的型別已經被改動了。思考原因,唯一有可能改變資料型別的地方是在 axios 。

我去翻看了一下 axios 的文件,裡面是這樣描述的

// `responseType` indicates the type of data that the server will respond with
// options are 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
responseType: 'json', // default
複製程式碼

因此,亂碼出現的原因是因為:axios 預設返回的是 json 文字形式,二進位制圖片資料被強制轉換成了 json 文字形式。

找到了原因,解決方案就好辦了。我們在 axios 裡面,responseType 預設返回資料型別是 json,將其改為返回資料型別 blob。

export function miniprogramQrcode (params) {
  return axios.post(
    env.MI_URL + '/XXXX/XXX/XXXX',
    params,
    // 將responseType的預設json改為blob
    {
    responseType: 'blob',
    emulateJSON: true
  }).then(res => {
    if (res.data) {
      return Promise.resolve(res.data)
    } else {
      throw res
    }
  }).catch(err => {
    return Promise.reject(err)
  })
}
複製程式碼

接下來的問題是,如何處理blob物件,將其顯示在前端頁面呢?

程式碼如下:

createMiniQrcode (blob) {
  let img = document.createElement('img')
  img.onload = function (e) {
    // 元素的onload 事件觸發後將銷燬URL物件, 釋放記憶體。
    window.URL.revokeObjectURL(img.src)
  }
  // 瀏覽器允許使用URL.createObjectURL()方法,針對 Blob 物件生成一個臨時 URL。
  // 這個 URL 以blob://開頭,表明對應一個 Blob 物件。
  img.src = window.URL.createObjectURL(blob)
  document.querySelector('.imgQrCode').appendChild(img)
}
複製程式碼

是不是以為就這樣結束了? No, No, No. 瞭解如何解決問題還不夠,還需要透過表象進行發散思考。

二、 發散思考

一般來說,圖片在後端的儲存方式分為兩種:

其一:可以將圖片以獨立檔案的形式儲存在伺服器的指定資料夾中,再將路徑存入資料庫欄位中;
其二:將圖片轉換成二進位制流,直接儲存到資料庫的 Image 型別欄位中.
複製程式碼

對於第一種儲存方式,我們前端直接將儲存路徑賦值給 src 屬性即可輕鬆顯示。

對於第二種儲存方式,我們前端需要將其二進位制流交由 blob 物件處理,然後通過 blob 的 API 生成臨時 URL 賦值給 src 屬性來顯示。

兩種儲存方式都有對應的解決方案,似乎已經完美解決了關於圖片顯示的問題。但是,我們的業務場景是多樣且多變的。有時候我們也會遇到這樣的場景,比如圖片拖拽上傳外掛後,自動返回給你了 Blob 物件,但不幸的是,你發現你又用了一個第三方的服務介面只接收 base64 格式的資料,是否有點欲哭無淚?

那麼,圖片的三種表現形式url、base64、blob,三者之間是否可以轉化以滿足需求呢?答案是可以的。如下:

流程圖

1. url 轉 base64

url to base64 的方法封裝

// 原理: 利用canvas.toDataURL的API轉化成base64

urlToBase64(url) {
  return new Promise ((resolve,reject) => {
      let image = new Image();
      image.onload = function() {
        let canvas = document.createElement('canvas');
        canvas.width = this.naturalWidth;
        canvas.height = this.naturalHeight;
        // 將圖片插入畫布並開始繪製
        canvas.getContext('2d').drawImage(image, 0, 0);
        // result
        let result = canvas.toDataURL('image/png')
        resolve(result);
      };
      // CORS 策略,會存在跨域問題https://stackoverflow.com/questions/20424279/canvas-todataurl-securityerror
      image.setAttribute("crossOrigin",'Anonymous');
      image.src = url;
      // 圖片載入失敗的錯誤處理
      image.onerror = () => {
        reject(new Error('圖片流異常'));
    };
}
複製程式碼

你可以這樣呼叫:

let imgUrL = `http://XXX.jpg`

this.getDataUri(imgUrL).then(res => {
  // 轉化後的base64圖片地址
  console.log('base64', res)
})
複製程式碼

2. base64 轉 blob

base64 to blob 的方法封裝

// 原理:利用URL.createObjectURL為blob物件建立臨時的URL

base64ToBlob ({b64data = '', contentType = '', sliceSize = 512} = {}) {
    return new Promise((resolve, reject) => {
      // 使用 atob() 方法將資料解碼
      let byteCharacters = atob(b64data);
      let byteArrays = [];
      for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
        let slice = byteCharacters.slice(offset, offset + sliceSize);
        let byteNumbers = [];
        for (let i = 0; i < slice.length; i++) {
            byteNumbers.push(slice.charCodeAt(i));
        }
        // 8 位無符號整數值的型別化陣列。內容將初始化為 0。
        // 如果無法分配請求數目的位元組,則將引發異常。
        byteArrays.push(new Uint8Array(byteNumbers));
      }
      let result = new Blob(byteArrays, {
        type: contentType
      })
      result = Object.assign(result,{
        // jartto: 這裡一定要處理一下 URL.createObjectURL
        preview: URL.createObjectURL(result),
        name: `圖片示例.png`
      });
      resolve(result)
    })
 }
複製程式碼

你可以這樣呼叫:

let base64 = base64.split(',')[1]

this.base64ToBlob({b64data: base64, contentType: 'image/png'}).then(res => {
    // 轉後後的blob物件
    console.log('blob', res)
})



複製程式碼

3. blob 轉 base64

blob to base64 的方法封裝

// 原理:利用fileReader的readAsDataURL,將blob轉為base64

blobToBase64(blob) {
    return new Promise((resolve, reject) => {
      const fileReader = new FileReader();
      fileReader.onload = (e) => {
        resolve(e.target.result);
      };
      // readAsDataURL
      fileReader.readAsDataURL(blob);
      fileReader.onerror = () => {
        reject(new Error('檔案流異常'));
      };
    });
}
複製程式碼

你可以這樣呼叫:

this.blobToBase64(blob).then(res => {
    // 轉化後的base64
    console.log('base64', res)
})
複製程式碼

更多的url轉 base64, base64 與 blob 的相互轉化的 demo ,後續會更新在這裡,有興趣可以戳一下傳送門

ps: 以上方法是針對玩轉圖片流的優化,感謝原作者。

三、圖片處理方式的歸納

1. 後端的圖片的儲存方式

在前面我們提到過,圖片在後端的儲存有兩種方式,我們回顧一下:其一:可以將圖片以獨立檔案的形式儲存在伺服器的指定資料夾中,再將路徑存入資料庫欄位中;其二:將圖片轉換成二進位制流,直接儲存到資料庫的 Image 型別欄位中;

那麼這兩種儲存方式,哪種更優呢?

據我瞭解,在網際網路環境中,大訪問量,資料庫速度和效能方面很重要。一般在資料庫儲存圖片的做法比較少,更多的是將圖片路徑儲存在資料庫中,展示圖片的時候只需要連線磁碟路徑把圖片載入進來即可。因為圖片是屬於大欄位。一張圖片可能1m到幾m。這樣的大欄位資料會加重資料庫的負擔,拖慢資料庫。在大併發訪問的情況下很重要。這是一個經驗。去看看dba對資料庫效能調優方面的分析都能得到這個答案的:就是圖片不要儲存在資料庫中。

因此,如果你司的後端小哥哥經常將圖片以二進位制的形式儲存到資料庫然後返回給你對接,你應該知道如何去dui他了吧(滑稽臉)。

更多關於圖片或者檔案在資料庫的儲存方式的歸納請戳這裡

2. 前端的圖片的顯示方式

對於前端來說: 圖片在前端顯示有三種方式:url、base64、blob

三種顯示方式,哪種更優雅呢?

url: 一般來說,圖片的顯示還是建議使用url的方式比較好。如果後端傳過來的欄位是圖片路徑的話。

base64:如果圖片較大,圖片的色彩層次比較豐富,則不適合使用這種方式,因為其Base64編碼後的字串非常大,會明顯增大HTML頁面,影響載入速度。 如果圖片像loading或者表格線這樣的,大小極小,但又佔據了一次HTTP請求,而很多地方都會使用。則非常適用“base64:URL圖片”技術進行優化了!詳細的張鑫旭的Demo演示,請戳這裡一下

blob: 當後端返回特定的圖片二進位制流的時候,就像我第一part裡的情景再現說的,前端用blob容器接收。圖片用blob展示會比較好。

四、感想

付出,記錄,總結。在專案中遇到的問題我都會一點一滴的記錄整理下來。我相信,這些都是一片一片散落的枝葉,隨著專案經驗的增多,這些枝葉最終一定能夠成長為一棵參天大樹。

文中的觀點受限於本人當前的技術水平,難免會有講錯的地方,歡迎評論區留言交流指正。

隨著技術水平的提升,文章會不定期的迭代而優化~你可以通過下面的方式聯絡到我。

關於我

微信公眾號二維碼

文章回顧:

參考資料:

相關文章