在Web項目中,我們可能遇到需要利用Ajax來獲取圖片的情況。因爲客戶端處理的是圖片文件的二進制流,所以可利用Blob和File API來將圖片轉爲URL,賦值給img的src屬性來解決這個問題。本文總結Ajax獲取圖片的兩種方式,即針對XMLHttpRequest Level 1和Level 2給出解決方案。(注意:若無說明,下文中所有xhr都代表XMLHttpRequest 對象)。PS:最近想到這種需求,於是閱讀一些資料,整了一天,寫了本篇文章。如有錯誤,請不吝指正。題外話:寫博客貴在堅持呀!!!
首先給出html結構,因爲重點講Ajax,所以用一個很簡單的例子來說明,頁面只有一個button和一個img。
<!--login.html的其他代碼省略 --> <input type="button" id="btn"> <!--獲取的圖片將在此顯示--> <img id="preview">
因爲Ajax要和後臺交互,此處將使用Node.js完成後臺的邏輯,邏輯同樣很簡單。只需對
/
和/upload
做處理即可。Ajax將請求/upload
的內容。當請求時,發送給客戶端圖片。代碼如下:var http=require('http'); var fs=require('fs'); http.createServer((req,res)=>{ console.log(`${req.method}:${req.url}`); if(req.url=='/'){ res.writeHead(200,{'Content-Type':'text/html,charset=utf-8'}); fs.createReadStream('login.html').pipe(res); }else if(req.url=='/upload'){ if(req.method.toLowerCase()=='get'){ fs.readFile('images/captcha.0953f.png',(err,file)=>{ res.setHeader('Content-Length',file.length); console.log(file.length); res.writeHead(200,{'Content-Type':'image/png'}); res.end(file); }) }else{ res.end('success'); } }else{ fs.readFile('.'+req.url,(err,file)=>{ if(err){ res.writeHead(404); res.end('Not found!'); } fs.createReadStream('.'+req.url).pipe(res); }) } }).listen(3000);
Ajax請求圖片的兩種方式
首先公共代碼如下:window.onload=function(){ var btn=document.getElementById('btn'); btn.onclick=function(){ sendRequest(); } }
XMLHttpRequest level 1(兼容舊瀏覽器):利用
overrideMimeType()
方法,將xhr設置爲xhr.overrideMimeType('text/plain;charset=x-user-defined');
。其中text/plain
表示把響應作爲普通文本來處理;charset=x-user-defined
表示使用用戶自定義的字符集,以告訴瀏覽器不要去解析數據,直接返回未處理過的字節碼。此步驟必須,不可忽略。之後就可以通過xhr.responseText
取得響應文本,然後將其轉爲二進制數據,傳遞給Blob構造函數。而後,通過FileReader
的readAsDataURL()
方法創建URL賦值給圖片的src屬性即可。具體代碼如下:function sendRequest(){ var xhr=new XMLHttpRequest(); xhr.onload=function(){ if(xhr.status>=200 && xhr.status<300){ var binStr = this.responseText; //console.log(this.response==this.responseText);//true var arr = new Uint8Array(binStr.length); for(var i = 0, l = binStr.length; i < l; i++) { arr[i] = binStr.charCodeAt(i); //arr[i] = binStr.charCodeAt(i) & 0xff; } //console.log(binStr.charCodeAt(0).toString(16)); //console.log(arr[0].toString(16)); var blob=new Blob([arr.buffer],{type:'image/png'}) loadImage_file(blob); }else{ console.log('error'); } } xhr.open('get','/upload',true); //兼容老瀏覽器。必須,以文本格式接受,字符集自定義 xhr.overrideMimeType('text/plain;charset=x-user-defined'); xhr.send(null); } function loadImage_file(blob){ var fr=new FileReader(); fr.readAsDataURL(blob); fr.onload=function(e){ var preview=document.getElementById('preview'); preview.src=e.target.result; } }
該方法關鍵點是把響應文本轉爲二進制數據,利用字符串的
charCodeAt(index)
方法可以得到index位置的字符碼,遍歷整個字符串將其編碼保存至Uint8Array
中,而後傳遞給Blob來重構URL。關於arr[i] = binStr.charCodeAt(i) & 0xff;
,表示在每個字符的兩個字節之中,只保留後一個字節,將前一個字節扔掉。原因是瀏覽器解讀字符的時候,會把字符自動解讀成Unicode的0xF700-0xF7ff區段。但因爲我用的是Uint8Array
,它會自動截取低8位,所以做不做與運算也就無所謂了。XMLHttpRequest level 2:設置xhr的responseType屬性,指定爲
blob
(或ArrayBuffer
,如設置爲ArrayBuffer
,還要轉Uint8Array
,然後轉成blob
),例如,xhr.responseType='blob';
。這樣就可以通過xhr.response
來獲取該blob對象。注意不能用xhr.responseText
來獲取,因爲該屬性只在響應類型是“text”時有效,強行獲取只會得到錯誤:Uncaught InvalidStateError: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'blob').
。該方法響應blob對象,所以無需對響應再做其他處理,只需傳給FileReader
的readAsDataURL()
就可以了。代碼如下:function sendRequest(){ var xhr=new XMLHttpRequest(); xhr.onload=function(){ if(xhr.status>=200 && xhr.status<300){ var blob = this.response; //loadImage_file()方法與前面相同 loadImage_file(blob); }else{ console.log('error'); } } xhr.open('get','/upload',true); xhr.responseType='blob'; xhr.send(null); }
從中可以看出,代碼量大大減少,語法簡潔而清晰。
總結:兩種方式各有各的有點,方法一兼容性好,方法二清晰簡潔。具體用那種,還看各位的想法了。
最後,給出幾篇關於XMLHttpRequest的優秀文章:
1. 你真的會使用XMLHttpRequest嗎?
2. XMLHttpRequest Level 2 使用指南