base-64作為常見的編碼函數,在基本認證、摘要認證以及一些HTTP擴展中得到了大量應用。在前端領域,也常常把圖片轉換為base-64編碼在網絡中傳輸。本文將詳細介紹base64的原理及用js實現base64編碼器的過程
原理
Base-64編碼可以將任意一組字節轉換成較長的常見文本字符序列,從而可以合法地作為首部字段值。Base-64編碼將用戶輸入或二進制數據,打包成一種安全格式,將其作為 HTTP首部字段的值 發送出去,而無須擔心其中包含會破壞HTTP分析程序的冒號、換行符或二進制值
Base-64編碼是作為MIME多媒體電子郵件標准的一部分開發的,這樣MIME就可以在不同的合法電子郵件網關之間傳輸富文本和任意的二進制數據了。Base-64編碼與將二進制數據文本化表示的uuencode和BinHex標准在本質上很類似,但空間效率更高
【拆分】
Base-64編碼將一個8位字節序列拆散為6位的片段,並為每個6位的片段分配一個字符,這個字符是Base-64字母表中的64個字符之一。這64個輸出字符都是很常見的,可以安全地放在HTTP首部字段中。這64個字符中包含大小寫字母、數字、+和/,還使用了特殊字符=
下表Base-64的字母表
0 A 8 I 16 Q 24 Y 32 g 40 o 48 w 56 4 1 B 9 J 17 R 25 Z 33 h 41 p 49 x 57 5 2 C 10 K 18 S 26 a 34 i 42 q 50 y 58 6 3 D 11 L 19 T 27 b 35 j 43 r 51 z 59 7 4 E 12 M 20 U 28 c 36 k 44 s 52 0 60 8 5 F 13 N 21 V 29 d 37 l 45 t 53 1 61 9 6 G 14 O 22 W 30 e 38 m 46 u 54 2 62 + 7 H 15 P 23 X 31 f 39 n 47 v 55 3 63 /
[注意]由於Base64編碼用8位字符表示信息中的6個位,所以Base-64編碼字符串大約比原始值擴大了 33%
【編碼實現】
下圖是一個簡單的Base-64編碼實例。在這里,三個字符組成的輸入值“Ow!”是Base-64編碼的,得到的是4個字符的Base-64編碼值“T3ch”。它是按以下方式工作的
1、字符串“Ow!”被拆分成3個8位的字節(0x4F、0x77、0x21)
2、這3個字節構成了一個24位的二進制值010011110111011100100001
3、這些位被划分為一些6位的序列010011、110111、01110、100001
4、每個6位值都表示了從0-63之間的一個數字,對應Base-64字母表中64個 字符之一。得到的Base-64編碼字符串是個4字符的字符串“T3ch”,然后就可 以通過線路將這個字符串作為“安全的”8位字符傳送出去,因為只用了一些 移植性最好的字符(字母、數字等)
【填充】
Base-64編碼收到一個8位字節序列,將這個二進制序列流划分成6位的塊。二進制序列有時不能正好平均地分成6位的塊,在這種情況下,就在序列末尾填充零位,使二進制序列的長度成為24的倍數(6和8的最小公倍數)
對已填充的二進制串進行編碼時,任何完全填充(不包含原始數據中的位)的6位組都由特殊的第65個符號“=”表示。如果6位組是部分填充的,就將填充位設置為0
下表顯示了一些填充實例
初始輸入字符串“a:a”為3字節(24位)。24是6和8的倍數,因此無需填充,得到的Base-64編碼字符串為“YTph”
然而,再增加一個字符,輸入字符串會變成32位長。而6和8的下一個公倍數是48,因此要添加16位的填充碼。填充的前4位是與數據位混合在一起的。得到的6位組01xxxx,會被當作010000、十進制中的16,或者Base-64編碼的Q來處理。剩下的兩個6位組都是填充碼,用“=”表示
[注意]Base-64編碼的官方規范移步至此
應用
網頁上的每一個圖片,都需要消耗一個http請求下載而來的。所以,才有了雪碧圖技術
無論如何,圖片的下載始終都要向服務器發出請求,要是圖片的下載不用向服務器發出請求,而可以隨着HTML的下載同時下載到本地那就太好了,而base64正好能解決這個問題
前面提到過Base-64編碼字符串大約比原始值擴大了33%。所以,不是所有的圖片使用base-64編碼都合適
但是,如果圖片足夠小且因為用處的特殊性(如需要平鋪等)無法被制作成雪碧圖,在整個網站的復用性很高且基本不會被更新。那么此時使用base64編碼傳輸圖片就可謂好鋼用在刀刃上
比如,一個只有50字節的2px*2px的背景圖。將其轉化成base64編碼,只有100多個字符,相比一個http請求,這種轉換無疑更值得推崇
把要轉化的圖片直接拖入chrome中,使用控制台中的Source選項,可直接查看圖片的base64編碼
字符串編碼
對於字符串來說,在javaScript中,有2個函數分別用來處理解碼和編碼base64字符串:atob() 和 btoa()
btoa()函數能夠從二進制數據“字符串”創建一個base-64編碼的ASCII字符串;相反地,atob()函數能夠解碼通過base-64編碼的字符串數據。btoa()函數從 String
對象中創建一個 base-64 編碼的 ASCII 字符串,其中字符串中的每個字符都被視為一個 二進制數據字節。
console.log(btoa('abc'));//'YWJj' console.log(atob('YWJj'));//'abc'
[注意]IE9-瀏覽器不支持
但是,以上方法有局限性,就是無法轉換中文
Uncaught DOMException: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.
這時,就需要使用編碼方法,先轉換為btoa()識別的字符,再進行base64編碼,如可以使用encodeURI()方法
var str = btoa(encodeURI('小火柴')); console.log(str);//JUU1JUIwJThGJUU3JTgxJUFCJUU2JTlGJUI0 console.log(decodeURI(atob(str)));//'小火柴'
<p style="margin:0">請在下面的框中輸入要轉換的字符</p> <textarea id="ta" cols="30" rows="10"></textarea> <button id="btn1">轉換</button><button id="btn2">反向轉換</button><br> <p style="margin:0">轉換后的字符如下:</p> <textarea id="result" cols="30" rows="10" readonly></textarea> <button id="sel">全選</button> <button id="reset">清空</button> <script> reset.onclick = function(){history.go();} btn1.onclick = function(){result.value = btoa(encodeURI(ta.value));} btn2.onclick = function(){result.value = decodeURI(atob(ta.value));} sel.onclick = function(){ result.focus(); result.select(); } </script>
圖片編碼
使用文件File API的readAsDataURL()方法,可以將文件以數據URI(進行Base64編碼)形式保存在result屬性中
//base64轉換函數 function base64(file){ if(fileData.innerHTML){ fileData.innerHTML = ''; btn.style.display = 'none'; } if(file){ if(/image/.test(file.type)){ var reader = new FileReader(); reader.readAsDataURL(file); reader.onload = function(){ fileData.innerHTML = reader.result; } }else{ alert("You must select a valid image file!"); } } }
一般地,生成的Base64編碼都比較長,可以增加一個全選代碼的功能
reader.onload = function(){ fileData.innerHTML = reader.result; btn.style.display = 'inline-block'; btn.onclick = function(){ fileData.focus(); fileData.select(); } }
可以使用文件File API選擇圖片文件
//點擊事件替代 targetArea.onclick = function(){file1.click();} //控件選中 file1.onchange = function(){ var file = file1.files[0]; base64(file); }
當然也可以使用原生拖拽,實現圖片拖拽,在某個區域顯示Base64編碼的效果
targetArea.ondragenter = function(e){this.style.outline = "1px solid black";} targetArea.ondragleave = function(e){this.style.outline = "";} //拖拽選中 targetArea.ondrop = function(e){ e = e || event; this.style.outline = ""; var file = e.dataTransfer.files[0]; base64(file); }
由於File API的兼容性限制,以下代碼在IE9-瀏覽器中無法正常運行
<input id="file1" type="file" accept="image/gif,image/jpeg,image/jpg,image/png,image/x-icon" style="display:none"> <div id="targetArea" style="display:inline-block;vertical-align:middle;height:100px;line-height:50px;width:210px;background:lightblue;">將圖片文件拖放到該區域內<br>或者點擊該區域選擇本地文件</div> <textarea id="fileData" style="vertical-align:middle;width:400px;height:200px;overflow:auto;word-wrap: break-word;"></textarea> <button id="btn" style="display:none;position:absolute;margin:220px 0 0 -80px">全選代碼</button> <script> //base64轉換函數 function base64(file){ if(fileData.innerHTML){ fileData.innerHTML = ''; btn.style.display = 'none'; } if(file){ if(/image/.test(file.type)){ var reader = new FileReader(); reader.readAsDataURL(file); reader.onload = function(){ fileData.innerHTML = reader.result; btn.style.display = 'inline-block'; btn.onclick = function(){ fileData.focus(); fileData.select(); } } }else{ alert("You must select a valid image file!"); } } } //點擊事件替代 targetArea.onclick = function(){file1.click();} //控件選中 file1.onchange = function(){ var file = file1.files[0]; base64(file); } //兼容事件處理程序 function addEvent(target,type,handler){ if(target.addEventListener){ target.addEventListener(type,handler,false); }else{ target.attachEvent('on'+type,function(event){ return handler.call(target,event); }); } } //兼容阻止默認事件 function preventDefault(e){ e = e || event; if(e.preventDefault){ e.preventDefault(); }else{ e.returnValue = false; } } addEvent(document,'dragover',preventDefault); addEvent(document,'drop',preventDefault); addEvent(targetArea,'dragenter',preventDefault); addEvent(targetArea,'dragover',preventDefault); addEvent(targetArea,'dragleave',preventDefault); addEvent(targetArea,'drop',preventDefault); targetArea.ondragenter = function(e){this.style.outline = "1px solid black";} targetArea.ondragleave = function(e){this.style.outline = "";} //拖拽選中 targetArea.ondrop = function(e){ e = e || event; this.style.outline = ""; var file = e.dataTransfer.files[0]; base64(file); } </script>