纯js实现上传文件小工具

在前端开发工作中,上传文件是一个很常见的功能,尤其是后台管理方面的项目,经常需要用到。而目前单页面应用(SPA)框架的流行,使得以往上传文件的方式略显笨拙,给开发带来不便。那么今天我们就一起来实现一个非常简洁易用的上传工具。

如同你看到的标题一样,为什么说是纯js呢?因为我们的目标是:

  1. 不引入任何三方工具(jquery也不用)
  2. 不需要写任何标签和css(反感写样式的童鞋,有没有很期待?)
  3. 支持任何主流前端框架引入使用(这也是纯js实现的好处)
老夫就是任性

明确了目标,接下来我们需要知道为什么要做这样一个工具,能解决现实工作中的什么问题。

先回顾一下传统上传文件的方式:

  1. 首先需要写一个表单标签 form,指定请求方式为 post,指定 enctypemultipart/form-data,定义好处理上传文件的 action;
<form method="POST" action="https://your.domain.com/upload"></form>
  1. 在写好的form标签里添加input标签,typefile,用来选择要上传的文件。
<input type="file" value="请选择文件" />
  1. 继续添加一个按钮标签,类型为submit,用来提交表单。
<button type="submit">提交</button>

完整代码如下

<form method="POST" action="https://your.domain.com/upload">
    <input type="file" value="请选择文件" />
    <button type="submit">提交</button>
</form>

最终界面可能长这个样子


还能再丑一点么

由于form表单的提交会导致页面重定向,当我们点击选择文件,然后点击提交按钮后,当前页面重定向到了action所指定的页面,再加上目前很多处理文件上传的action都是基于微服务架构的RESTful接口,你会发现当前页面被刷新变成了接口返回的数据!

现在我们给form的 target 属性设置 _blank,经过测试发现,当前页面虽然没有刷新,但是action指向的页面在一个新窗口中打开了,而实际需求往往是我们需要在当前页面接收返回的数据,明确告诉用户上传是否成功,很多情况下还需要把上传的文件回显出来(比如图片或者音频资源),因此,我们必须在当前页面处理上传结果。

我们在刚才的form下面写一个iframe标签,指定id和name,然后将form的target属性设置为这个iframe的id,这样做的目的是将form表单的提交目标指向这个iframe,当提交表单之后,接口返回值便会显示在iframe里面,代码如下。

......
</form>
<iframe id="uploadtarget" name="uploadtarget"></iframe>

但这样做依然没有达到我们的预期,然而事情似乎变得容易了,我们把iframe隐藏起来,然后用js读取iframe里面的内容(接口返回值)即可,那我们什么时候去读取呢?好在iframe每次加载成功后都会派发onload事件,而上传文件接口的返回值指向了iframe会触发它的onload事件,因此我们只需要为onload事件添加回调函数即可。

......
</form>
<iframe style="display:none" onload="onFrameLoad()" 
  id="uploadtarget" name="uploadtarget"></iframe>
<script>
  function onFrameLoad(){
    var frame = document.getElementById("uploadtarget");
    var content = frame.contentWindow.document.body.innerHtml;
    //解析content,分析上传接口返回值(略)
  }
</script>

通过上面的传统方式我们已经基本实现了无刷新上传文件,并且能相对容易的获取到上传接口的返回数据了。但我们发现这种实现方式的成本还是比较大的,尤其是在如今前端工程化,模块化,组件化要求必不可少的情况下,这样的实现方式显然不能被接受。

接下来正式进入本文的正题,不写标签,不引入其他工具,用最纯粹的方式实现上面的步骤。


首先让我们分析一下刚才的示例,其中类型为file的input标签必不可少,因为它是用户访问本地磁盘文件的入口,但基于我们的目标是不让开发者写任何标签,因此这个input必须由我们的工具动态创建,现在我们就来实现这微不足道但又必不可少的第一步。

//先给我们的目标功能起一个名字 easyUpload
function easyUpload(){
  var input = document.createElement("input");
  input.type = "file";
}

接下来我们需要让这个input去浏览本地磁盘以便让用户去选择想要上传的文件,但是问题来了,由于我们的工具不提供任何界面,那如何让用户去点击input呢?很简单,我们只需要调用这个动态input的click方法即可,但是,浏览器出于安全的考虑,选择本地文件必须由用户行为触发,因此我们定义的easyUpload函数也必须在用户事件(如click回调函数)里去调用,这一点请务必遵守。

input.click(); //代码执行到这里,本地文件选择框便会打开
//加油,离目标似乎很近了!

现在假设用户已经选择了想要上传的文件,那我们该如何去获取这个文件呢?很简单,input标签在每次选择了文件之后都会触发onchange事件,我们在这个回调里获取文件即可。通过访问input的files属性,我们可以获取到用户选择的文件集合,这里我们只实现单文件上传,因此我们获取files下标为0的对象。这个对象包含了文件的一些常见属性,如name(文件名)、size(文件大小)、type(文件类型)等等,利用这些属性,我们可以对小工具进行功能的扩展,比如利用size和type可以控制上传文件的大小限制和文件类型要求。这个不在本文讨论的范围内。

input.onchange = function(){
  var file = input.files[0];
}

拿到文件之后,我们就需要把它提交给服务端了,在上面的传统方式中,提交文件我们用到了form标签,但由于我们的工具想要尽可能的利用编程式思路去实现文件上传,如果还是像input一样去动态创建dom的话,会觉得不够优雅,虽然一样能解决我们的问题,而且原理也是完全相同的,但我们总是希望自己的代码足够清晰和简介明了。所以,在这里我们放弃动态创建form的方式,而使用FormData来实现。

让我们先了解一下FormData的定义。

FormData对象用以将数据编译成键值对,以便用XMLHttpRequest来发送数据。其主要用于发送表单数据,但亦可用于发送带键数据(keyed data),而独立于表单使用。如果表单enctype属性设为multipart/form-data ,则会使用表单的submit()方法来发送数据,从而,发送数据具有同样形式。

相信你已经发现了它的好处,FormData完全就是form标签的对象形式,有了它,我们就可以用编程的方式去操作form了,然后通过XMLHttpRequest来提交表单,最后在它的异步回调里轻松的拿到接口数据!废话少说,秀代码!

var form = new FormData();
form.append("file", file); //第一个参数是后台读取的请求key值
form.append("fileName", file.name);
form.append("other", "666666"); //实际业务的其他请求参数
var xhr = new XMLHttpRequest();
var action = "http://localhost:8080/upload.do"; //上传服务的接口地址
xhr.open("POST", action);
xhr.send(form); //发送表单数据
xhr.onreadystatechange = function(){
  if(xhr.readyState==4 && xhr.status==200){
    var resultObj = JSON.parse(xhr.responseText);
    //处理返回的数据......
  }
}

通过这段示例代码我们不难发现,使用FormData我们可以很轻松的把文件添加到表单里,并为这个文件指定参数名,文件名,以及其他跟实际上传业务相关的附加参数。更重要的是,利用FormData我们还可以实现在线录音,在线签字,在线截图以及你能想到的更多可能,这方面的内容也不在本文讨论的范围内,如果你感兴趣,欢迎在评论区域留言,我会抽时间来写这方面的文章。

上面的代码也为我们展示了如何用XMLHttpRequest来提交表单,以及如何获取接口数据,其中有几个关键点需要注意,action变量和传统方式表单的action属性值是一样的,是用来处理上传文件的接口地址,这个变量我们可以作为参数传递给小工具,然后我们需要调用XMLHttpRequest对象的open方法,open方法的第一个参数定义了请求类型,这里我们务必要使用post类型,open方法的第二个参数就是上面定义的action,最后我们调用send方法将表单对象作为参数发送出去,别忘了在onreadystatechange回调函数里接收接口数据,像这样,一个最原始的Ajax请求就完成了,虽然原始,但它是解决问题最简单,最直接有效的方式。固然npm上有丰富的工具,几乎涵盖了我们能遇到的所有问题,但有时候解决问题可能只需要很简单的几步,在这种情况下,如果还是去安装三方依赖的话,反而把简单问题复杂化了,这个话题仁者见仁,不再过多谈论。

下面是全部示例代码:

function easyUpload(){
  var input = document.createElement("input");
  input.type = "file";
  input.click();
  input.onchange = function(){
    var file = input.files[0];
    var form = new FormData();
    form.append("file", file); //第一个参数是后台读取的请求key值
    form.append("fileName", file.name);
    form.append("other", "666666"); //实际业务的其他请求参数
    var xhr = new XMLHttpRequest();
    var action = "http://localhost:8080/upload.do"; //上传服务的接口地址
    xhr.open("POST", action);
    xhr.send(form); //发送表单数据
    xhr.onreadystatechange = function(){
      if(xhr.readyState==4 && xhr.status==200){
        var resultObj = JSON.parse(xhr.responseText);
        //处理返回的数据......
      }
    }
  }
}

本文重在讨论解决问题的思路,其中展示的示例代码仅供参考,如果需要使用这个小工具,欢迎安装我的npm包。
包名:zq-easy-uploader
在线地址:zq-easy-uploader
Github地址:https://github.com/VirgilZhang/easyuploader

如果对本文中的任何内容有异议,或者发现有错误的地方,欢迎在评论区域留言,笔者会及时回复大家。如果你喜欢笔者的文章,记得点赞哦,你们的支持是我写作的动力!

[ 原创文章,转载请注明出处 ]

我的其他文章

反馈在人机交互中的重要性
聊一聊CSS文本两端对齐


欢迎大家在评论区留言自己想了解的前端话题,我会继续推出更多精彩的文章!

原创不易,有钱的捧个钱场,给个打赏

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,561评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,218评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,162评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,470评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,550评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,806评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,951评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,712评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,166评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,510评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,643评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,306评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,930评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,745评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,983评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,351评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,509评论 2 348

推荐阅读更多精彩内容