上一次我們通過File API 裏面的 FileReader類型裏的readAsText,readAsDataURL等方法來讀取文件File。但是如果一個文件十分大的時候,或者只需要讀取部分內容,如(文本文件),那麼我們就可以通過這次介紹的slice方法對文件進行分割成二進制Blob對象。這次我們還是根據上次說的圖片上傳爲例,講解一下如何分割讀取圖片的。
一 : 介紹Blob對象
Blob對象自帶屬性
1.size 表示二進制對象的大小
2.type 表示二進制對象的類型 (如果是File對象分割的,會繼承type屬性)
3.slice 方法 分割文件
二:介紹blob.slice方法
1. 方法介紹 : blob.slice(); 屬於Blob對象的一個方法,而File對象是繼承Blob對象的,因此File對象也含有slice方法
2. 參數介紹 : blob.slice(startByte,endByte); 這裏需要注意它的參數,第一個參數startByte表示文件起始讀取Byte字節,第二個參數則是結束讀取字節。這裏重點注意一下第二個參數,一開始我以爲它是讀取的長度,因爲是在閱讀《javascript高級程序設計》時它的例子介紹用length作爲形參。結果我在進行文件分割上傳的時候,一直獲取不到第二次請求後的數據。
3.返回值 : newBlob = blob.slice(startByte,endByte); 它返回的仍然是一個Blob類型。
三 : 兼容slice方法
function blobSlice(blob,startByte,endByte){
if(blob.slice){
return blob.slice(startByte,endByte);
}
// 兼容firefox
if(blob.mozSlice){
return blob.mozSlice(startByte,endByte);
}
// 兼容webkit
if(blob.webkitSlice){
return blob.webkitSlice(startByte,endByte);
}
return null;
}
四 : 分割文件上傳
曾經我有介紹過如何將File對象指定的文件上傳到服務器中,其中就是通過了FormData對象封裝表單數據,通過Ajax請求進行傳輸。在這裏我也要強調一下,一般的 jQuery庫和zepto庫是不支持FormData對象.因此我們想要調用它們的方法進行發送是不行的,除非你使用了某某插件。既然這樣我們就自己用原生的方法把文件上傳至服務器吧。
我們還是借用上次寫好的一個生產XMLHttpRequest對象的方法。
function createXHR(){
if( typeof XMLHttpRequest != "undefined"){
return new XMLHttpRequest();
}
if(typeof ActiveXobject == "undefined"){
throw new Error(" not support ");
}
//判斷是否爲 IE
if(typeof arguments.callee.activeString != "string"){
var versions = ["MSXML2.XMLHttp.6.0","MSXML2.XMLHttp3.0","MSXML2.XMLHttp"],
i,len;
for (var i = 0;i<versions.length;i++) {
try{
new ActiveXobject(versions[i]);
arguments.callee.activeString=versions[i];
break;
}catch(ex){
// no action
}
};
}
return new ActiveXobject(arguments.callee.activeString);
}
接下來可以去看看上一個關於本地加載圖片的例子。我們就是要豐富一下它的上傳按鈕的事件。我們再來看看這個頁面的結構。
<body>
<article>
<header id="header"><h1>讀取部分內容</h1></header>
<section class="box">
<form id="form" enctype='multipart/form-data' method='post' action='#'>
<div class="upload-label"><h2>請選擇文件</h2></div>
<div class="upload-box add-button"></div>
<button class="upload-button" type="submit">上傳</button>
<input type="file" name="files" id="files" >
</form>
</section>
</article>
</body>
1.綁定click 事件。
$(function(){
var maxlen = 1,
filelist = [], //文件列表
targetId = 'files',
isUpload = false;
/*上回介紹的本地讀取圖片方法 add filter 方法 就不在介紹 */
$(".add-button").bind('click',add);
$("#files").bind('change',filter);
$(".upload-button").bind("click",function(e){
e.preventDefault();
if(filelist.length <= 0){ /* 沒有需要上傳的文件 */
return ;
}
if(!isUpload){ /*文件未上傳*/
//測試 只發送第一個filelist數組的第一個file對象
uploadImage(targetId,filelist[0]);//執行uploadImage方法
}else{
/* '文件正在上傳'*/;
}
});
// 修改了addImage方法,這個方法是被filter方法所調用
function addImage(file){
//給文件對象添加了一個id標示
file.id = (file.lastModifiedDate + "").replace(/\W/g, '')+ file.size;
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function(e){
// 顯示圖片
reader.myload.wrapInner('<img style="width:100%;height:100%; padding:.2rem;" src="'+reader.result+'" alt="'+file.name+'" />');
delete reader ;
}
reader.onprogress =function(e){
// 顯示加載進度狀態
if(e.lengthComputable){
reader.myload = reader.myload || $('<div class="upload-box" id="'+file.id+'"></div>').insertBefore('.add-button');
reader.myload.text( Math.ceil((e.loaded / e.total) * 100) +"%");
console.log(e.loaded / e.total);
}
}
}
function uploadImage(target,file){
/*詳細代碼在後面介紹*/
}
});
大家肯定是來看slice方法使用的,至於後面的都是例子了,寫的不是很好,我就挑重點的說了。核心就是將File對象分割成小的blob對象,通過FormData對象的append方法添加至表單中,因爲是分割發送的所以要保證文件發送在服務端的唯一性。上面我自己添加的 file.id 只是作爲一個示範id,可以有許多改進。(問題大大的存在)
下面我們來看看uploadImage方法的代碼
function uploadImage(target,file){
if(file == undefined){
AnimateUtils.info('至少選擇一個文件');
return ;
}
var formData,
//創建一個XMLHttpRequest對象
xhr = createXHR(),
//服務端接收tmp文件的鍵名
targetId = target;
//創建一個攜帶數據的FormData對象
function createData(data){
var formData = new FormData();
for(var name in data){
formData.append(name,data[name]);
}
return formData;
}
// 數據封裝
var data = {
'startbyte' : 0,
'loadsize' : 1024 * 128,
'totalsize' : file.size,
'filetype' : file.type,
'filename' : file.name,
'key' : targetId
};
//新的文件名
data['fileid'] = file.id;
//新的文件名後綴
data['suffix'] = file.name.substr(file.name.lastIndexOf('.'));
//分割的文件二進制對象
data[targetId] = blobSlice(file,data.startsize,data.startbyte+data.loadsize);
//封裝值FormData對象中
formData = createData(data);
//ajax請求前的參數設置
xhr.open('post','upload.php');
xhr.onload = function (){
if (this.status == 200) {
// 要求傳遞的爲json格式數據
var json = JSON.parse(xhr.responseText);
if( !json.complete ){
//尚未完全上傳, 更新下次上傳數據
data.startbyte = json.nextbyte;
data.loadsize = json.loadsize;
data[targetId] = blobSlice(file,data.startbyte,data.startbyte+json.loadsize);
//生成新的表單對象
formData = createData(data);
// 延時 1s發送請求, 可自定義
setTimeout(function(){
//再次發送
xhr.open('post','upload.php');
xhr.send(formData);
},1000);
}else if(json.complete){
// 上傳成功 刪除 filelist數組的指定的file對象
// 設置上傳的狀態爲未上傳.
isUpload = false;
}
}
};
//開始發送數據
xhr.send(formData);
isUpload = true;
}
如果大家想要加點什麼duangduang的效果也是可以的,不過我寫的效果一般,就不拿出來說了。這裏比較重要的就是那個data數據裏面的內容.
2.data數據介紹
{
fileid : 上傳後保存的文件id
startbyte : 起始的文件位置 針對 file對象
loadsize : 準備分割的大小
totalsize : 整個文件的大小
filetype : 文件類型
filename : 文件原始名稱
suffix : 文件後綴名
key :上傳至服務器的file文件鍵名
targetId : 文件二進制對象
}
其實有很多數據可以不必要傳送的,測試的時候就把相關的數據都放上去了。
3.回送的json數據介紹
回送的時候有兩種情況,一種是未全部上傳完,另外一種就是以及全部上傳完畢。
未上傳
{
complete : false,
nextbyte : xxx 下一次需要上傳的其實位置
loadsize : 下一次需要讀取的大小
}
上傳完成
{
complete : true
imageURL : 文件上傳後服務器的地址
}
介紹到這裏,我們就需要去看看服務器端的代碼了,由於沒有用任何框架,也沒有太多判斷變量類型和請求源,因此肯定不是安全的,但還是比較可用的.
<?php
$filename = $_POST['fileid'].$_POST['suffix'];
$uploadDir = "/upload/img/";
//驗證上傳類型是否合法
if(($_POST['totalsize'] - $_POST['startbyte']) > 0 || intval($_POST['loadsize']) != 0){
//上傳文件的鍵名
$key = $_POST['key'];
$uploadDir = $_SERVER['DOCUMENT_ROOT'].$uploadDir;
$filename = $uploadDir.$filename;
//以追加的形式寫入文件 所以fileId 十分關鍵,如果服務端已經存在的話,會導致文件再次填寫,源文件破壞
file_put_contents($filename,file_get_contents($_FILES[$key]['tmp_name']), FILE_APPEND);
$nextbyte = $_POST['loadsize'] + $_POST['startbyte'];
$lesssize = $_POST['totalsize'] - $nextbyte;
// 檢測是否需要更改 loadsize大小
$loadsize = ($_POST['loadsize'] - $lesssize) > 0 ? $lesssize : $_POST['loadsize'];
echo json_encode(
array( 'complete'=>false,
'nextsize'=> $nextbyte,
'loadsize'=> $loadsize
));
}else{
$imageURL = $uploadDir.$filename;
echo json_encode(array('complete'=>true,'imageURL'=>$imageURL));
}
exit;
?>
這裏我們的代碼就介紹完了 ,實際上並不只是講了blob.slice方法,更多的是講了一個文件分割上傳的功能,但是我們沒有記錄下這個文件上傳的一些信息,當瀏覽器刷新或者意外關閉的時候,服務端已經上傳的內容還在,但客戶端又必須重新上傳纔行,如果要解決這個問題,那就是斷點續傳的功能了。主要用了一些可以進行本地存儲的localstorage等存儲。保存了許多重要的信息,還有如果想要添加什麼暫停上傳,取消上傳,繼續上傳等等,就需要許多細節上的考慮了。我們這個例子說真的就是一個簡單的引導和使用slice方法.結合javascript其它處理,就能做很多事情了。
下面截一些圖片來看看做的簡單的效果。
文件上傳完畢後顯示圖片
服務器文件存儲名稱
關於文件File API 的內容大致介紹到這裏,還有關於ArrayBuffer對象的認識,沒有接觸過,所以現在也不好做分享。