【javascript】File API slice方法對File文件分割 - 2

上一次我們通過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對象的認識,沒有接觸過,所以現在也不好做分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章