1

重新整理文件上传

 2 years ago
source link: https://segmentfault.com/a/1190000040762748
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

重新整理文件上传

文件上传对于前端来说应该是既陌生又熟悉,每次在做文件上传的时候无论是文件上传图片还是上传其他类型文件,如果文件相对来说比较小的情况可以把文件转换成文件流传输到服务器,为了能够更好的完善上传文件功能,做了一些调研并整理一了下。

了解File对象

目前前端暂不支持操作本地文件,所以只能用户主动触发才能获取到用户所选择的File对象。用户可以通过三种方法操作触发:

  1. 通过input type="file"选择本地文件
  2. 通过拖拽的方式把文件拖过来
  3. 在编辑框里面复制粘贴

通过input获取

当然了第一种方法是目前前端使用最为普遍的,通过input的类型,可以快速拿到用户所选择的File对象。

HTML代码如下:

<form>
  <input type="file" id="fileInput" name="fileContent">
</form>

然后通过FormData(文末会稍加解释)对象获取到整个表单的内容:

document.getElementById("fileInput").onchange = function(){
  let formData = new FormData(this.form);
  formData.append("fileName", this.value);
  console.log(this.value);
  console.log(formData);
}

代码中分别打印了input.valueformDatainput.value所打印出来的是一个虚拟的路径,是无法通过或者路径访问到用户所选择的文件的。然而formData打印出来的则是一个空对象,我们所看到的是空对象,并不代表整个对象就是空的,只是浏览器对该对象进行了出了,无法对文件进行操作,只能通过append添加一些字段。

//  FormData
{
    __proto__: FormData
}

说了这么多还是没有说到File对象,其实当用户选择完文件之后,File对象的实例就已经创建了,存放到了对应input DOMfiles中。

在使用input type="file"的时候,可以在浏览器上看到一个浏览器默认的按钮,貌似看起来不是那么特别的友好。笔者对于这个问题处理如下:

<button id="btn"></button>
document.getElementById("btn").onclick = function(){
    const oInput = document.createElement("input");
    oInput.setAttribute("type","file");
    oInput.click();
    oInput.onchange = function(){
        console.log(this.files[0])
    }
}
//  File输出结果
{
    lastModified: 1600000000000,
    lastModifiedDate: Thu Sep 30 2021 15:11:10 GMT+0800 (中国标准时间),
    name: "logo.jpg",
    size: "20000",
    type: "image/jpg",
    webkitRelativePath: "",
    __proto__: File
}

当然File只是存放于input DOM中,使用哪种方式获取都是可以的。我们所看到的File对象,其实是File的实例,包含了修改时间,文件名、文件大小等信息。

由于我们所获取到的File对象,所以没有办法直接展示在页面中,但是像图片这种文件又需要预览,我们就需要用到FileReader(文末介绍)对象对File对象来进一步处理。

通过实例化FileReader调它的readAsDataURL并把File对象传给它,监听它的onload事件,load完读取的结果就在它的result属性里了。它是一个base64格式的,可直接赋值给一个imgsrc

document.getElementById("btn").onclick = function(){
    const oInput = document.createElement("input");
    oInput.setAttribute("type","file");
    oInput.click();
    oInput.onchange = function(){
        let fileReader = new FileReader();
        let { type:fileType } = this.files[0];
        fileReader.onload = function(){
            if(/^image/.test(fileType)){
                const img = document.createElement("img");
                console.log(this.result);
                img.setAttribute("src",this.result);
                document.body.appendChild(img);
            }
        }
        fileReader.readAsDataURL(this.files[0]);
    }
}

使用FileReader除了可读取为base64之外,还能读取为以下格式:

// 按base64的方式读取,结果是base64,任何文件都可转成base64的形式
fileReader.readAsDataURL(this.files[0]);

// 以二进制字符串方式读取,结果是二进制内容的utf-8形式,已被废弃了
fileReader.readAsBinaryString(this.files[0]);

// 以原始二进制方式读取,读取结果可直接转成整数数组
fileReader.readAsArrayBuffer(this.files[0]);

其它的主要是能读取为ArrayBuffer,它是一个原始二进制格式的结果。它对前端开发人员也是透明的,不能够直接读取里面的内容,但可以通过ArrayBuffer.length得到长度,还能转成整型数组,就能知道文件的原始二进制内容。

Drop读取文件

通过Drop如何才能读取到文件内容呢?如果说通过input是传统的话,那么通过Drop获取文件就只能说是流行了。

HTML:

<div class="drop-container">
    drop your image here
</div>

javascript:

const onImageDrop = document.getElementById("img-drop");
onImageDrop.addEventListener("dragover",function(event){
  event.preventDefault();
})
onImageDrop.addEventListener("drop", function(event){
  event.preventDefault();
  console.log(event);
  let file = event.dataTransfer.files[0];
  let fileReader = new FileReader();
  let { type:fileType } = file;
  fileReader.onload = function(){
    if(/^image/.test(fileType)){
      const img = document.createElement("img");
      img.setAttribute("src",this.result);
      document.body.appendChild(img);
    }
  }
  fileReader.readAsDataURL(file);
  let formData = new FormData();
  formData.append("fileContent", file);
});

数据在drop事件的event.dataTransfer.files里面,拿到这个File对象之后就可以和输入框进行一样的操作了,即使用FileReader读取,或者是新建一个空的formData,然后把它appendformData里面。

粘贴读取文件

还有一种方式则是通过粘贴的形式获取到文件内容,这种读取文件的方式,通常实在一个编辑框里面操作,把divcontenteditable设置为true:

<div contenteditable="true">
  hello, paste your image here
</div>

粘贴的数据是在event.originalEvent.files里面:

document.getElementById("editor").addEventListener("paste",function(event){
  let file = event.clipboardData.files[0];
  console.log(file)
});

通过三种方法都可以获取到File对象,目前对于前端来说有两种常用的上传文件方法方法。

  1. 整文件上传

整文件上传

其实对于上传整文件相对来说是比较简单的,因为不需要太多的操作,通过FormData对象,把相应的文件传输给对应的地址即可。

document.getElementById("btn").onclick = function(){
    const oInput = document.createElement("input");
    oInput.setAttribute("type","file");
    oInput.click();
    oInput.onchange = function(){
        const formdata = new FormData();
        formdata.append("file",file);
        const xhr = new XMLHttpRequest();
        xhr.open("post","上传文件地址");
        //获取上传的进度
        xhr.upload.onprogress = function (event) {
            if(event.lengthComputable){
                //  进度
                const percent = event.loaded/event.total *100;
            }
        }
        //将formdata上传
        xhr.send(formdata);
    }
}

切片上传

文件太大的时候使用普通方式上传就不太靠谱了,长时间的等待回让用户失去耐心,甚至导致用户的流失。这个时候就需要用到切片上传,把文件切割成几个小的文件,分别上传到服务端。切片上传相对普通文件上传来说难度要大一些,因为涉及到文件分割,和后端的配合,这里只讲述前端内容,对后端如何实现不做赘述(后续会使用node实现)。

document.getElementById("btn").onclick = function(){
  const oInput = document.createElement("input");
  oInput.setAttribute("type","file");
  oInput.click();
  oInput.onchange = function(){
    const file = oInput.files[0];
    const perFileSize = 2097152;
    const blobParts = Math.ceil(file.size / perFileSize);
    let progress = 0;
    let blobSize = 0;
    for (let i = 0; i < blobParts; i++) {
      const formData = new FormData();
      const _blob = file.slice(i * perFileSize, (i + 1) * perFileSize);
      formData.append('_blob', _blob);
      formData.append('filename', file.name);
      formData.append('index', i + 1);
      formData.append('total', blobParts);
      const xhr = new XMLHttpRequest();
      xhr.open("post","上传文件地址");
      xhr.onload = function onload() {
        blobSize += _blob.size;
        //  进度
        progress = parseInt((blobSize / file.size) * 100);
      };
      //    将formdata上传
      xhr.send(formdata);
    };
  }
}

上述内容中主要是通过file.slice对文件进行切割拆分,获取到切割后的Blod(文末介绍)对象,然后把Blod对象传输给后端即可,接下来就是后端对传输的内容进行处理了,这里暂时不做赘述。

对于这次文件上传的学习学到了很多东西,虽然都是基础性的东西,但是还是很有用的,对于组件的封装以及文件上传工具的封装,都是有很大的帮助的。

上面也只是举了一些简单的例子,具体的业务逻辑还是需要具体的分析的。很多东西并不是一概而论的。


注:关于FormData

FormData类型其实是在XMLHttpRequest2级定义的,它是为序列化表以及创建与表单格式相同的数据(当然是用于XHR传输)提供便利。FormData里面存储的数据形式,一对key/value组成一条数据,key是唯一的,一个key可能对应多个value。如果是使用表单初始化,每一个表单字段对应一条数据,它们的HTML name属性即为key值,它们value属性对应value值。

  1. 通过append(key, value)添加数据;
  2. 通过get(key)/getAll(key)获取对应的value;
  3. 通过set(key, value)设置修改数据;
  4. 通过has(key)判断是否对应的key值;
  5. 通过delete(key)删除数据;

注:关于FileReader

FileReader是前端进行文件处理的一个重要的web api,特别是在对图片的处理上。FileReader对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用FileBlob对象指定要读取的文件或数据。

FileReader读取文件方法如下:

  1. readAsText(file, encoding):以纯文本形式读取文件,读取到的文本保存在result属性中。第二个参数代表编码格式;
  2. readAsDataUrl(file):读取文件并且将文件以数据URI的形式保存在result属性中;
  3. readAsBinaryString(file):读取文件并且把文件以字符串保存在result属性中;
  4. readAsArrayBuffer(file):读取文件并且将一个包含文件内容的ArrayBuffer保存咋result属性中;

FileReader事件监控

  1. progress:每隔50ms左右,会触发一次progress事件;
  2. error:在无法读取到文件信息的条件下触发;
  3. load:在成功加载后就会触发;

注:关于Blod

Blob对象表示一个不可变的,原始数据的类似文件对象。Blob表示的数据不一定是一个JavaScript原生格式blob对象本质上是js中的一个对象,里面可以储存大量的二进制编码格式的数据。

Blob属性:

  1. isClosed 是否在该对象上调用过
  2. size 对象中所包含数据的大小
  3. type 对象所包含数据的MIME类型

Blob方法:

  1. close 关闭 Blob 对象,以便能释放底层资源
  2. slice 返回一个新的 Blob 对象,包含了源 Blob 对象中指定范围内的数据

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK