Skip to content

文件上传

大文件上传方法(切片上传)

文件拖拽上传是怎么实现的?

  • dragstart:事件主体是被拖放元素,在开始拖放被拖放元素时触发。
  • drag:事件主体是被拖放元素,在正在拖放被拖放元素时触发。
  • dragenter:事件主体是目标元素,在被拖放元素进入某元素时触发。
  • dragover:事件主体是目标元素,在被拖放在某元素内移动时触发。
  • dragleave:事件主体是目标元素,在被拖放元素移出目标元素是触发。
  • drop:事件主体是目标元素,在目标元素完全接受被拖放元素时触发。
  • dragend:事件主体是被拖放元素,在整个拖放操作结束时触发。
html
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <title>首页</title>
    <style>
      .btn {
        width: 100px;
        height: 100px;
        border: 1px dashed black;
        margin: 50px auto;
        cursor: pointer;
      }
      #upload {
        display: none;
      }
    </style>
  </head>

  <body>
    <div id="box">
      <input type="file" id="upload" />

      <div class="btn" id="drop"></div>
    </div>

    <script>
      const input = document.querySelector("#upload");
      let files;
      input.addEventListener("change", (e) => {
        files = e.target.files;
        upload();
      });

      function upload() {
        console.log(files);
      }

      document.querySelector(".btn").addEventListener("click", (e) => {
        input.click();
      });

      const dropBox = document.querySelector("#drop");
      dropBox.addEventListener("dragenter", dragEnter, false);
      dropBox.addEventListener("dragover", dragOver, false);
      dropBox.addEventListener("drop", drop, false);

      function dragEnter(e) {
        e.stopPropagation();
        e.preventDefault();
      }
      function dragOver(e) {
        e.stopPropagation();
        e.preventDefault();
      }

      function drop(e) {
        // 当文件拖拽到dropBox区域时,可以在该事件取到files
        event.preventDefault();
        files = e.dataTransfer.files;
        upload();
      }
    </script>
  </body>
</html>

文件预览

  • 通过 FileReader 实现
js
const input = document.querySelector("#upload");
input.addEventListener(
  "change",
  function () {
    const files = this.files;
    const fileList = Object.entries(files).map(([_, file]) => {
      const reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = (e) => {
        file.preview = e.target.result;
      };
      return file;
    });

    /*
     *  file中的preview存的就是可以预览使用url,如果你需要保证fileList的顺序,
     *  请不要使用这种方式,你可以采用索引的方式存储,来保证它的顺序一致性
     */
    console.log(fileList);
  },
  false
);
  • 通过 window.URL.createObjectURL()实现
js
const input = document.querySelector("#upload");
input.addEventListener(
  "change",
  function () {
    const files = this.files;
    const fileList = Object.entries(files).map(([_, file]) => {
      const preview = window.URL.createObjectURL(file);
      file.preview = preview;
      // 需要在合适的实际进行销毁,否则只有在页面卸载的时候才会自动卸载.
      // window.URL.revokeObjectURL(preview);
      return file;
    });

    /*
     *  file中的preview存的就是可以预览使用url
     */
    console.log(fileList);
  },
  false
);

限制上传的文件类型只能是图片呢?

  • accept 属性

属性是一个字符串,它定义了文件 input 应该接受的文件类型。这个字符串是一个以逗号为分隔的唯一文件类型说明符列表。由于给定的文件类型可以用多种方式指定,因此当你需要给定格式的文件时,提供一组完整的类型指定符是非常有用的。

一个以英文句号(".")开头的合法的不区分大小写的文件名扩展名。例如: .jpg,.pdf 或 .doc。

一个不带扩展名的 MIME 类型字符串。

字符串 audio/*, 表示 “任何音频文件”。

字符串 video/*,表示 “任何视频文件”。

字符串 image/*,表示 “任何图片文件”。

accept 属性不验证所选文件的类型;它只是为浏览器提供提示来引导用户选择正确的文件类型。用户仍然可以(在大多数情况下)在文件选择器中切换一个选项,使其能够覆盖此选项并选择他们希望的任何文件,然后选择不正确的文件类型。

html
<input type="file" id="avatar" name="avatar" accept="image/png, image/jpeg" />
  • 事件
js
const fileTypes = ["image/jpeg", "image/pjpeg", "image/png"];

// 验证格式
function validFileType(file) {
  return fileTypes.includes(file.type);
}
// 计算文件大小
function returnFileSize(number) {
  if (number < 1024) {
    return number + "bytes";
  } else if (number >= 1024 && number < 1048576) {
    return (number / 1024).toFixed(1) + "KB";
  } else if (number >= 1048576) {
    return (number / 1048576).toFixed(1) + "MB";
  }
}
const input = document.querySelector("input[type=file]");
input.addEventListener("change", upload);
function upload(e) {
  let file = e.target.files[0];
  if (validFileType(file.type)) {
  } else {
    console.log("文件类型错误");
    e.target.value = "";
    return;
  }
}

大型文件上传如何切片?

参考:https://mp.weixin.qq.com/s/B4Q3b8MWLCAC8KVprLHMPw

文件上传一般是基于两种方式,FormData 以及 Base64

基于 FormData 实现文件上传

js
let formData = new FormData();
formData.append("file", file);
formData.append("filename", file.name);
// ... 增加字段
// 上传

基于 Base64 实现文件上传

js
// 转换为Base64
export changeBASE64=(file) => {
    return new Promise(resolve => {
        let fileReader = new FileReader();
        fileReader.readAsDataURL(file);
        fileReader.onload = ev => {
            resolve(ev.target.result);
        };
    });
};

let data = await instance.post('/upload_single_base64', {
// encodeURIComponent(BASE64) 防止传输过程中特殊字符乱码,
// 同时后端需要用decodeURIComponent进行解码
    file: encodeURIComponent(BASE64),
    filename: file.name
}, {
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
});

上传进度

文中用到的请求库是 axios,进度管控主要基于 axios 提供的 onUploadProgress 函数进行实现,这里一起看下这个函数的实现原理 :xhr.upload.onprogress

  • loaded 已经传输的数据量
  • total 总量

大文件上传

1、上传前判断文件是否存在(hash 值),判断上传完成还是上传部分 2、服务器已经存在,则上传完成,没有上传完成,返回已经上传列表(续传) 3、文件切片 4、依次上传

File.prototype.slice() / Blob.prototype.slice()

参考文章

Released under the MIT License.