Help us understand the problem. What is going on with this article?

javascriptでBase64

More than 3 years have passed since last update.

javascriptでBase64

Data URI Schemeなどで任意のデータのBase64文字列が必要な場合がある。 大体のブラウザでwindow.btoa()及びwindow.atob()が存在するが これに渡せる文字列には制限があり、文字コードが0xff以下でないとエラーになる。

Base64文字列を得る方法として以下を考えた

  1. btoaに渡す文字列を工夫する
  2. 自前で実装する
  3. FileReader.prototype.readAsDataURLを利用する

btoaに渡す文字列を工夫する

btoa()に直接バイナリデータ文字列(8bit)を渡せば良い。 バイナリデータ文字列ならば文字コードが全て0xff以下であるので正常に変換が可能。

var binstr = String.fromCharCode(227, 129, 130); //=> "\xe3\x81\x82" ←"あ"のUTF-8表現
btoa(binstr); //=> "44GC"

エンコードしたいデータが文字データであり、かつ文字コードがUTF-8で良いのならば以下のようにする

var Base64 = {
    encode: function(str) {
        return btoa(unescape(encodeURIComponent(str)));
    },
    decode: function(str) {
        return decodeURIComponent(escape(atob(str)));
    }
};

Base64.encode("Base64エンコード"); //=> "QmFzZTY044Ko44Oz44Kz44O844OJ"
Base64.decode("QmFzZTY044OH44Kz44O844OJ"); //=> "Base64デコード"

encodeURIComponent()が文字列をUTF-8化してURLエスケープするので、 それをアンエスケープしてUTF-8のバイナリデータ文字列を得る。 文字データに限られるが非常に短く実装できる。

注意すべきなのは、、バイナリデータ文字列を上記の関数に渡すと 文字列に再度UTF-8変換がかかるので、おかしなBase64になってしまう。

var binstr = String.fromCharCode(128, 129, 130); //3byte
Base64.encode(binstr); //=> "woDCgcKC" ←4byteではなく8byteになっている。UTF-8では0x80以上は2byteになる為
btoa(binstr);          //=> "gIGC"     こちらが正しい

通常の文字列(printしたものが可読なもの)に対してのみ利用すること

自前で実装する

ArrayやArrayBufferから変換するのに一度文字列化するのは無駄な処理になる。 そこでArrayやArrayBufferからBase64文字列へ自前で変換してみる。

Base64の仕様

Base64は割と簡単な仕組みなので実装は難しくない。 encode時には3byte(8bit * 3)を4byte(6bit * 4)に変換し、A-Za-z0-9+/を割り当てる。 最後4byteに足りない場合には = で埋める。 decode時はその逆である。

実装

var Base64a = {
    encode: (function(i, tbl) {
        for(i=0,tbl={64:61,63:47,62:43}; i<62; i++) {tbl[i]=i<26?i+65:(i<52?i+71:i-4);} //A-Za-z0-9+/=
        return function(arr) {
            var len, str, buf;
            if (!arr || !arr.length) {return "";}
            for(i=0,len=arr.length,buf=[],str=""; i<len; i+=3) { //6+2,4+4,2+6
                str += String.fromCharCode(
                    tbl[arr[i] >>> 2],
                    tbl[(arr[i]&3)<<4 | arr[i+1]>>>4],
                    tbl[i+1<len ? (arr[i+1]&15)<<2 | arr[i+2]>>>6 : 64],
                    tbl[i+2<len ? (arr[i+2]&63) : 64]
                );
            }
            return str;
        };
    }()),
    decode: (function(i, tbl) {
        for(i=0,tbl={61:64,47:63,43:62}; i<62; i++) {tbl[i<26?i+65:(i<52?i+71:i-4)]=i;} //A-Za-z0-9+/=
        return function(str) {
            var j, len, arr, buf;
            if (!str || !str.length) {return [];}
            for(i=0,len=str.length,arr=[],buf=[]; i<len; i+=4) { //6,2+4,4+2,6
                for(j=0; j<4; j++) {buf[j] = tbl[str.charCodeAt(i+j)||0];}
                arr.push(
                    buf[0]<<2|(buf[1]&63)>>>4,
                    (buf[1]&15)<<4|(buf[2]&63)>>>2,
                    (buf[2]&3)<<6|buf[3]&63
                );
            }
            if (buf[3]===64) {arr.pop();if (buf[2]===64) {arr.pop();}}
            return arr;
        };
    }())
};


Base64a.encode([97, 98, 99, 100]); //=> "YWJjZA=="
Base64a.decode("YWJjZA=="); //=> [97, 98, 99, 100]

ArrayBuffer,Blob(File)などから変換する場合

Base64a.encodeに渡す配列が Uint8Array ならば良いので

Base64a.encodeArrayBuffer = function(ab) {
    return Base64a.encode(new Uint8Array(ab));
};
Base64a.encodeBlob = function(blob, callback) {
    var r = new FileReader();
    r.onload = function() {callback(Base64a.encodeArrayBuffer(r.result));};
    r.readAsArrayBuffer(blob);
};

var blob = new Blob(["abcd"]);
Base64a.encodeBlob(blob, function(str) {
    console.info(str); //=> "YWJjZA=="
});

FileReader.prototype.readAsDataURLを利用する

FileReader.prototype.readAsDataURLならばbase64を含んだData URIが取得出来る。
data:mime/type;base64,...Base64String...という形式なのでカンマ以降を抜き出せばよい。

var blob = new Blob(["abcd"]),
    r = new FileReader();
r.onload = function() {
    console.info(r.result.substr(r.result.indexOf(',')+1));
};
r.readAsDataURL(blob);
weal
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした