目录

目前实现上传的方式

浏览器小于等于IE9(低版本浏览器)使用下面的方式实现的

  1. flash实现(主流插件的方式,本文不涉及)
  2. form + iframe(项目中很少用到,本文不涉及)

form表单提交的方式是所有浏览器都支持的,借助iframe是为了实现不刷新界面上传

主流浏览器 + IE10+ 则是通过以下方式实现的上传

FormData + XHR2 + FileReader + canvas

FormData介绍

FormData接口提供了一种轻松构造一组表示表单字段及其值的键/值对的方法,然后可以使用XMLHttpRequest.send()方法轻松地发送这些值。如果将编码类型设置为“multipart/form-data”,则使用与表单相同的格式。

常用方法:

  1. FormData.append(0; // 添加键值对
  2. FormData.delete(); // 删除键值对
  3. FormData.entries(); // 返回允许遍历此对象中包含的所有键/值对的迭代器

...具体还有很多方法可以参考网站:FormData

XMLHttpRequest简介

使用XMLHttpRequest (XHR)对象可以与服务器交互。您可以从URL获取数据,而无需让整个的页面刷新。这使得Web页面可以只更新页面的局部,而不影响用户的操作。XMLHttpRequest在 Ajax 编程中被大量使用。

常用属性和方法

  1. const xhr = new XMLHttpRequest()
  2. // 常用属性
  3. xhr.onreadystatechange // 当readyState属性发生变化时调用的函数
  4. xhr. readyState // 存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。
  5. 0: 请求未初始化
  6. 1: 服务器连接已建立
  7. 2: 请求已接收
  8. 3: 请求处理中
  9. 4: 请求已完成,且响应已就绪
  10. xhr.responseText // 包含对请求的响应,如果请求未成功或尚未发送,则返回null
  11. xhr.timeout // 表示该请求的最大请求时间(毫秒),超过该时间请求会自动结束。
  12. xhr.upload // 表示上传过程。
  13. // 常用方法
  14. xhr.abort() // 中止请求
  15. xhr.open() // 初始化一个请求。该方法只能JavaScript代码中使用
  16. xhr.setRequestHeader() // 设置HTTP请求头的值
  17. xhr.send() // 发送请求。如果请求是异步的(默认),那么该方法将在请求发送后立即返回

更多关于XMLHttpRequest的信息点击:XMLHttpRequest

FileReader

对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。

其中File对象可以是来自用户在一个<input>元素上选择文件后返回的FileList对象,也可以来自拖放操作生成的 DataTransfer对象,还可以是来自在一个HTMLCanvasElement上执行mozGetAsFile()方法后返回结果。

属性:

  1. FileReader.error // 表示在读取文件时发生的错误
  2. FileReader.readyState // 表示FileReader状态的数字。0:还没有加载任何数据; 1:数据正在被加载;2:已完成全部的读取请求
  3. FileReader.result // 文件的内容。该属性仅在读取操作完成后才有效,数据的格式取决于使用哪个方法来启动读取操作。

事件处理

  1. FileReader.onabort= ()=>{} // 该事件在读取操作被中断时触发
  2. FileReader.onerror = ()=>{} // 该事件在读取操作发生错误时触发。
  3. FileReader.onload = ()=>{} // 该事件在读取操作完成时触发。
  4. FileReader.onloadstart = ()=>{} // 该事件在读取操作开始时触发
  5. FileReader.onloadend = ()=>{} // 该事件在读取操作结束时(要么成功,要么失败)触发
  6. FileReader.onprogress = ()=>{} // 该事件在读取Blob时触发

方法

  1. FileReader.abort() // 中止读取操作。在返回时,readyState属性为DONE
  2. FileReader.readAsArrayBuffer() // 开始读取指定的 Blob中的内容, 一旦完成, result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象
  3. FileReader.readAsDataURL() // 开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个data: URL格式的字符串以表示所读取文件的内容
  4. FileReader.readAsText() // 开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个字符串以表示所读取的文件内容。

form表单上传

  1. <form id="uploadForm" method="POST" action="upload" enctype="multipart/form-data">
  2. <input type="file" id="myFile" name="file" />
  3. <input type="submit" value="提交" />
  4. </form>

所有浏览器都支持的上传方式,且submit提交后页面会刷新。

action: 提交地址

enctype的常见类型(告诉服务器我们发送过去的数据是用哪种格式进行编码的)

  • application/x-www-form-urlencoded (默认数据编码方式)
  • multipart/form-data(复杂,但它允许在数据中包含整个文件,所以常用于文件上传)
  • text/plain(一般用于debug)

FormData + XHR2 + FileReader + canvas

【实现步骤】

  1. 监听一个input(type=‘file’)的onchange事件,这样获取到文件file;
  2. 将file转成dataUrl;
  3. 然后根据dataUrl利用canvas绘制图片压缩,然后再转成新的dataUrl;
  4. 再把dataUrl转成Blob;
  5. 把Blob append进FormData中;
  6. xhr实现上传。

HTML代码

  1. <input type="file" name="file" accept=“image/*” onchange='handleInputChange(event)'>

1、监听input的change事件

  1. function handleInputChange (event) {
  2. const file = event.target.files[0]; // 获取当前选中的文件
  3. const imgMasSize = 1024 * 1024 * 10; // 限制大小10MB
  4. // 检查文件类型
  5. if(['jpeg', 'png', 'gif', 'jpg'].indexOf(file.type.split("/")[1]) < 0){
  6. // 不支持该文件类型
  7. }
  8. // 文件大小限制
  9. if(file.size > imgMasSize ) {
  10. // 文件大小自定义限制
  11. }
  12. // 图片压缩处理函数
  13. transformFileToDataUrl(file);
  14. }

2、将file转成dataUrl

  1. function transformFileToDataUrl (file) {
  2. const imgCompassMaxSize = 200 * 1024; // 超过 200k 就压缩
  3. // 存储文件相关信息
  4. imgFile.type = file.type || 'image/jpeg';
  5. imgFile.size = file.size;
  6. imgFile.name = file.name;
  7. imgFile.lastModifiedDate = file.lastModifiedDate;
  8. // 封装好的函数
  9. const reader = new FileReader();
  10. // file转dataUrl是个异步函数,onload表示读取完成了
  11. reader.onload = function(e) {
  12. const result = e.target.result;
  13. if(result.length < imgCompassMaxSize) {
  14. compress(result, processData, false ); // 图片不压缩
  15. } else {
  16. compress(result, processData); // 图片压缩
  17. }
  18. };
  19. reader.readAsDataURL(file);
  20. }

3、canvas进行压缩的处理

  1. function compress (dataURL, callback, shouldCompress = true) {
  2. const img = new window.Image(); // new 一个图片对象
  3. img.src = dataURL; // 通过fileReader读取到的base64数据
  4. img.onload = function () {
  5. // 1、创建canvas上下文
  6. const canvas = document.createElement('canvas');
  7. const ctx = canvas.getContext('2d');
  8. // 获取图片宽高赋值给canvas绘图
  9. canvas.width = img.width;
  10. canvas.height = img.height;
  11. // 绘制出一张canvas图片
  12. ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  13. let compressedDataUrl;
  14. if(shouldCompress){
  15. compressedDataUrl = canvas.toDataURL(imgFile.type, 0.2); // 后面的系数是绘图输出图片质量
  16. } else {
  17. compressedDataUrl = canvas.toDataURL(imgFile.type, 1); // 不改变原图质量
  18. }
  19. document.getElementById('preview').appendChild(img);
  20. callback(compressedDataUrl); // 最后图片压缩好后,去进行base64 -> blob的转换(传递到后台)
  21. }
  22. }

4、把Blob append进FormData中;

  1. function processData (dataURL) {
  2. const binaryString = window.atob(dataURL.split(',')[1]); // window.atob对用base-64编码过的字符串进行解码
  3. const arrayBuffer = new ArrayBuffer(binaryString.length); // ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。ArrayBuffer 不能直接操作,而是要通过类型数组对象或 DataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。
  4. const intArray = new Uint8Array(arrayBuffer); // Uint8Array类型化数组表示一个由8位无符号整数组成的数组。内容初始化为0。一旦建立,您可以使用对象的方法或使用标准数组索引语法(即使用括号符号)引用数组中的元素。
  5. for (let i = 0, j = binaryString.length; i < j; i++) {
  6. intArray[i] = binaryString.charCodeAt(i);
  7. }
  8. const data = [intArray];
  9. let blob;
  10. // 通过兼容性判断,最后转换为二进制数据
  11. try {
  12. blob = new Blob(data, { type: imgFile.type });
  13. } catch (error) {
  14. window.BlobBuilder = window.BlobBuilder ||
  15. window.WebKitBlobBuilder ||
  16. window.MozBlobBuilder ||
  17. window.MSBlobBuilder;
  18. if (error.name === 'TypeError' && window.BlobBuilder){
  19. const builder = new BlobBuilder();
  20. builder.append(arrayBuffer);
  21. blob = builder.getBlob(imgFile.type);
  22. } else {
  23. throw new Error('版本过低,不支持上传图片');
  24. }
  25. }
  26. // blob 转 file
  27. const fileOfBlob = new File([blob], imgFile.name); // File 对象是特殊类型的 Blob,且可以用在任意的 Blob 类型的 context 中。
  28. const formData = new FormData(); // 把要传输的数据添加到FormData()对象中
  29. // type
  30. formData.append('type', imgFile.type);
  31. // size
  32. formData.append('size', fileOfBlob.size);
  33. // name
  34. formData.append('name', imgFile.name);
  35. // lastModifiedDate
  36. formData.append('lastModifiedDate', imgFile.lastModifiedDate);
  37. // append 文件
  38. formData.append('file', fileOfBlob);
  39. uploadImg(formData); // 调用xhr发送数据到后台
  40. }

5、xhr实现上传

  1. function uploadImg (formData) {
  2. const xhr = new XMLHttpRequest();
  3. // 进度监听
  4. xhr.upload.addEventListener('progress', (e)=>{
  5. console.log(e, e.loaded , e.total); // 可以利用这两个对象算出目前的传输比例
  6. }, false);
  7. xhr.onreadystatechange = function () {
  8. if (xhr.readyState === 4) {
  9. const result = JSON.parse(xhr.responseText);
  10. if (xhr.status === 200) {
  11. // 上传成功
  12. console.log(result);
  13. } else {
  14. // 上传失败
  15. }
  16. }
  17. };
  18. xhr.open('POST', '/upload' , true); // 中间"/upload"为后台上传地址(如果需要兼容性强可以使用限制的ajax库)
  19. xhr.send(formData); // 发送到后台
  20. }

代码github访问地址:上传实例代码

小结

通过本节内容,我们应该彻底的理解了前端上传是如何实现的,给出的代码实例虽然简单,但是已经是非常核心,我们可以通过这个版本去实现一个非常复杂的需求,譬如多图片上传,那么也就是遍历的调用transformFileToDataUrl这个方法去实现。在例如添加上拖拽文件到指定区域去上传,那么我们只需要了解下drag对象就可以很轻松的实现了。

js文件上传原理(form表单 ,FormData + XHR2 + FileReader + canvas)的更多相关文章

  1. 【文件上传】文件上传的form表单提交方式和ajax异步上传方式对比

    一.html 表单代码 …… <input type="file" class="file_one" name="offenderExcelFi ...

  2. Java中request请求之 - 带文件上传的form表单

    常用系统开发中总免不了显示图片,保存一些文件资料等操作. 这些操作的背后,就是程序员最熟悉的 enctype="multipart/form-data"类型的表单. 说起file类 ...

  3. Layer文件上传同时传递表单数据

    (1)index.html <!DOCTYPE html> <html> <head> <title>TODO supply a title</t ...

  4. Struts2文件上传(基于表单的文件上传)

    •Commons-FileUpload组件 –Commons是Apache开放源代码组织的一个Java子项目,其中的FileUpload是用来处理HTTP文件上传的子项目   •Commons-Fil ...

  5. jfinal文件上传和form表单值为null的解决方法

    今天使用jfinal做上传提交的时候,遇到一个问题:添加了上传功能,原来的form表单submit提交时所有值都为null了,研究了很长时间,终于发现 在jfinal上传时候,jsp加 enctype ...

  6. servlet文件上传2——复合表单提交(数据获取和文件上传)

    上传文件时表单enctype属性必须要更改为<enctype='multipart/form-data'>:采用post提交表单,元素需要有name属性: 利用第三方jar包(common ...

  7. 文件上传之form表单篇

    form表单上传文件 作为本系列的最后一篇,也是楼主知道的第三种文件上传的方式--隆重推出Form表单 这是最传统的上传文件,提交数据的方式 Html: <form action="/ ...

  8. 理解流方式上传和form表单上传

    流方式上传: $post_input = 'php://input'; $save_path = dirname( __FILE__ ); $postdata = file_get_contents( ...

  9. BootStrap fileinput.js文件上传组件实例代码

    1.首先我们下载好fileinput插件引入插件 ? 1 2 3 <span style="font-size:14px;"><link type="t ...

随机推荐

  1. Loj #2321. 「清华集训 2017」无限之环

    Loj #2321. 「清华集训 2017」无限之环 曾经有一款流行的游戏,叫做 *Infinity Loop***,先来简单的介绍一下这个游戏: 游戏在一个 \(n \times m\) 的网格状棋 ...

  2. Linux删除文件夹和修改文件名

    rm [选项] 文件 -f, --force 强力删除,不要求确认 -i 每删除一个文件或进入一个子目录都要求确认 -I 在删除超过三个文件或者递归删除前要求确认 -r, -R 递归删除子目录 -d, ...

  3. 文本分类实战(七)—— Adversarial LSTM模型

    1 大纲概述 文本分类这个系列将会有十篇左右,包括基于word2vec预训练的文本分类,与及基于最新的预训练模型(ELMo,BERT等)的文本分类.总共有以下系列: word2vec预训练词向量 te ...

  4. 洛谷 P1439 【模板】最长公共子序列

    \[传送门啦\] 题目描述 给出\(1-n\)的两个排列\(P1\)和\(P2\),求它们的最长公共子序列. 输入输出格式 输入格式: 第一行是一个数\(n\), 接下来两行,每行为\(n\)个数,为 ...

  5. JDK1.8源码(八)——java.util.HashSet 类

    在上一篇博客,我们介绍了 Map 集合的一种典型实现 HashMap ,在 JDK1.8 中,HashMap 是由 数组+链表+红黑树构成,相对于早期版本的 JDK HashMap 实现,新增了红黑树 ...

  6. SpringCloud(5)路由网关Spring Cloud Zuul

    一个简单的微服务系统如下图: 1.为什么需要Zuul Zuul很容易实现 负载均衡.智能路由 和 熔断器,可以做身份认证和权限认证,可以实现监控,在高流量状态下,对服务进行降级. 2.路由网关 继续前 ...

  7. 故障公告:docker swarm集群“群龙无首”造成部分站点无法访问

    今天傍晚 17:38-18:18 左右,由于 docker swarm 集群出现 "The swarm does not have a leader" 问题,造成博问.闪存.园子. ...

  8. 【Swift 3.0】iOS 国际化切换语言

    有的 App 可能有切换语言的选项,结合系统自动切换最简单的办法: fileprivate var localizedBundle: Bundle = { return Bundle(path: Bu ...

  9. 【北航软件工程】Alpha阶段前端页面编写及服务器部署

    前端页面编写 虽然之前对html语法有过一些了解,但是完全没有编写前端页面的经验,和我合作的czy大概也是这么个情况.在Alpha阶段的前端页面编写过程中,我们是摸着石头过河,html是个入门很快专精 ...

  10. Kubernetes — 深入解析Pod对象:基本概念(一)

    在上一篇文章中,我详细介绍了 Pod 这个 Kubernetes 项目中最重要的概念. 现在,你已经非常清楚:Pod,而不是容器,才是 Kubernetes 项目中的最小编排单位.将这个设计落实到 A ...