DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> HTML基礎知識 >> HTML5教程 >> 從web圖片裁剪出發:了解H5中的Blob
從web圖片裁剪出發:了解H5中的Blob
編輯:HTML5教程     

  剛開始做前端的時候,有個功能卡住我了,就是裁剪並上傳頭像。當時兩個方案擺在我面前,一個是flash,我不會。另一個是通過iframe上傳圖片,然後再上傳坐標由後端裁剪,而我最終的選擇是後者。有人會疑惑,為什麼不用H5的Canvas和FormData,第一要考慮ie8的兼容性,第二那時候眼界沒到,這種新東西光是聽聽都怕。

  後來隨著Mobile項目越做越多,類似的功能開發得也越來越多,Canvas+FormData成為了標配方案。但做的多了卻一直沒有靜下心來研究,浏覽器怎麼使用H5的方式裁剪並把文件發送出去,回過頭看都是知其然不知其所以然。這篇隨筆先做個初步的拆解,就是當通過input選擇一張圖片後,這張圖片在浏覽器裡是怎樣的一個存在。

  文件操作一直是早期浏覽器的痛點,全封閉式,不給JS操作的空間,而隨著H5一系列新接口的推出,這個壁壘被打破。對,是一系列接口,以下會涉及到如下概念:Blob、File、FileReader、ArrayBuffer、ArrayBufferView、DataURL等,其他如FormData、XMLHttpRequest、Canvas等暫不深入。

  我們先創建一個簡單的頁面,只有一個input[type=file]。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <input type="file">
</body>
</html>

  然後我們在JS中獲取這個元素

var input = document.querySelector('input[type=file]');

  可以看到這個元素有個屬性files,它的類型是FileList。這個類不做過多介紹,就是一個類數組,由浏覽器通過用戶行為往裡面添加或刪除元素,JS只有訪問其元素的接口,無法對其進行操作。而files的元素就是File類型,File是blob的子類,比blob主要多出一個name的屬性。

  現在我們選取一個文件,這裡問題來了,這個元素是文件在浏覽器的完整備份,還是一個指向文件系統的引用?答案是後者,我們選定文件,然後修改文件名,再上傳文件,浏覽器報錯了。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <form name='test'>
    <input type="file">
    <input type="submit" value="提交">
  </form>
  <script>
    var input = document.querySelector('input[type=file]'),
        form = document.test;
    form.addEventListener('submit', function(e) {
      e.preventDefault();
      var file = input.files[0],
            fd = new FormData(),
            xhr = new XMLHttpRequest();
      fd.append('file', file);
      xhr.open('post', '/upload');
      xhr.send(fd);
    });
  </script>
</body>
</html>

  使用chrome打開chrome://blob-internals/,可以看到一條這樣的記錄

  可見這僅僅是一條引用。第二個問題來了,如果我們要對圖片進行處理,那麼只拿到引用是不行的,肯定要在浏覽器有一份數據的備份,那麼怎麼獲取這個備份呢?答案就是FileReader,FileReader的對象主要有readAsArrayBuffer、readAsBinaryString、readAsDataURL、readAsText等方法,它們的入參都是Blob對象或是File對象,結果對應最終獲取的數據類型。這幾個方法是異步的,讀取過程中會拋出對應的事件,其中讀取完畢的事件為load,所以數據的處理要放在onload下。我先給一個簡單的example:

input.addEventListener('change', function() {
  var file = this.files[0],
      fr = new FileReader(),
      blob;
  fr.onload = function() {
    blob = new Blob([this.result]);
  };
  fr.readAsArrayBuffer(file)
});

  當用戶選取圖片時,調用FileReader的readAsArrayBuffer把圖片數據讀出來,然後生成新的blob對象保存在浏覽器中。查看chrome://blob-internals/,可以注意到這一項:

  對應的就是剛才的blob,可以對比length和圖片本身的大小。上面那個demo很突兀,完全沒有解釋什麼是ArrayBuffer,為什麼創建blob要傳入一個ArrayBuffer。那麼第三個問題來了,什麼是ArrayBuffer、BinaryString、DataURL、Text,它們有什麼聯系和不同,Blob類到底是個什麼東西?首先,圖片是個二進制文件,它的內容也是由0和1組成的。用戶肯定是看不懂0和1的組合的,能看懂的只有最終展示的圖片,而程序員也看不懂0和1,但程序員能看懂另外幾種0和1變換後的組合。它們就是以上的4種:ArrayBuffer、BinaryString、DataURL和Text。

  其中ArrayBuffer是最接近二進制數據的表現的,可以理解為它就是二進制數據的存儲器,這也是為什麼二進制文件的Blob需要傳入ArrayBuffer。正因為它的內部是二進制數據,所以我們是不可以直接操作的。這時候就需要一個代理者幫助我們讀或寫,這個代理者就是ArrayBufferView。

  ArrayBufferView不是一個類,而是一個類的集合,包括:Int8Array、Uint8Array、Uint8ClampedArray、Int16Array、Uint16Array、Int32Array、Uint32Array、Float32Array、Float64Array和DataView,分別表示以8位、16位、32位、64位數字為元素對ArrayBuffer內的二進制數據進行展現,它們都有統一的屬性buffer指向對應的ArrayBuffer。栗子暫時不舉,之後會用到。

  ArrayBuffer簡單介紹了,那什麼是BinaryString呢?是二進制數據直接以byte的形式展現的字符串,比如1100001,用Uint8表示就是97,用BinaryString表示就是'a'。對,前者是charCode,後者是char,所以BinaryString和Uint8Array之間是可以自由轉換的。

  接下來是DataURL了,這是一個經過base64編碼的字符串,它的組成如下:

data:[mimeType];base64,[base64(binaryString)]

  除了固定的字符串部分,它主要包含兩個重要信息即中括號括起的部分,mimeType和base64編碼後的binaryString,從它裡面我們可以這樣取到這兩個信息。

var binaryString = atob(dataUrl.split(',')[1]),
    mimeType = dataUrl.split(',')[0].match(/:(.*?);/)[1];

  最後,Text是什麼呢?在ftp上,文本傳輸和二進制傳輸的區別是什麼,那Text類型和BinaryString類型的區別就是什麼了,也就是Text類型是經過一定轉換的BinaryString,對於圖片來說,這個類型是用不到的。

  好了,現在我們了解了一張圖片在浏覽器裡以數據的形式可以表現為ArrayBuffer、BinaryString、DataURL,那麼第四個問題來了,它們各有實際用途呢?我們從應用場景出發,回到文章開頭的問題,圖片的裁剪和上傳。圖片的裁剪我們要倚仗牛逼的canvas,而canvas的context有這麼一個方法toDataURL,就是把canvas的內容轉換為圖片數據,而數據的表現形式就是DataURL!圖片的上傳我們用的是FormData,它可以添加Blob類型的對象進去,那Blob類型除了從input[type=file]中直接獲取,還能靠什麼生成呢?自然是ArrayBuffer!好了,裁剪圖片的功能要用到DataURL,上傳圖片的功能要用到ArrayBuffer,那怎麼從DataURL轉換為ArrayBuffer呢?我們知道DataURL很重要的組成部分就是經過base64編碼的BinaryString,那麼很顯然我們可以從DataURL中提取BinaryString,而BinaryString就是ArrayBuffer對應的Uint8Array的字符形式的表現,所以可以由BinaryString生成ArrayBuffer,那麼DataURL到ArrayBuffer之間的橋就是BinaryString!

  到現在為止,我們說了很多概念,然而這並沒有什麼卵用,驗證概念的方法不是提出新的概念,而是建立一個example。以下的example就是把圖片數據從input中取出,然後以DataURL的格式進行預覽,提交時把預覽生成圖片上傳的整個流程。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <form name='test'>
    <input type="file" name='file'>
    <input type="submit" value="提交">
  </form>
  <img src="" alt="">
  <script>
    var img = document.querySelector('img'),
        preview;
    document.test.file.addEventListener('change', function() {
      var fr = new FileReader();
      fr.onload = function() {
        preview = this.result;
        img.src = preview;
      };
      fr.readAsDataURL(this.files[0]);
    })
    document.test.addEventListener('submit', function(e) {
      e.preventDefault();
      var binaryString = atob(preview.split(',')[1]),
          mimeType = preview.split(',')[0].match(/:(.*?);/)[1],
          length = binaryString.length,
          u8arr = new Uint8Array(length),
          blob,
          fd = new FormData(),
          xhr = new XMLHttpRequest();
      while(length--) {
        u8arr[length] = binaryString.charCodeAt(length);
      }
      blob = new Blob([u8arr.buffer], {type: mimeType});
      fd.append('file', blob);
      xhr.open('post', '/upload');
      xhr.send(fd);
    })
  </script>
</body>
</html>

  現在圖片已經被我們發射出去了,那麼圖片在協議包裡是以怎樣的數據形式存在的呢?當然是以二進制的形式,我們抓一下包,發現在fiddler裡面這個二進制串會轉換為字符串,即上面的binaryString。

  既然通過發送的blob到最後在數據包裡都是以binaryString的形式展示,那麼是否可以直接使用xhr.send(binaryString)發送圖片呢?貌似是可以的,但我們試一下就會發現問題,服務器獲取到的信息不能生成一張圖片,說明數據被破壞了。那麼數據是誰破壞的呢?這個罪魁禍首就是send,當send的參數是字符串的時候,會對字符串進行utf8編碼。我們看下相同的圖片通過blob發送出去和通過binaryString直接發送出去的數據會有什麼不同。這裡我們用wireshark抓包,因為wireshark會自動對數據塊進行分割,可以比較直觀的看到圖片所對應的數據。PS: 這張圖片一張1px白色的png。

 

   前面是正常的圖片數據,後面是經過了utf8編碼的圖片數據。我們可以看到數據確實被破壞了,當然在知道元數據是binaryString的情況下,這種破壞是可以恢復的,不過不是這裡討論的范疇了,感興趣的可以跳轉阮老師的博客《字符編碼筆記:ASCII,Unicode和UTF-8》。

  好了,整個圖片在浏覽器端的拆解到此結束。理解了這些,就走完了寫出牛逼的客戶端圖片裁剪工具的第一步。

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved