117
93

More than 5 years have passed since last update.

javascriptでBase64

Last updated at Posted at 2016-02-12

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);
117
93
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
117
93