美文网首页
关于文件上传下载我所知道的全部内容

关于文件上传下载我所知道的全部内容

作者: Patrick浩 | 来源:发表于2019-01-01 16:13 被阅读0次

    文件上传是一个很基础的内容,有很多的应用场景,但是前端各种库和框架实在是太便利了,根本不用了解到用原生的是怎么实现的,一遇到问题就各种懵逼,最近刚好经历了几种文件上传的需求,就以此来作为开年的第一篇分享

    1. 表单上传

    在AJAX还不流行的年代,表单上传文件是基本操作。表单上传文件很简单,有两个需要重点关注的属性:

    1.1 enctype

    属性用于设定form表单提交的时候数据编码方式,一共有三种参数选择:

    1. application/x-www-form-urlencoded 发送前编码所有字符
    2. multipart/form-data 不对字符进行编码
    3. text/plain 空格转换为+,但是不会对字符进行编码

    如果想要使用文件上传,必须指定为第二个属性值:enctype=multipart/form-data

    1.2 multiple

    对于选择文件的时候如果想对文件进行多选,那么必须要设置<input type="file" multiple="multiple">

    一个比较完整代码片段

    <form action="http://localhost:3000/upload" method="POST" enctype="multipart/form-data">
        <input type="file" name="file" multiple="multiple">
        <input type="submit" value="submit"/>
    </form>
    

    2. AJAX上传

    如果要实现页面不刷新的文件上传,有两种常用的方案:

    1. <iframe>表单提交方案
    2. AJAX方案

    第一种方案在页面中嵌套一个<iframe>,将表单放置于<iframe>中,此时完成表单提交不会发生全局页面刷新。但是这个方案,随着AJAX的逐渐完善以及前后端分离和单页面应用的普及,轮为了很不常规的替代方案。

    2.1 基本内容

    实现AJAX上传,首先需要对XHR有所了解(如有不了解的可以参照MDN的学习文档AJAX开始

    XHR在发送数据的时候可以接受一个html5的新对象FormData,可以通过将包含文件的表单/活着将文件放到FormData中传递到后端接口,

    html:
    <form id="fileForm">
        <input type="file" name="file" multiple="multiple" onchange="changeFileChoose(event)">
        <input type="button" onclick="upload();" value="submit"/>
    </form>
    
    js:
    let formData = new FormData(document.getElementById('fileForm'));
    let xhr = new XMLHttpRequest();
    xhr.open('POST', 'http://localhost:3000/upload');
    xhr.setRequestHeader('Content-Type', 'multipart/form-data');
    xhr.send(formData);
    

    如果表单中每个文件想单独发送请求(发送多次请求),可以获取表单中文件信息并构建多个表单对象上传

    formData.getAll('file').filter(file => {
        return file.name
    }).forEach((file, index) => {
        let separateFormData = new FormData();
        separateFormData.set('file', file);
        xhr.send(separateFormData)
    })
    

    PS:在传递到时候注意设置请求头信息Content-type: multiple/form-data来支持文件上传操作

    2.2 上传进度

    将上传过程的上传进度告诉用户是一个很好的用户交互行为,一方面避免用户多次重复上传,另一方面也是对用户操作对反馈,告诉用户系统正在处理他的操作。

    监听文件上传进度,个人认为要么前端轮询获取后端的文件写入情况,要么前端有支持上传进度获取对事件,其实确实AJAX上传过程中提供了相关对象,获取到文件的网络传输情况的,所以在对上传结果要求并非十分严格的情况下,通过前端监听反馈进度已经足够了

    上传进度的监听需要使用xhr.upload对象的事件,利用监听xhr.upload.onprogress来实现上传进度的监听

    xhr.upload.onprogress = ev => {
        console.log(`upload loaded: ${ev.loaded}, total: ${ev.total}`);
        progress = ev.loaded * 100 / ev.total;
    }
    

    onprogress事件的event对象中包含前端已经传输的数据信息ev.loaded以及文件的总尺寸信息ev.total,利用这些信息就可以在页面中显示文件上传进度

    2.3 取消上传

    AJAX自身提供了取消操作,通过利用xhr.abort()方法来取消掉整个xhr的请求,当然如果仅仅想取消文件上传而不是取消整个AJAX过程,也可以使用xhr.upload.abort()单独的取消掉AJAX过程中的文件上传

    2.4 选择图片并上传预览

    <input type="file">onchange事件在选择文件发生变更的时候会触发,利用事件中的event对象的event.target.files,可以获取到当前选择的文件集合,遍历该集合,根据file.type来判断文件类型,并利用window.URL.createObjectURL(file)可以拿到转换过后的base64图片地址,最后再给图片img.src设置路径从而实现选择回显(图片可以使用createElement('img')body.appendChild(),也可以使用new Image()canvasdragImage()方法来实现绘制)

    /**
     * 验证图片类型
     * @param {*} type 文件类型
     */
    function validateImage(type) {
        return ['image/jpeg', 'image/png', 'image/jpg'].includes(type);
    }
    
    if (validateImage(file.type)) {
        let image = document.createElement('img');
        // URL.createObjectURL可以接受File, Blob, MediaSource对象
        image.style.height = '100px';
        image.style.width = '100px';
        image.src = window.URL.createObjectURL(file);
        document.body.appendChild(image);
    }
    

    PS:由于图片加载对浏览器来说是异步的过程,如果要对图片进行相关操作,请在img.onload操作以后执行

    3. 拖拽上传

    在了解AJAX上传的基础上,其实拖拽上传只需要知道如何获取到拖拽文件对象,就可以使用相同的方法进行上传了。
    拖拽也是有一系列事件,具体拖拽相关事件,可以参见接下来的分享或者MDN Drag and Drop API

    3.1 文件拖拽

    文件拖拽上传的关键在于,可以通过event.dataTransfer获取到拖拽信息。该对象存在的两个对象属性filesitems,如果拖拽的内容是文件,那么可以遍历files对象,就可以获得文件信息

    html:
    <div>
        <p>拖拽上传</p>
        <div id="fileArea" class="file_area">拖拽到此区域上传</div>
    </div>
    
    js:
    let fileArea = document.querySelector('#fileArea')
    fileArea.addEventListener('drop', ev => {
        let files = ev.dataTransfer.files
        for (let i = 0; i < files.length; i++) {
            // 调用ajax相关内容
            sendFile(files[i]);
        }
        // 防止浏览器直接打开文件
        ev.preventDefault();
    })
    

    3.2 目录拖拽

    突然某一天出现了目录拖拽的需求,以为和文件上传是同样可以通过files来获取,结果发现不行。这个时候需要使用另一个属性对象items,并利用File and Directory Entries API来处理items

    首先利用item.webkitGetAsEntry()/item.getEntry()获取到FileEntry,之后使用entry.createReader()获取到reader对象,之后reader.readEntries读取信息并递归分别处理文件和文件夹,如果是文件通过entry.file()的方式获取文件信息

    js:
    fileArea.addEventListener('drop', ev => {
        for (let i = 0; i < ev.dataTransfer.items.length; i++) {
            // 获取entry对象
            let entry = ev.dataTransfer.items[i].webkitGetAsEntry()
            if (entry) {
                scanFiles(entry, sendFile)
            }
        }
        // 防止浏览器直接打开文件
        ev.preventDefault();
    })
    
    function scanFiles (entry, callback) { // 浏览文件结构
        // 如果是文件目录,那么继续循环获取到目录下的文件
        if (entry.isDirectory) {
          let directoryReader = entry.createReader();
          directoryReader.readEntries(entries => {
            entries.forEach(entry => {
              scanFiles(entry, callback);
            })
          }, err => {
            console.log(err, err.message);
          })
        }
        // 如果是文件,安么添加到最后的文件数据集中
        if (entry.isFile) {
            i++
            entry.file(file => {
                callback(file, i);
            }, err => {
                console.log(err, err.message);
            })
        }
    }
    

    PS:

    1. 这里尤其要注意entry.file()方法,想要获取到文件信息只能在回调函数中获取
    2. 由于浏览器安全性问题,本地是不能直接访问文件系统的,所以,如果以上的例子不在服务端运行,会报错DOMException(这个问题花费了我N个小时),可以全局安装一个http-server来运行上面的代码

    4. 总结

    编程真的是一件很好玩的事情,最近看算法的基础,觉得真的很有意思,前端编程也一样,如果仅仅停留在使用组件上,真的很没意思,有时间可以多多看看各种原生的事件和方法,深入研究一下框架相当有意思。超级感谢MDN啊,基本上可以获取到所有想要的信息

    完整DEMO的:https://github.com/PatrickLh/file-upload

    5. 参考

    MDN XMLHttpRequest

    MDN File and Directory Entries API

    MDN HTML Drag and Drop API

    相关文章

      网友评论

          本文标题:关于文件上传下载我所知道的全部内容

          本文链接:https://www.haomeiwen.com/subject/cxwnlqtx.html