やりたいこと
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)
あとはこの文字列をバックエンドに渡して保存するだけです。
おしまい。