Blob、File 、DataURL(Base64)、BlobURL 之間的類型閉環,你肯定都知道?

做者: gauseen
原文: https://github.com/gauseen/blog
公衆號【學前端】只搞技術,不搞廣告文,肯定不關注一下?

說一說本篇文章講哪些點

相信在工做中常常遇到,文件上傳、圖片壓縮、文件下載、大文件斷點續傳,等等關於 js 來操做文件的需求。那麼你真的瞭解文件類型之間的轉換關係嗎?以下:javascript

  • Blob --> File
  • File --> DataURL(base64)
  • File --> BlobURL
  • HTTPURL| DataURL | BlobURL --> Blob

文件類型轉換閉環

提示: 公衆號回覆 「file」 可得高清原圖html

Blob 類型

Blob 類型是 File 文件類型的父類,它表示一個不可變、原始數據的類文件對象前端

如何獲得 blob 對象?

1. new Blob(array, options)java

let hiBlob = new Blob([`<h1>Hi gauseen!<h1>`], { type: 'text/html' })

如上代碼,就建立了一個 blob 對象,並聲明瞭 text/html 類型 ,就像是建立一個 .html 文件。只不過它存在於瀏覽器的內存裏。jquery

2. fetch(url)nginx

js 爲咱們提供了不少獲取資源的 api,如:<img> 和 <script>
Fetch API 提供了一個獲取資源的統一接口(包括跨域請求)git

關於 fetch(url, options), url 參數支持格式有:github

截止到 2020-01-13
  • http、https
  • blobURL: 好比經過 URL.createObjectURL() 得到web

    // blobURL 示例:
    blob:null/7025638d-c05f-4c75-87d6-470a427e9aa3
  • dataURL: 如圖片的 base64 格式,好比經過 convasElement.toDataURL() 得到json

    // dataURL(base64) 黑色 1 像素示例:
    data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=

fetch(url, options) 響應數據可被解析成:

  • res.arrayBuffer(): 通用、固定長度的原始二進制數據緩衝區
  • res.blob(): Blob 類型
  • res.formData(): 表單數據類型
  • res.json(): JSON 格式
  • res.text(): 文本格式

本文主要關心 blob 類型轉換,以下代碼,用 fetch api 獲取圖片資源的 blob 對象,
固然也能夠獲取其它類型的資源。如:.txt .html

// 獲取圖片的 blob 對象
// 經過 http、https 獲取
fetch('http://eg.com/to/path/someImg.png')
  .then(res => res.blob())
  .then(blob => {
    console.log('blob: ', blob)
  })

3. canvasElement.toBlob(callback)

canvas 具備圖像操做能力,支持將一個已有的圖片做爲圖片源,來操做圖像。

以下,經過 canvas 將圖片資源轉成 blob 對象

<body>
  <canvas width="100" height="100"></canvas>
</body>

<script>
  const $ = arg => document.querySelector(arg)
  let convas = $('canvas')
  // async 自執行函數
  (async () => {
    let imgUrl = 'http://eg.com/to/path/someImg.png'
    let ctx = convas.getContext('2d')
    let img = await fetchImg(imgUrl)
    // 向 canvas 畫布上下文繪製圖片
    ctx.drawImage(img, 0, 0)

    // 獲取圖片 blob 對象
    convas.toBlob((blob) => {
      console.log('blob: ', blob)
    })

    // 獲取圖片 dataURL,也是 base64 格式
    let dataURL = convas.toDataURL()
    console.log('dataURL: ', dataURL)
  })()

  // 獲取圖片資源,封裝成 promise
  function fetchImg (url) {
    return new Promise((resolve, reject) => {
      let img = new Image()
      // 跨域圖片處理
      img.crossOrigin = 'anonymous'
      img.src = url
      // 圖片資源加載完成回調
      img.onload = () => {
        resolve(img)
      }
    })
  }
</script>

注:

  • 若是圖片沒加載完,就調用 drawImage,canvas 繪製將失敗,因此咱們簡單封裝了 fetchImg 方法,確保圖片資源加載完成後再開始繪製圖片。
  • 因爲 canvas 中的圖片可能來自一些第三方網站。在不作處理的狀況下,使用跨域的圖片繪製時會污染畫布,這是出於安全考慮。在「被污染」的畫布中調用 toBlob() toDataURL() getImageData() 會拋出安全警告。

    解決方法:

    let img = new Image()
    // 1. 增長 crossOrigin 屬性,值爲 anonymous
    // 含義:執行一個跨域請求,在請求頭裏加 origin 字段
    // 2. 後端要返回 Access-Control-Allow-Origin 響應頭來容許跨域
    img.crossOrigin = 'anonymous'
    img.src = 'to/path'

    本質就是解決跨域問題,也可使用 nginx 作個代理來解決

  • blobslice(startIndex, endIndex) 方法,複製 blob 對象某片斷,與 js 數組的 slice 方法相似,文件的斷點續傳功能就是利用了改特性。

File 類型

File 包含文件的相關信息,能夠經過 js 來訪問其內容

如何獲取 file 對象?

1. new File(bits, name[, options])

// 1. 參數是字符串組成的數組
let hiFile = new File([`<h1>Hi gauseen!<h1>`], 'fileName', { type: 'text/html' })

// 2. blob 轉 file 類型
let hiBlob = new Blob([`<h1>Hi gauseen!<h1>`], { type: 'text/html' })
let hiFile = new File([ hiBlob ], 'fileName', { type: 'text/html' })

如上代碼,經過 File 構造函數,建立一個 file 對象,與上面的提到的 blob 相似。能夠將 blob 轉成 file 類型,這意味着上面獲取的 blob,能夠轉成 file 類型。

2. inputElement.files

經過 <input type="file"> 標籤獲取 file 對象

// input 上傳文件時觸發 change 事件
$('input').addEventListener('change', e => {
  let file = e.target.files[0]
  console.log('file: ', file)
})

3. DragEvent.dataTransfer.files

經過拖、放獲取 file 對象

<body>
  <div id="output">
     將文件拖放到這裏~
  </div>
</body>

<script>
  const $ = arg => document.querySelector(arg)
  let outputEle = $('#output')
  // ondragover 事件規定在何處放置被拖動的數據
  outputEle.addEventListener('dragover', dragEvent => {
    dragEvent.preventDefault()
  })
  // ondrop 事件放置文件時觸發
  outputEle.addEventListener('drop', dragEvent => {
    dragEvent.preventDefault()
    // DataEvent.dataTransfer 屬性保存着拖拽操做中的數據
    let files = dragEvent.dataTransfer.files
    console.log('drag files: ', files)
  })
</script>

4. canvasElement.mozGetAsFile()

注: 截止 2020-01-13,該方法僅支持火狐瀏覽器

let file = canvasElement.mozGetAsFile('imgName')

DataURL(base64)

DataURL,前綴爲 data: 協議的 URL,能夠存儲一些小型數據

語法:data:[<mediatype>][;base64],<data>

以下,黑色 1 像素示例:

data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=

上面提到的 Blob File 類型,如何「消費」它們呢?接着向下看

1. FileReader

容許 Web 應用程序異步讀取存儲在用戶計算機上的文件(blobfile)。

// 將 blob 或 file 轉成 DataURL(base64) 形式
fileReader(someFile).then(base64 => {
  console.log('base64: ', base64)
})

function fileReader (blob) {
  return new Promise((resolve, reject) => {
    let reader = new FileReader()
    reader.onload = (e) => {
      resolve(e.target.result)
    }
    reader.readAsDataURL(blob)
  })
}

2. convasElement.toDataURL()

能夠經過 canvas 圖像處理能力,將圖片轉成 dataURL 形式。在上面 Blob 部分講解中,代碼已實現。

BlobURL(ObjectURL)

BlobURL 也叫 ObjectURL,它可讓只支持 URL 協議的 Api(如:<a> <link> <img> <script>) 訪問 fileblob 對象。
dynamic-import-polyfill 庫也用到了其特性。

以下,生成 blobURLcreateObjectURL 方法建立從 URL 到 Blob 的映射關係。
如:blob:http://eg.com/550e8400-e29b-41d4-a716-446655440000

// object 建立 URL 的 File 對象、Blob 對象或者 MediaSource 對象
let blobURL = URL.createObjectURL(object)

以下,revokeObjectURL 方法撤消 blobURL 與 Blob 的映射關係,有助於瀏覽器垃圾回收,提示性能。

URL.revokeObjectURL(blobURL)

造成閉環

經過上面的一系列轉換關係,能夠知道:

blob --> file --> dataURL(base64) | blobURL --> blob

這樣就造成了一個閉環,文章開頭的思惟導圖很好的說明了之間的轉換關係。

應用舉例

文件下載

經過 a 標籤實現下載,blob 或 file 對象。至於如何獲取 blob 和 file 上面已經說得很清楚了。

function downloadFile1 (blob, fileName = 'fileName') {
  let blobURL = URL.createObjectURL(blob)
  let link = document.createElement('a')
  link.download = fileName
  link.href = blobURL
  link.click()
  // 釋放 blobURL
  URL.revokeObjectURL(blobURL)
}

// 固然也能夠經過 window.location.href 下載文件
function downloadFile2 (blob, fileName = 'fileName') {
  let blobURL = URL.createObjectURL(blob)
  window.location.href = blobURL
  // 釋放 blobURL
  URL.revokeObjectURL(blobURL)
}

壓縮圖片

// 壓縮圖片,圖片質量爲 0.6
compressImage('to/path/someImg.png', 0.6).then(base64 => {
  console.log('compressImage: ', base64)
})

// imgUrl 圖片地址
// quality 圖片質量 0 ~ 1 之間
// type 壓縮圖片只支持,image/jpeg 或 image/webp 類型
// 返回 base64 數據
async function compressImage (imgUrl, quality = 1, type = 'image/jpeg') {
  let imgEle = await fetchImg(imgUrl)
  let canvas = document.createElement('canvas')
  let cxt = canvas.getContext('2d')
  // 設置 canvas 寬高
  let { width, height } = imgEle
  canvas.setAttribute('width', width || 100)
  canvas.setAttribute('height', height || 100)

  cxt.drawImage(imgEle, 0, 0)
  return canvas.toDataURL(type, quality)
}

// 經過 url 獲取圖片
function fetchImg (url) {
  return new Promise((resolve, reject) => {
    let img = new Image()
    img.crossOrigin = 'Anonymous'
    img.src = url
    img.onload = () => {
      resolve(img)
    }
  })
}

總結

相信讀完這篇文章之後,你會對文件類型之間的轉換有更全方位的瞭解,其實還有不少像 ArrayBuffer 存儲二進制數據相關的 Api 沒有寫到,由於平時用到的場景比較少,感興趣的能夠結合本文,去更深一步的探索。

歡迎關注無廣告文章公衆號:學前端

PS: 只搞技術不搞廣告文都不關注?沒天理啦!
你的關注是我更文最大動力!

無廣告公衆號學前端

參考

相關文章
相關標籤/搜索