點燈坊

失くすものさえない今が強くなるチャンスよ

如何計算圖片的 Base64 ?

Sam Xiao's Avatar 2021-09-02

使用 File Upload 選擇圖片後,接著會將檔案轉成 Base64 String 透過 API 上傳,該如何將檔案轉成 Base64 String 呢 ?

Version

Vue 3.2

FileReader

base000

上傳檔案計算出檔案的 Base64 並顯示。

<template>
  <input type="file" @input="onInput" ref="fileUpload" accept="images/*" multiple hidden>
  <button class="bg-blue-500 hover_bg-blue-700 text-white font-bold py-2 px-4 rounded" @click="onUpload">Upload</button>
  <img :src="base64">
</template>

<script setup>
let base64 = $ref ('')
let fileUpload = $ref (null)

let onUpload = _ => fileUpload.click ()

let onInput = _ => {
  let fileReader = new FileReader
  fileReader.onload = _ => base64 = fileReader.result
  fileReader.onerror = console.error
  fileReader.readAsDataURL (fileUpload.files[0])
}
</script>

14 行

let fileReader = new FileReader
fileReader.onload = _ => base64 = fileReader.result
fileReader.onerror = console.error

要將檔案計算出 Base64 不難,HTML 內建的 FileReader 就可完成。

不過 FileReader 以 event-based API 實現 asynchronous,必須在 onload event 內讀取 fileReader.result 才能得到 Base64。

17 行

fileReader.readAsDataURL (fileUpload.files[0])

將 file 傳入 fileReader.readAsDataURL 即可得到 Base64。

Promise

base000

結果不變,但使用 Promise 改寫。

<template>
  <input type="file" @input="onInput" ref="fileUpload" accept="images/*" multiple hidden>
  <button class="bg-blue-500 hover_bg-blue-700 text-white font-bold py-2 px-4 rounded" @click="onUpload">Upload</button>
  <img :src="base64">
</template>

<script setup>
let base64 = $ref ('')
let fileUpload = $ref (null)

let toBase64 = file => new Promise ((resolve, reject) => {
  let fileReader = new FileReader
  fileReader.onload = _ => resolve (fileReader.result)
  fileReader.onerror = reject
  fileReader.readAsDataURL (file)
})

let onUpload = _ => fileUpload.click ()

let onInput = _ => 
  toBase64 (fileUpload.files[0])
    .then (x => base64 = x)
    .catch (console.error)
</script>

FileReader 雖然可用,但畢竟屬於較早期 Web API,asynchronous 仍使用 event 處理,比較好的方式是改用 Promise。

11 行

let toBase64 = file => new Promise ((resolve, reject) => {
  let fileReader = new FileReader
  fileReader.onload = _ => resolve (fileReader.result)
  fileReader.onerror = reject
  fileReader.readAsDataURL (file)
})

自行建立 toBase64,使用 new Promise ((resolve, reject) => {}) 建立 Promise,在 onload 使用 resolve 回傳 fileReader.result,且在 onerror 使用 reject 回傳 error。

file 傳入 fileReader.readAsDataURL 計算出 Base64。

21 行

toBase64 (fileUpload.files[0])
  .then (x => base64 = x)
  .catch (console.error)

toBase64 回傳 Promise 後,就可使用 Promise Chain 處理 Base64。

Point-free

base000

結果不變,但使用 Point-free 改寫。

<template>
  <input type="file" @input="onInput" ref="fileUpload" accept="images/*" multiple hidden>
  <button class="bg-blue-500 hover_bg-blue-700 text-white font-bold py-2 px-4 rounded" @click="onUpload">Upload</button>
  <img :src="base64">
</template>

<script setup>
import { ref, unref } from 'vue'
import { read, write } from 'vue3-fp'
import { pipe, prop, andThen as then, otherwise, head } from 'ramda'
import { error } from 'wink-fp'

let base64 = ref ('')
let fileUpload = ref (null)

let toBase64 = file => new Promise ((resolve, reject) => {
  let fileReader = new FileReader
  fileReader.onload = _ => resolve (fileReader.result)
  fileReader.onerror = reject
  fileReader.readAsDataURL (file)
})

let onUpload = _ => unref (fileUpload).click ()

let onInput = pipe (
  read (fileUpload),
  prop ('files'),
  head,
  toBase64,
  then (write (base64)),
  otherwise (error)
)
</script>

25 行

let onInput = pipe (
  read (fileUpload),
  prop ('files'),
  head,
  toBase64,
  then (write (base64)),
  otherwise (error)
)

使用 pipe 組合出 onInput

  • read (fileUpload):讀取 fileUpload
  • prop ('files'):讀取 files prop
  • headfiles 為 Array,取其第一筆
  • toBase64:將 file 轉成 Base64
  • then (write (base64))toBase64 回傳為 Promise,使用 then 拆解後寫入 base64 state
  • otherwise (error):處理 Rejected

Conclusion

  • 實務上較少使用 new Promise ((resolve, reject) => {}) 建立 Promise,但遇到 FileReader 這類 event-based API,只能使用這種方式回傳 Promise