开发中会经常涉及到文件上传的需求,根据业务不同的需求,有不同的文件上传情况。

有简单的单文件上传,有多文件上传,因浏览器原生的文件上传样式及功能的支持度不算太高,很多时候我们会对样式进行美化,对功能进行完善。

本文根据一个例子,对多文件的上传样式做了一些简单的美化(其实也没怎么美化。。),同时支持选择文件后自定义删除相关的文件,最后再上传

文章篇幅较长,先简单看看图示:

目录

一、文件上传基础

1. 单文件上传

最简单的文件上传,是单文件上传,form标签中加入enctype="multipart/form-data",form表单中有一个input[type="file"]项

  1. <form name="form1" method="post" action="/abc.php" enctype="multipart/form-data">
  2. <input type="text" name="user" id="user" placeholder="请输入昵称">
  3. <input type="file" name="userImage" id="userImage">
  4. <input type="submit" name="sub" value="提交">
  5. </form>

2. 多文件上传

  1)类似单文件上传,简单的多文件上传其实就是多几个input[type="file"]项

  1. <form name="form1" method="post" action="/abc.php" enctype="multipart/form-data">
  2. <input type="text" name="user" id="user" placeholder="请输入昵称">
  3. <input type="file" name="userImage1" id="userImage1">
  4. <input type="file" name="userImage2" id="userImage2">
  5. <input type="file" name="userImage3" id="userImage3">
  6. <input type="submit" name="sub" value="提交">
  7. </form>

  2) HTML5为表单文件项新增了一个multiple属性,可以设置实现选择多个文件,如

  1. <form name="form1" method="post" action="/abc.php" enctype="multipart/form-data">
  2. <input type="text" name="user" id="user" placeholder="请输入昵称">
  3. <input type="file" name="userImage" id="userImage" multiple>
  4. <input type="submit" name="sub" value="提交">
  5. </form>

要注意的是,对于multiple这个新属性,在IE9及以下版本中不被支持,在移动端安卓平台下会忽略,也就是只能选择一个文件

二、表单文件上传的美化

看了上面几个图片,可以知道原生的文件选择项样式是最基本的,主要体现在三个点:

  1. 无边框,与其他有边框的元素不合拍
  2. 选择文件的按钮样式太基础
  3. 选择多个文件后只显示总数,未显示详细选择的文件名

基于几个问题,可以按需对其进行美化

第一点可以直接添加边框的样式

第二点需要增添其他元素,可以新增一个按钮(自行按需美化),将原始文件框隐藏,用JS事件绑定,点击按钮后模拟文件框的点击

  1. <input type="file" name="userImage" id="userImage" style="display: none;">
  2. <input type="button" id="" value="选择文件" onclick="document.getElementById('userImage').click()">

第三点与第二点类似,也得添加新的元素,选择文件后,通过JS获取选择的文件信息,并在新的元素中显示出来

想着很简单,但随之而来的问题就是,如果选中的文件数量很多,新元素占空间的多少就是个问题,可以默认显示几个文件,再通过“查看更多文件”查看到更多的信息

随之另外的想法是,一次性选中的文件很多,想取消某个文件时,又得重新选择。这未免太繁琐,所以需要提供即时删除某个选中文件的操作

三、选中文件后的删除

要提供选中文件后可删除的操作,就必然需要提供相关入口及脚本操作,下面围绕这点来做些解析

1. 界面的处理

选择文件后,我们可以通过删除按钮删除选中的文件,因为会出现多文件的情况,所以需要一个信息模版

  1.   <!-- 当前选择的文件列表 文件信息模版 -->
  2. <script type="text/template" id="file-temp-item-tpl">
  3. <span class="file-temp-item" style="{{style}}">
  4. <span class="file-temp-name">{{name}}</span>
  5. <span class="file-temp-btn">&times;</span>
  6. </span>
  7. </script>

选中的文件一多,就得再增添一个下拉框做辅助,最多显示5个文件信息,然后通过下拉按钮展开下拉框(按钮样式自行设定)

这里5个文件间的位置计算的不是很到位,主要是这段代码,可以自行设定

  1. // 计算每一项坐标left、占宽width
  2. left = i === 0 ? 2 : 2 + i * (100 / fileTempLen);
  3. width = 100 / fileTempLen - 2;

下拉列表里面的每一项也是一个模版

  1.   <!-- 查看更多文件 文件信息模版 -->
  2. <script type="text/template" id="file-more-item-tpl">
  3. <li>
  4. <span class="file-item-more-name">{{name}}</span>
  5. <span class="file-item-more-btn">&times;</span>
  6. </li>
  7. </script>

以下为初始的HTML结构

  1.   <form name="form" id="form" method="post" action="fileTest.php" enctype="multipart/form-data">
  2. <!-- <input type="number" name="numberTest" value="100"> -->
  3. <input type="file" name="fileTest[]" id="fileTest" multiple>
  4. <!-- 当前选择的文件列表(最多显示5条) -->
  5. <span class="file-temp">
  6. </span>
  7. <!-- 查看更多文件 -->
  8. <ul class="item-more">
  9. </ul>
  10. <input type="button" class="btn btn-success" id="uploadBtn" value="上传">
  11. <p class="upload-tip">文件上传成功</p>
  12. </form>

以下为全部CSS样式

  1. <link rel="stylesheet" type="text/css" href="bootstrap.min.css">
  2. <style type="text/css">
  3. html {
  4. font-family: Arial;
  5. }
  6.  
  7. form {
  8. margin: 50px auto;
  9. width: 400px;
  10. }
  11.  
  12. input {
  13. width: 300px;
  14. padding: 4px;
  15. }
  16.  
  17. #uploadBtn {
  18. margin-top: -3px;
  19. margin-left: 5px;
  20. width: 60px;
  21. height: 30px;
  22. font-weight: bold;
  23. font-size: 12px;
  24. }
  25.  
  26. #fileTest {
  27. display: inline-block;
  28. border: 1px solid #ccc;
  29. border-radius: 3px;
  30. }
  31.  
  32. .file-temp {
  33. position: relative;
  34. display: none;
  35. width: 300px;
  36. height: 31px;
  37. }
  38.  
  39. .file-temp-item {
  40. position: absolute;
  41. top: 4px;
  42. height: 24px;
  43. }
  44.  
  45. .item-more-btn {
  46. display: inline-block;
  47. position: absolute;
  48. top: 18px;
  49. right: 0.5%;
  50. width: 10px;
  51. height: 10px;
  52. color: #777;
  53. cursor: pointer;
  54. }
  55.  
  56. .item-more-btn:hover {
  57. border-top-color: #aaa;
  58. }
  59.  
  60. .file-temp-name {
  61. display: inline-block;
  62. overflow: hidden;
  63. width: 90%;
  64. height: 26px;
  65. padding: 2px 15px 2px 5px;
  66. border-radius: 2px;
  67. background-color: #eaeaf3;
  68. text-overflow: ellipsis;
  69. white-space: nowrap;
  70. }
  71. .file-temp-btn {
  72. position: absolute;
  73. display: inline-block;
  74. top: 4px;
  75. right: 11%;
  76. width: 18px;
  77. height: 18px;
  78. line-height: 18px;
  79. text-align: center;
  80. border: 1px solid #ddd;
  81. background-color: #ccc;
  82. border-radius: 50%;
  83. color: #fff;
  84. font-size: 18px;
  85. cursor: pointer;
  86. }
  87.  
  88. .item-more {
  89. position: absolute;
  90. overflow-y: auto;
  91. display: none;
  92. padding-left: 0;
  93. width: 300px;
  94. max-height: 150px;
  95. list-style: none;
  96. }
  97.  
  98. .item-more li {
  99. position: relative;
  100. padding: 5px;
  101. border: 1px solid #ccc;
  102. border-top: none;
  103. }
  104. .item-more li:hover {
  105. background-color: #f5f5f9;
  106. }
  107.  
  108. .file-item-more-name {
  109. display: inline-block;
  110. width: 90%;
  111. overflow: hidden;
  112. text-overflow: ellipsis;
  113. white-space: nowrap;
  114. }
  115. .file-item-more-btn {
  116. position: absolute;
  117. display: inline-block;
  118. top: 8px;
  119. right: 2%;
  120. width: 18px;
  121. height: 18px;
  122. line-height: 18px;
  123. text-align: center;
  124. border: 1px solid #ddd;
  125. background-color: #ddd;
  126. border-radius: 50%;
  127. color: #fff;
  128. font-size: 18px;
  129. cursor: pointer;
  130. }
  131. .file-item-more-btn:hover {
  132. background-color: #ccc;
  133. }
  134.  
  135. .upload-tip {
  136. display: none;
  137. margin: 50px auto;
  138. text-align: center;
  139. font-size: 12px;
  140. }
  141. </style>

2. 脚本的处理

下面,着重介绍JS脚本的处理

要获取到选中文件的信息,自然想到用value属性,但通过文件项的value只能获取到一个文件路径(第一个),无论有没有multiple

无multiple

  1. <input type="file" onchange="console.log(this.value);">

有multiple

  1. <input type="file" multiple onchange="console.log(this.value);">

既然直接通过value获取不到所有选中的文件信息,只能寻求其他途径。

  1)FileList

获取选中的文件信息,还可以用FileList对象,这是在HTML5中新增的,每个表单文件项都有个files属性,里边存储这选中的文件的一些信息

  1. <input type="file" multiple onchange="console.log(this.files);">

选中两个文件后,查看文件信息

FileList对象看起来是个类数组,有length属性。所以我们应该可以通过修改或删除相关的项来自定义我们选择的文件(注意其实这是不能修改的,且继续看下去)

假如我选择了两个文件,想删除第二项目,使用splice删除,则

  1. <input type="file" multiple onchange="console.log(Array.prototype.splice.call(this.files, 1, 1));">

报错,由此可知FileList的length属性是只读的,那直接修改为可写可配置呢

  1. Object.defineProperty(FileList.prototype, 'length', {
  2. writable: true,
  3. configurable: true
  4. });

配置之后length能修改了,乍一看还以为splice生效了,然而输出一看,FileList对象内容不变,仍为两项

查阅了一些资料后,了解到浏览器为了安全性的考虑,把FileList对象的内容设为了不可更改,只可以手动置空,但不能修改内容

所以,解决办法是,新增一个数组,初始复制FileList对象的文件内容,之后的修改操作则通过这个可更改的数组进行

  1. // 存储更新所选文件
  2. var curFiles = [];
  3. ...
  4.  
  5. // 选中文件后
  6.  
  7. var files = this.files;
  8.  
  9. if (files && files.length) {
  10. // 原始FileList对象不可更改,所以将其赋予curFiles提供接下来的修改
  11. Array.prototype.push.apply(curFiles, files);
  12. }

假如点击了删除叉叉,可以直接更新文件信息数组

  1. var name = $(this).prev().text();
  2. // 去除该文件
  3. curFiles = curFiles.filter(function(file) {
  4. return file.name !== name;
  5. });

这样一来,更新文件信息的问题得到解决,然后就可以进行文件的上传了

点击文件上传,如果直接调用$form.submit(); 则上传的文件信息依然是初始的FileList对象,达不到我们自定义的要求,所以需要用Ajax提交

那么,该怎么想后台提供一个文件对象呢?

  2)FormData

HTML5引入了表单的新对象FormData, 它可以生成一个表单对象,我们可以向其中获取/设置键值对信息,再一并提交给后台

引用MDN的FormData使用方法,我们可以添加各种类型的数据,使用ajax提交

  1. var oMyForm = new FormData();
  2.  
  3. oMyForm.append("username", "Groucho");
  4. oMyForm.append("accountnum", 123456); // 数字123456被立即转换成字符串"123456"
  5.  
  6. // fileInputElement中已经包含了用户所选择的文件
  7. oMyForm.append("userfile", fileInputElement.files[0]);
  8.  
  9. var oFileBody = '<a id="a"><b id="b">hey!</b></a>'; // Blob对象包含的文件内容
  10. var oBlob = new Blob([oFileBody], { type: "text/xml"});
  11.  
  12. oMyForm.append("webmasterfile", oBlob);
  13.  
  14. var oReq = new XMLHttpRequest();
  15. oReq.open("POST", "http://foo.com/submitform.php");
  16. oReq.send(oMyForm);

也可使用JQ的封装的ajax,不过要注意设置processData和contentType属性为false,防止JQ胡乱解析文件格式

  1. var fd = new FormData(document.getElementById("fileinfo")); // 使用某个表单作为初始项
  2. fd.append("CustomField", "This is some extra data");
  3. $.ajax({
  4. url: "stash.php",
  5. type: "POST",
  6. data: fd,
  7. processData: false, // 告诉jQuery不要去处理发送的数据
  8. contentType: false // 告诉jQuery不要去设置Content-Type请求头
  9. });

这里有几个要注意的点:

1)FormData中的属性值接受的是单个文件信息,不能是复合性的对象。可能表意不明,且看

  1. var fd = new FormData($('#form')[0]);
  2. fd.append('myFileTest', curFiles);
  1. $files = $_REQUEST['myFileTest'];
  2. var_dump($files);

用PHP接收传过来的数据,数据却被直接转换成字符串了,非文件对象

curFiles是文件对象,那PHP端是不是应该用$_FILES来接收信息呢,试试换成$files = $_FILES['myFileTest'];

直接出问题了,说明不能这样处理,需要将curFiles内容一项一项拆开,即单个文件信息

  1. var fd = new FormData($('#form')[0]);
  2. for (var i = 0, j = curFiles.length; i < j; ++i) {
  3. fd.append('myFileTest[]', curFiles[i]);
  4. }
  1. $files = $_FILES['myFileTest'];
  2. var_dump($files);

文件接收成功,接下来就可以按需进行文件的操作了

2)后端获取文件信息的时候,是直接通过原始$_FILES获取的,其他一般的信息才用$_REQUEST获取

换成$files = $_REQUEST['myFileTest'];试试,直接就是出现找不到myFileTest的问题

试试添加一般的文件再提交

  1. var fd = new FormData($('#form')[0]);
  2. for (var i = 0, j = curFiles.length; i < j; ++i) {
  3. fd.append('myFileTest[]', curFiles[i]);
  4. }
  5.  
  6. fd.append('myTest', [1, 2, 3]);
  1. $files = $_FILES['myFileTest'];
  2. $test = $_REQUEST['myTest'];
  3. var_dump($test);
  4. var_dump($files);

3)如果需要multiple的多文件上传,则需要在文件项的文件后添加[]号,表示这是一个多文件的数组,以供后端处理解析

  1. fd.append('myFileTest[]', curFiles[i]);

如果没有后面的[],则连续的append会直接覆盖原来的,最后后端获取到的只是最后append进去的项

4)不要直接在JQ的ajax中实例化出一个FormData对象,会出问题

直接在data属性中生成FormData对象,会被JQ忽略,所以后端什么信息也拿不到

混合表单项简单的例子:

在表单处理中,很多时候我们会进行文件上传和其他基础项的提交,简单地多加一个input项目,看看是否处理成功

  1. <input type="number" name="numberTest" value="100">

  1. <?php
  2. $files = $_FILES['myFileTest'];
  3. $test = $_REQUEST['numberTest'];
  4.  
  5. echo json_encode(array(
  6. 'len' => count($files['name']),
  7. 'num' => $test
  8. ));
  9.  
  10. ?>

以下为全部的JS脚本:

  1. <script type="text/javascript">
  2. /**
  3. * 向文件列表元素中添加相应的文件项
  4. * @param {Array} files 当前的文件列表数组对象
  5. */
  6. function addItem(files) {
  7. var fileTempItemTpl = $('#file-temp-item-tpl').html(),
  8. fileMoreItemTpl = $('#file-more-item-tpl').html()
  9. htmlTemp = [],
  10. htmlMoreTemp = [],
  11. // 文件列表中各文件坐标位置及所占空间
  12. left = 2,
  13. width = 100,
  14. // 最多取前5个文件
  15. fileTempLen = files.length > 5 ? 5 : files.length;
  16.  
  17. for (var i = 0, j = files.length; i < j; ++i) {
  18. // 当i > 4,即第6个文件开始
  19. if (i > 4) {
  20. htmlMoreTemp.push(fileMoreItemTpl.replace('{{name}}', files[i].name));
  21. continue;
  22. }
  23.  
  24. // 计算每一项坐标left、占宽width
  25. left = i === 0 ? 2 : 2 + i * (100 / fileTempLen);
  26. width = 100 / fileTempLen - 2;
  27.  
  28. htmlTemp.push(fileTempItemTpl
  29. .replace('{{style}}', 'left: ' + left + '%;width: ' + width + '%;')
  30. .replace('{{name}}', files[i].name)
  31. );
  32. }
  33.  
  34. // 渲染相关元素内容
  35. $('.file-temp').html(''
  36. + '<input type="text" style="background-color:#fff;" class="form-control" id="fileTemp" readonly>'
  37. + htmlTemp.join('')
  38. + (files.length > 5
  39. ? '<span class="item-more-btn" title="查看更多">=</span>'
  40. : ''
  41. )
  42. );
  43.  
  44. $('.item-more').html(htmlMoreTemp.join(''));
  45. }
  46.  
  47. // 保存当前选择的(更新后)文件列表
  48. var curFiles = [];
  49.  
  50. // 初始选择文件时触发
  51. $('#fileTest').change(function() {
  52. var $this = $(this),
  53. $temp = $('.file-temp'),
  54. files = this.files;
  55.  
  56. if (files && files.length) {
  57. // 原始FileList对象不可更改,所以将其赋予curFiles提供接下来的修改
  58. Array.prototype.push.apply(curFiles, files);
  59. addItem(curFiles);
  60.  
  61. $this.hide();
  62. $temp.css('display', 'inline-block');
  63. }
  64. });
  65.  
  66. $(document)
  67. // 取消选择某个文件时,在文件列表数组对象中删除这个值,并更新列表
  68. .on('click', '.file-temp-btn, .file-item-more-btn', function() {
  69. $('.upload-tip').hide();
  70. var name = $(this).prev().text();
  71. // 去除该文件
  72. curFiles = curFiles.filter(function(file) {
  73. return file.name !== name;
  74. });
  75. // 文件列表数组对象长度大于5才显示“更多文件列表”下拉项
  76. if (curFiles.length <= 5) {
  77. $('.item-more').hide();
  78. }
  79. // 文件列表数组被清空则重置文件选择表单项
  80. if (!curFiles.length) {
  81. $('#fileTest').val('').show();
  82. $('.file-temp').css('display', 'none');
  83. } else {
  84. addItem(curFiles);
  85. }
  86.  
  87. console.log(curFiles)
  88. })
  89. // 显示“更多文件列表”下拉项
  90. .on('click', '.item-more-btn', function() {
  91. $('.upload-tip').hide();
  92. $('.item-more').show('normal');
  93. });
  94.  
  95. // 上传操作
  96. $('#uploadBtn').click(function() {
  97. $('.upload-tip').hide();
  98.  
  99. // 构建FormData对象
  100. var fd = new FormData($('#form')[0]);
  101. for (var i = 0, j = curFiles.length; i < j; ++i) {
  102. fd.append('myFileTest[]', curFiles[i]);
  103. }
  104.  
  105. $.ajax({
  106. url: 'fileTest.php',
  107. type: 'post',
  108. data: fd,
  109. processData: false,
  110. contentType: false,
  111. success: function(rs) {
  112. rs = JSON.parse(rs);
  113. $('.upload-tip')
  114. .addClass('text-success')
  115. .removeClass('text-error')
  116. .text(rs.len + '个文件上传成功, number项值为' + rs.num)
  117. .show();
  118. },
  119. error: function(err) {
  120.  
  121. }
  122. });
  123. });
  124. </script>

后记:

另外,可以考虑给文件上传增加进度条

而当文件太大时,会因为后端支持的最大文件size不够造成崩溃,这时可以考虑进行断点续传

断点续传,前端可以通过二进制流和本地存储的结合来实现,这里就不多说了

表单多文件上传样式美化 && 支持选中文件后删除相关项的更多相关文章

  1. JS实现表单多文件上传样式美化支持选中文件后删除相关项

    http://www.youdaili.net/javascript/5903.html

  2. ajaxfileupload多文件上传 - 修复只支持单个文件上传的bug

    搜索: jquery ajaxFileUpload AjaxFileUpload同时上传多个文件 原生的AjaxFileUpload插件是不支持多文件上传的,通过修改AjaxFileUpload少量代 ...

  3. 表单同时有中文字段和文件上传,加上enctype="multipart/form-data"后导致的中文乱码问题

    因为一个表单需要同时上传字段和文件,所以加上enctype="multipart/form-data",但是上传后的中文字段变成了乱码. 把enctype="multip ...

  4. Django框架 之 Form表单和Ajax上传文件

    Django框架 之 Form表单和Ajax上传文件 浏览目录 Form表单上传文件 Ajax上传文件 伪造Ajax上传文件 Form表单上传文件 html 1 2 3 4 5 6 7 <h3& ...

  5. 自定义input文件上传样式

    前言 文件上传是我们经常会用到的功能,但是原生的input样式太丑了,能不能自定义一个input文件上传样式呢?我这里写了两种方法,form表单提交跟ajax异步提交都没有问题,自动上传或者点击上传按 ...

  6. hTML5实现表单内的上传文件框,上传前预览图片,针刷新预览images

    hTML5实现表单内的上传文件框,上传前预览图片,针刷新预览images, 本例子主要是使用HTML5 的File API,建立一個可存取到该file的url, 一个空的img标签,ID为img0,把 ...

  7. php前台表单限制PHP上传大小

    在php文件上传时候,一般我都认为考虑php.ini配置修改文件上传大小,还后台控制上传大小,这里教你php前台表单限制PHP上传大小 <form action="http://www ...

  8. bootstrap上传表单的时候上传的数据默认是0 一定要小心

    bootstrap上传表单的时候上传的数据默认是0 一定要小心

  9. JS_单个或多个文件上传_不支持单独修改

    A-From表单直接填写提交地址,不过干预: 1. 单文件上传 最简单的文件上传,是单文件上传,form标签中加入enctype="multipart/form-data",for ...

随机推荐

  1. IOS_反射

    // // PJReflect.m // 新浪微博 // // Created by pj on 14-8-8. // Copyright (c) 2014年 pj. All rights reser ...

  2. 基于OWin的Web服务器Katana发布版本3

    当 ASP.NET 首次在 2002 年发布时,时代有所不同. 那时,Internet 仍处于起步阶段,大约有 5.69 亿用户,每个用户平均每天访问 Internet 的时间为 46 分钟,大约有 ...

  3. C#实现DNS解析服务和智能DNS服务

    C#实现DNS解析服务有一个开源项目ARSoft.Tools.Net, ARSoft.Tools.Net是一个非常强大的开源DNS控件库,包含.Net SPF validation, SenderID ...

  4. 【性能为王】从PHP源码剖析array_keys和array_unique

    之前在[译]更快的方式实现PHP数组去重这篇文章里讨论了使用array_flip后再调用array_keys函数替换直接调用array_unique函数实现数组去重性能较好.由于原文没有给出源码分析和 ...

  5. 编写具有单一职责(SRP)的类

    这两周我需要对一个历史遗留的功能做一些扩展,正如很多人不愿意碰这些历史遗留的代码一样,我的内心也同样对这样的任务充满反抗.这些代码中充斥着各种null判断(你写的return null正确吗?),不规 ...

  6. Spring Rabbitmq HelloWorld实例

    之前的博客和大家分享了Rabbitmq的基本框架,及其工作原理,网址为 < http://www.cnblogs.com/jun-ma/p/4840869.html >.今天呢,想和大家一 ...

  7. CentOS 7 网络配置

    Virtual box 安装了CentOS 7最小模式后马上用ifconfig命令查看网络情况,发现该命令不存在. [root@centos1 ~]# ifconfig -bash: ifconfig ...

  8. WebDriver API元素的定位

    一.以下截图为用FireBug定位的用火狐(Firefox)浏览器打开的百度首页,下面所讲述的八种定位方法,就是以该截图中的百度输入框为例子. ①.FireBug是Firefox浏览器下的开发类插件, ...

  9. WaitType:SOS_SCHEDULER_YIELD

    今天遇到一个query,处于SOS_SCHEDULER_YIELD 状态,physical IO 不增加,CPU的使用一直在增长.当一个sql query长时间处于SOS_SCHEDULER_YIEL ...

  10. ClickOnce部署(2):自动更新

    上次我们说了如何用最基本的方式用ClickOnce技术部署应用程序项目,本篇我们来认识一下如何让应用程序具备自动更新的功能. 我们依然通过实例来学习. 第一步,随便建一个应用程序项目,至于是控制台.W ...