2021, Feb 14

【React, JavaScript】 URLから画像を取得してbase64に変換する方法

ReactでSNS認証後にプロフィール画像を自分のバックエンドサーバーにbase64で保存したい時にやった方法です。

やりたいこと

Twitter 認証機能を実装していて、取得したプロフィール画像 URL を元に自分のサーバー内へそのプロフィール画像の base64 形式のテキストデータとして格納したかったので、そのやり方を調べました。

これで動く

Chrome の開発ツールなどで以下を試してみてください。動くと思います。

// @twitterアカウントのプロフィール画像を例として取得
const url =
  'https://pbs.twimg.com/profile_images/1354479643882004483/Btnfm47p_400x400.jpg'

// 取得してbase64画像化されたテキストを返す関数
async function getImageBase64(url) {
  const response = await fetch(url)
  const contentType = response.headers.get('content-type')
  const arrayBuffer = await response.arrayBuffer()
  let base64String = btoa(
    String.fromCharCode.apply(null, new Uint8Array(arrayBuffer))
  )
  return `data:${contentType};base64,${base64String}`
}

// 実際に使う
const str = await getImageBase64(url)
console.log(str)

Maximum call stack size exceeded

ファイルサイズが小さいとこれで動くんですけど、数百キロバイトくらいの画像になってくると、内部で使っているapplyメソッドの再帰呼び出しエラーが出てしまいます。

なので、大きいサイズの画像を扱う場合は、ちょっとトリッキーな工夫を入れる必要があります。

大きいサイズもカバーした実装

const large_image =
  'https://images.ctfassets.net/3b7wpxdiy8tj/564b576VdCocq4yumy8qs4/f8f5ea027e46c130367506e9453d3f82/puppy-litter.jpg'

// 取得してbase64画像化されたテキストを返す関数
async function getImageBase64(url) {
  const response = await fetch(url)
  const contentType = response.headers.get('content-type')
  const arrayBuffer = await response.arrayBuffer()
  const APPLY_MAX = 1024
  let encodedStr = ''
  // ArrayBufferの中身を1024バイトに区切って少しずつ文字列にしていく
  for (var i = 0; i < arrayBuffer.byteLength; i += APPLY_MAX) {
    encodedStr += String.fromCharCode.apply(
      null,
      new Uint8Array(arrayBuffer.slice(i, i + APPLY_MAX))
    )
  }
  let base64String = btoa(encodedStr)
  return `data:${contentType};base64,${base64String}`
}

// 実際に使う
const str = await getImageBase64(large_image)
console.log(str)

あとはこの文字列をバックエンドに渡して保存するだけです。

おしまい。