傲娇大少之——【JS实现文件的上传全功能】

liao一下JS的上传功能

讲真:我拼尽了一身的力气,过着平凡的一生。
拼命努力不是为了想做什么就做什么,只是为了想不做什么就不做什么。

前端开发绝对离不开上传功能,无论你开发的项目是什么类型都是如此。上传头像,图片,文档,PPT,导入表格,视频等等。

所以这门技术必须要掌握!我刚做前端的时候非常不喜欢上传下载,感觉相比操作数据来说,操作文档真的挺烦,但是又离不开,总是要做的。毕竟后端比你还烦,哈哈。

话不多说,开giao!

input标签实现上传功能

<input type="file"/>

目前通过浏览器上传的根本都是用input标签,所以万变不离其宗。用它!
想一下,实际开发中我们在做上传功能时,会触及那些需求:

  • 各种千奇百怪的上传样式
  • 限制类型
  • 限制上传文件大小
  • 获取上传图片的实际宽高
  • 图片演示
  • 支持多文件上传
  • 上传至服务器
  • 显示上传进度

很恶心哈,不过现在有很多主流的框架都封装好了这些功能,可以直接用,这些我们在这里就不提了,这里只讲述单纯的使用JS实现文件的上传。

好的,现在我们来解决以上需求,首先,要知道的是input type="file"上传标签拥有哪些属性:

属性名称描述
value用户指定的上传文件名称
required是否为表单必填项
accept允许上传的文件类型
multiple是否上传多个文件
files已选择的文件列表
capture捕获的图片或视频源

然后再看,它拥有的事件:

事件名称描述
onchange上传文件发生变化时触发

了解了这些之后,开始开发!

1. 样式问题

大家都知道input type="file"各浏览器的默认样式都很丑哈,肯定是不能直接使用的,一般我们伟大的UI设计师给我们的图图都是这样这样那样那样的:
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
所以这个时候,我们丑丑的input标签就需要隐藏起来了,点击上边这些图图后主动去触发input标签的点击事件。
大概酱:

<div class="your-style" onclick="this.nextElementSibling.click()">
<input type="file" style="display: none;"/>

关于nextElementSibling和nextSibling的区别,可以看最下边哈。

第一个问题解决,点击你设计好的样式的div或者其它元素,都可以弹出我们的上传选择文件框。

2. 限制类型

限制上传的文件类型。这个需求很常见哈,比如你想上传个头像,那就不能上传文档和视频吧,所以限制用户上传的文件类型还是很重要的,这时候,我们用到的就是原生的accept属性了。上面的表格中有介绍哈。

accept属性值为一个MIME 类型字符串,它规定了input能接受的上传文件类型。多个值的话用逗号分隔。(常见的MIME类型可以见最下边的拓展

咱们举例说明一下:

<!-- 只允许上传png格式的图片 -->
<input type="file" accept=".png"/> 
<input type="file" accept="image/png"/>
<!-- 允许上传固定几种格式的图片 -->
<input type="file" accept=".png, .gif, .jpg"/> 
<input type="file" accept="image/png, image/git, image/jpg"/>
<!-- 允许上传所有类型的图片 -->
<input type="file" accept="image/*"/> 

到此,限制上传文件类型的功能已经搞定了。当然如果这种方式不能满足你的需求,也可以在上传文件后获取文件的路径,然后判断文件的类型是否满足您的需求。下面会提及这个问题。

3. 限制大小

限制上传文件的大小。这个功能也非常常见和实用。
限制上传文件的大小,首先要知道的是用户选中文件的大小是多少,那么这个操作一定是在用户选中文件之后进行的。所以这个判断要在input的onchange函数中实现。

<input type="file" onchange="changeFile()" id="inputFile">
function changeFile() {
	let fileDom = document.getElementById('inputFile');
     // 获取上传文件路径,可以根据这个路径判断是否满足您特殊的文件类型的限制。
     console.log(fileDom.value); // C:\fakepath\jiayou.gif
     if (fileDom.value.indexOf('.gif') > 0) {
         console.log('不支持上传.gif类型的文件!');
         return false;
     }
     // 获取上传文件的大小,然后进行文件大小限制的判断。
     console.log(fileDom.files[0].size); // 获取上传文件大小,这个上传文件的大小是以字节为单位的(byte)
     // 一般我们的需求都是以MB为单位的,所以这里需要换算一下
     if (fileDom.files[0].size > 1 * 1024 * 1024) {
         alert('上传文件不得超过1M!');
         return false;
     }
}

4. 上传图片(实际宽高以及图片展示)

上传图片经常碰到的两个问题,就是需要根据图片的实际宽高来设置图片的适配比例。所以我们需要知道如何获取上传图片的实际宽高。

<input type="file" onchange="changeFile()" id="inputFile">
<img id="uploadImg" src=""/>
function changeFile() {
	let fileDom = document.getElementById('inputFile');
	let imgDom = document.getElementById('uploadImg'); // 页面的img对象
    let reader = new FileReader(); // 读取计算机文件对象,详见下边扩展
    reader.readAsDataURL(fileDom.files[0]);
    reader.onload = (e) => {
        let img = new Image(); // 创建图片对象实例
        img.src = e.target.result; // 设置图片的src为上传的图片
        img.onload = () => {
            console.log(img.width); // 750 ,得到上传图片的实际宽度为750px
            console.log(img.height); // 1280 ,得到上传图片的实际高度为1280px
        }
        imgDom.src = e.target.result; // 给img标签赋值,图片就能显示啦!
    }
}

在这里插入图片描述

5. 多文件上传

input标签原生就是支持多文件上传的,只需要设置multiple属性即可。如下:

<input type="file" multiple onchange="changeFile()" id="inputFile">
function changeFile() {
    let fileDom = document.getElementById('inputFile');
    let reader = new FileReader();
    let list = Array.from(fileDom.files); // 需要先将多文件对象转换成数组格式
    list.forEach(file => {
        reader.readAsDataURL(file);
        reader.onload = (e) => {
            // 你的代码
            console.log(e.target.result);
        }
    });
}

6. 上传至服务器并显示上传进度

上传至服务器,对于前端来说,只需要确定好,后端需要的文件类型即可。
已知 FileReader 读取文件的方式有:readAsArrayBuffer, readAsBinaryString, readAsDataURL和readAsText。所以根据后端接口需要的文件类型,设置文件的读取方式,然后传过去就行啦。

<input type="file" multiple onchange="changeFile()" id="inputFile">
function changeFile() {
    let fileDom = document.getElementById('inputFile');
    let file = fileDom.files[0];
    let reader = new FileReader();
    reader.readAsArrayBuffer(file); // 比如后端接口需要的文件是ArrayBuffer的格式
    reader.onload = (e) => {
        console.log(e.target.result);
    };
}

执行结果:把这个结果作为参数传过去就可以啦。
在这里插入图片描述
那接下来问题就来了,我们怎么获得上传的进度呢?
这个基本原理就是监听XMLHttpRequest对象的onprogress,即xhr.upload.onprogress。
当然了各种框架都对xhr进行了封装,所以你可以根据使用的框架选择不同的方法,不过原理都是一致的,在这里简单的说三种方法:

关于进度条的样式问题,很多UI框架都是可以直接用的,没有的话,也可以自己写,很简单,外边一个div,里边一个div,设置不同的颜色,动态改变里边div的宽度就可以啦。如下图:
在这里插入图片描述
代码:

  • js通过xhr监听文件上传进度
function changeFile() {
	let fileDom = document.getElementById('inputFile');
	let file = fileDom.files[0];
	let reader = new FileReader();
	reader.readAsArrayBuffer(file);
	let progressBar = document.getElementById('progressBar'); // 进度条(里面的div)
	reader.onload = (e) => {
	    let form = mew FormData();
	    form.append('file', e.target.result);
	    let xhr = new XMLHttpRequest(); // 创建XMLHttpRequest对象实例
	    xhr.open('post', '/url/upload'); // 设置http请求的方式和地址
	    xhr.onreadystatechange = function () { // 请求完成时的回调函数
	        if (xhr.status === 200){
	            progressBar.style.width = '100%'; // 设置进度条宽度为100%,上传成功。
	        } else {
	            console.log('上传出错!');
	        }
	    };
	    xhr.upload.onprogress = function (e) { // 监听上传进度
	        if (event.lengthComputable){
	            progressBar.style.width = e.loaded / e.total * 100 + '%'; 
	            // 修改进度条宽度百分比
	            // e.loaded 为当前已上传的大小,e.total为文件总大小。
	        }
	    };
    	xhr.send(form); // 执行上传
	};
}
  • jQuery通过ajax获取文件上传进度
let form = mew FormData();
form.append('file', e.target.result);
$.ajax({ 
    url: "/url/upload", 
    type: "POST", 
    data: form, 
    processData: false, // 必须false才会避开jQuery对 formdata 的默认处理
    contentType: false, // 必须false才会自动加上正确的Content-Type
    xhr: function(){
        myXhr = $.ajaxSettings.xhr();
        if(myXhr.upload){
          	myXhr.upload.addEventListener('progress',function(e) {
          		progressBar.style.width = e.loaded / e.total * 100 + '%';
          	}, false);
        }
        return myXhr;
    },
    success: function(res){ 
    // 请求成功
    },
    error: function(res) {
    // 请求失败
        console.log(res);
    }
}); 
  • axios+vue上传文件显示进度
    随便挑了一个当前主流的前端框架举个例子。
methods: {
  uploadFile: function (e) {
      let formData = new FormData();
      formData.append("file", file);
      let config = {
          onUploadProgress: function (e) { // 监听上传进度
              // 属性lengthComputable主要表明总共需要完成的工作量和已经完成的工作是否可以被测量
              if (e.lengthComputable) {
                  progressBar.style.width = e.loaded / e.total * 100 + '%';
              }
          }
      };
      axios.post("/url/upload", formData, config).then(function (data) {
          console.log('上传成功!');
      }).catch(function (err) {
          console.log(err);
     });
  }
}

其实原理都是一样的,本质上都是对xhr对象做的upload.onprogress的监听。
在这里插入图片描述

到此,前端关于上传的基本知识都捋了一遍了。那么老规矩,giao点乱七八糟的知识点玩玩……

拓展

1.多次上传同一文件也想触发onchange事件的解决办法。

这个需求,其实在实际开发中经常遇到,反正我是经常碰到。
一般是已经选中了上传文件,然后又重新选中该文件上传,这时input会认为上传的文件没有发生变化,即input的value没有发生改变,所以不会触发onchange事件,所以解决的方案就是:选中文件后,将input的value值设置为null即可,代码如下:

document.getElementById('uploadfile').value = null;

2. nextElementSibling和nextSibling的区别是什么?

这个还是需要了解一下的,毕竟浏览器自带的input file的样式太丑了,基本我们都会将其隐藏的,但是还得触发它的点击事件。

  • nextSibling属性返回元素节点之后的兄弟节点(包括文本节点、注释节点(包括回车、换行、空格、文本等等));
  • nextElementSibling属性只返回元素节点之后的兄弟元素节点(不包括文本节点、注释节点);

什么意思呢?咱们举例说明一下,看下边两段代码:

<div class="your-style" onclick="console.log(this.nextSibling);"><!--123-->
<input type="file" style="display: none;"/>

执行一下:
在这里插入图片描述

<div class="your-style" onclick="console.log(this.nextElementSibling);"><!--123-->
<input type="file" style="display: none;"/>

执行一下:
在这里插入图片描述
这回看出区别了吧,nextSibling是当前元素紧跟着的节点,无论紧跟着的是空格,回车,还是注释,反正只要是紧跟着的就是你了。
nextElementSibling就是真正的下一个兄弟节点。ok!就这么回事。

3. 常见的MIME类型(通用型)?
MIME(Multipurpose Internet Mail Extensions)媒体类型。

上面说到我们在限制文件的上传类型的时候,可以通过accept属性来做限制,而该属性值为一个MIME 类型字符串。那么常见的MIME类型字符串有哪些呢?

MIME类型举例
超文本标记语言文本.html text/html
xml文档.xml text/xml
XHTML文档.xhtml application/xhtml+xml
普通文本.txt text/plain
RTF文本.rtf application/rtf
PDF文档.pdf application/pdf
Microsoft Word文件.word application/msword
PNG图像.png image/png
GIF图形.gif image/gif
JPEG图形.jpeg,.jpg image/jpeg
au声音文件.au audio/basic
MIDI音乐文件mid,.midi audio/midi,audio/x-midi
RealAudio音乐文件.ra, .ram audio/x-pn-realaudio
MPEG文件.mpg,.mpeg video/mpeg
AVI文件.avi video/x-msvideo
GZIP文件.gz application/x-gzip
TAR文件.tar application/x-tar
任意的二进制数据application/octet-stream

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

已知FileReader提供了以下读取方式:

读取方式描述
readAsDataURL读取文件内容,结果用data:url的字符串形式表示
readAsArrayBuffer按字节读取文件内容,结果用ArrayBuffer对象表示
readAsBinaryString按字节读取文件内容,结果为文件的二进制串
readAsText按字符读取文件内容,结果用字符串形式表示

当 FileReader 读取文件的方式为 readAsArrayBuffer, readAsBinaryString, readAsDataURL 或者 readAsText 的时候,会触发一个 load 事件。从而可以使用 FileReader.onload 属性对该事件进行处理。

我们一般都是在onload的回调函数中,对e.target.result进行操作。

当然,FIleReader还有一些其它的事件和方法,这里简要的提一下,有兴趣的自己去查。

注:以下内容部分摘抄自MDN。

FileReader的时间处理:

FileReader.onabort —— 处理abort事件。该事件在读取操作被中断时触发。
FileReader.onerror —— 处理error事件。该事件在读取操作发生错误时触发。
FileReader.onload —— 处理load事件。该事件在读取操作完成时触发。
FileReader.onloadstart —— 处理loadstart事件。该事件在读取操作开始时触发。
FileReader.onloadend —— 处理loadend事件。该事件在读取操作结束时(要么成功,要么失败)触发。
FileReader.onprogress —— 处理progress事件。该事件在读取Blob时触发。

FileReader的支持的方法:
FileReader.abort() —— 中止读取操作。在返回时,readyState属性为DONE。
FileReader.readAsArrayBuffer() —— result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象。
FileReader.readAsBinaryString() —— result 属性中保存的将是被读取文件的原始二进制数据。
FileReader.readAsDataURL() —— result 属性中保存的将是被读取文件的URL格式的Base64字符串。
FileReader.readAsText() —— result 属性中保存的将是一个字符串(表示所读取的文件内容)。
以上四个方法都是用来设置读取文件的方式,开始读取指定的 Blob中的内容, 一旦完成,e.target.result 属性中保存的将是被读取文件对应类型的数据对象。

5. 关于XMLHttpRequest
xhr对象用于与服务器进行交互,通过XMLHTTPRequest可以在不刷新页面的情况下请求特定的url,获取数据,允许在不影响用户操作的情况下,更新页面的局部内容。

xhr的属性:

XMLHttpRequest.readyState: (返回一个xhr代理当前所处的状态。)
一个xhr代理总是处于下列状态列表中的一个

状态码含义说明实例
0unset创建xhr对象,但是尚未初始化let xhr = new XMLHttpRequest();
1opened载入,调用open方法xhr.open(method,url,true);
2headers_recievedsend方法已经被调用,响应头也已经被接收 (但是此阶段的数据为原始数据,客户端不能直接使用)xhr.send(params);
3loading解析接收到的服务器端响应数据下载中
4done请求操作已经完成。

注:下面内容部分摘抄自MDN。

XMLHttpRequest.onreadystatechange (readyState发生改变时触发)。所有的浏览器都支持onreadystatechange 。后来许多浏览器为了方便,又提供了一些额外的事件(onload、onerror、onprogress 等)。

还有一些属性,简单说一下:
XMLHttpRequest.response —— 获取请求的响应
XMLHttpRequest.responseType —— 一个用于定义响应类型的枚举值
XMLHttpRequest.timeout —— 设置请求超时时间
XMLHttpRequestEventTarget.ontimeout —— 请求超时的触发器
XMLHttpRequest.upload —— 上传进度
XMLHttpRequest.withCredentials —— 布尔值,用来指定跨域 Access-Control 请求是否应当带有授权信息

xhr的方法:
XMLHttpRequest.abort() —— 中止请求(我有一次面试被问过这个,但当时我特么说的是bort(),我也是……)
XMLHttpRequest.getAllResponseHeaders() —— 以字符串的形式返回所有用 CRLF 分隔的响应头
XMLHttpRequest.getResponseHeader() —— 返回包含指定响应头的字符串
XMLHttpRequest.open() —— 初始化一个请求。
XMLHttpRequest.overrideMimeType() —— 覆写由服务器返回的 MIME 类型。
XMLHttpRequest.send() —— 发送请求。
XMLHttpRequest.setRequestHeader() —— 设置 HTTP 请求头的值。

  • 15
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值