【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对象的认识,没有接触过,所以现在也不好做分享。

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