開發中會經常涉及到文件上傳的需求,根據業務不同的需求,有不同的文件上傳情況。
有簡單的單文件上傳,有多文件上傳,因浏覽器原生的文件上傳樣式及功能的支持度不算太高,很多時候我們會對樣式進行美化,對功能進行完善。
本文根據一個例子,對多文件的上傳樣式做了一些簡單的美化(其實也沒怎麼美化。。),同時支持選擇文件後自定義刪除相關的文件,最後再上傳
文章篇幅較長,先簡單看看圖示:
一、文件上傳基礎
1. 單文件上傳
最簡單的文件上傳,是單文件上傳,form標簽中加入enctype="multipart/form-data",form表單中有一個input[type="file"]項
<form name="form1" method="post" action="/abc.php" enctype="multipart/form-data"> <input type="text" name="user" id="user" placeholder="請輸入昵稱"> <input type="file" name="userImage" id="userImage"> <input type="submit" name="sub" value="提交"> </form>
2. 多文件上傳
1)類似單文件上傳,簡單的多文件上傳其實就是多幾個input[type="file"]項
<form name="form1" method="post" action="/abc.php" enctype="multipart/form-data"> <input type="text" name="user" id="user" placeholder="請輸入昵稱"> <input type="file" name="userImage1" id="userImage1"> <input type="file" name="userImage2" id="userImage2"> <input type="file" name="userImage3" id="userImage3"> <input type="submit" name="sub" value="提交"> </form>
2) HTML5為表單文件項新增了一個multiple屬性,可以設置實現選擇多個文件,如
<form name="form1" method="post" action="/abc.php" enctype="multipart/form-data">
<input type="text" name="user" id="user" placeholder="請輸入昵稱">
<input type="file" name="userImage" id="userImage" multiple>
<input type="submit" name="sub" value="提交">
</form>
二、表單文件上傳的美化
看了上面幾個圖片,可以知道原生的文件選擇項樣式是最基本的,主要體現在三個點:
無邊框,與其他有邊框的元素不合拍
選擇文件的按鈕樣式太基礎
選擇多個文件後只顯示總數,未顯示詳細選擇的文件名
基於幾個問題,可以按需對其進行美化
第一點可以直接添加邊框的樣式
第二點需要增添其他元素,可以新增一個按鈕(自行按需美化),將原始文件框隱藏,用JS事件綁定,點擊按鈕後模擬文件框的點擊
<input type="file" name="userImage" id="userImage" style="display: none;"> <input type="button" id="" value="選擇文件" onclick="document.getElementById('userImage').click()">
第三點與第二點類似,也得添加新的元素,選擇文件後,通過JS獲取選擇的文件信息,並在新的元素中顯示出來
想著很簡單,但隨之而來的問題就是,如果選中的文件數量很多,新元素占空間的多少就是個問題,可以默認顯示幾個文件,再通過“查看更多文件”查看到更多的信息
隨之另外的想法是,一次性選中的文件很多,想取消某個文件時,又得重新選擇。這未免太繁瑣,所以需要提供即時刪除某個選中文件的操作
三、選中文件後的刪除
要提供選中文件後可刪除的操作,就必然需要提供相關入口及腳本操作,下面圍繞這點來做些解析
1. 界面的處理
選擇文件後,我們可以通過刪除按鈕刪除選中的文件,因為會出現多文件的情況,所以需要一個信息模版
<!-- 當前選擇的文件列表 文件信息模版 --> <script type="text/template" id="file-temp-item-tpl"> <span class="file-temp-item" style="{{style}}"> <span class="file-temp-name">{{name}}</span> <span class="file-temp-btn">×</span> </span> </script>
選中的文件一多,就得再增添一個下拉框做輔助,最多顯示5個文件信息,然後通過下拉按鈕展開下拉框(按鈕樣式自行設定)
這裡5個文件間的位置計算的不是很到位,主要是這段代碼,可以自行設定
// 計算每一項坐標left、占寬width left = i === 0 ? 2 : 2 + i * (100 / fileTempLen); width = 100 / fileTempLen - 2;
下拉列表裡面的每一項也是一個模版
<!-- 查看更多文件 文件信息模版 --> <script type="text/template" id="file-more-item-tpl"> <li> <span class="file-item-more-name">{{name}}</span> <span class="file-item-more-btn">×</span> </li> </script>
以下為初始的HTML結構
<form name="form" id="form" method="post" action="fileTest.php" enctype="multipart/form-data"> <!-- <input type="number" name="numberTest" value="100"> --> <input type="file" name="fileTest[]" id="fileTest" multiple> <!-- 當前選擇的文件列表(最多顯示5條) --> <span class="file-temp"> </span> <!-- 查看更多文件 --> <ul class="item-more"> </ul> <input type="button" class="btn btn-success" id="uploadBtn" value="上傳"> <p class="upload-tip">文件上傳成功</p> </form>
以下為全部CSS樣式
<link rel="stylesheet" type="text/css" href="bootstrap.min.css"> <style type="text/css"> html { font-family: Arial; } form { margin: 50px auto; width: 400px; } input { width: 300px; padding: 4px; } #uploadBtn { margin-top: -3px; margin-left: 5px; width: 60px; height: 30px; font-weight: bold; font-size: 12px; } #fileTest { display: inline-block; border: 1px solid #ccc; border-radius: 3px; } .file-temp { position: relative; display: none; width: 300px; height: 31px; } .file-temp-item { position: absolute; top: 4px; height: 24px; } .item-more-btn { display: inline-block; position: absolute; top: 18px; right: 0.5%; width: 10px; height: 10px; color: #777; cursor: pointer; } .item-more-btn:hover { border-top-color: #aaa; } .file-temp-name { display: inline-block; overflow: hidden; width: 90%; height: 26px; padding: 2px 15px 2px 5px; border-radius: 2px; background-color: #eaeaf3; text-overflow: ellipsis; white-space: nowrap; } .file-temp-btn { position: absolute; display: inline-block; top: 4px; right: 11%; width: 18px; height: 18px; line-height: 18px; text-align: center; border: 1px solid #ddd; background-color: #ccc; border-radius: 50%; color: #fff; font-size: 18px; cursor: pointer; } .item-more { position: absolute; overflow-y: auto; display: none; padding-left: 0; width: 300px; max-height: 150px; list-style: none; } .item-more li { position: relative; padding: 5px; border: 1px solid #ccc; border-top: none; } .item-more li:hover { background-color: #f5f5f9; } .file-item-more-name { display: inline-block; width: 90%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .file-item-more-btn { position: absolute; display: inline-block; top: 8px; right: 2%; width: 18px; height: 18px; line-height: 18px; text-align: center; border: 1px solid #ddd; background-color: #ddd; border-radius: 50%; color: #fff; font-size: 18px; cursor: pointer; } .file-item-more-btn:hover { background-color: #ccc; } .upload-tip { display: none; margin: 50px auto; text-align: center; font-size: 12px; } </style>
2. 腳本的處理
下面,著重介紹JS腳本的處理
要獲取到選中文件的信息,自然想到用value屬性,但通過文件項的value只能獲取到一個文件路徑(第一個),無論有沒有multiple
無multiple
<input type="file" onchange="console.log(this.value);">
有multiple
<input type="file" multiple onchange="console.log(this.value);">
既然直接通過value獲取不到所有選中的文件信息,只能尋求其他途徑。
1)FileList
獲取選中的文件信息,還可以用FileList對象,這是在HTML5中新增的,每個表單文件項都有個files屬性,裡邊存儲這選中的文件的一些信息
<input type="file" multiple onchange="console.log(this.files);">
選中兩個文件後,查看文件信息
FileList對象看起來是個類數組,有length屬性。所以我們應該可以通過修改或刪除相關的項來自定義我們選擇的文件(注意其實這是不能修改的,且繼續看下去)
假如我選擇了兩個文件,想刪除第二項目,使用splice刪除,則
<input type="file" multiple onchange="console.log(Array.prototype.splice.call(this.files, 1, 1));">
報錯,由此可知FileList的length屬性是只讀的,那直接修改為可寫可配置呢
Object.defineProperty(FileList.prototype, 'length', { writable: true, configurable: true });
配置之後length能修改了,乍一看還以為splice生效了,然而輸出一看,FileList對象內容不變,仍為兩項
查閱了一些資料後,了解到浏覽器為了安全性的考慮,把FileList對象的內容設為了不可更改,只可以手動置空,但不能修改內容
所以,解決辦法是,新增一個數組,初始復制FileList對象的文件內容,之後的修改操作則通過這個可更改的數組進行
// 存儲更新所選文件 var curFiles = []; ... // 選中文件後 var files = this.files; if (files && files.length) { // 原始FileList對象不可更改,所以將其賦予curFiles提供接下來的修改 Array.prototype.push.apply(curFiles, files); }
假如點擊了刪除叉叉,可以直接更新文件信息數組
var name = $(this).prev().text(); // 去除該文件 curFiles = curFiles.filter(function(file) { return file.name !== name; });
這樣一來,更新文件信息的問題得到解決,然後就可以進行文件的上傳了
點擊文件上傳,如果直接調用$form.submit(); 則上傳的文件信息依然是初始的FileList對象,達不到我們自定義的要求,所以需要用Ajax提交
那麼,該怎麼想後台提供一個文件對象呢?
2)FormData
HTML5引入了表單的新對象FormData, 它可以生成一個表單對象,我們可以向其中獲取/設置鍵值對信息,再一並提交給後台
引用MDN的FormData使用方法,我們可以添加各種類型的數據,使用ajax提交
var oMyForm = new FormData(); oMyForm.append("username", "Groucho"); oMyForm.append("accountnum", 123456); // 數字123456被立即轉換成字符串"123456" // fileInputElement中已經包含了用戶所選擇的文件 oMyForm.append("userfile", fileInputElement.files[0]); var oFileBody = '<a id="a"><b id="b">hey!</b></a>'; // Blob對象包含的文件內容 var oBlob = new Blob([oFileBody], { type: "text/xml"}); oMyForm.append("webmasterfile", oBlob); var oReq = new XMLHttpRequest(); oReq.open("POST", "http://foo.com/submitform.php"); oReq.send(oMyForm);
也可使用JQ的封裝的ajax,不過要注意設置processData和contentType屬性為false,防止JQ胡亂解析文件格式
var fd = new FormData(document.getElementById("fileinfo")); // 使用某個表單作為初始項 fd.append("CustomField", "This is some extra data"); $.ajax({ url: "stash.php", type: "POST", data: fd, processData: false, // 告訴jQuery不要去處理發送的數據 contentType: false // 告訴jQuery不要去設置Content-Type請求頭 });
這裡有幾個要注意的點:
1)FormData中的屬性值接受的是單個文件信息,不能是復合性的對象。可能表意不明,且看
var fd = new FormData($('#form')[0]); fd.append('myFileTest', curFiles); $files = $_REQUEST['myFileTest']; var_dump($files);
用PHP接收傳過來的數據,數據卻被直接轉換成字符串了,非文件對象
curFiles是文件對象,那PHP端是不是應該用$_FILES來接收信息呢,試試換成$files = $_FILES['myFileTest'];
直接出問題了,說明不能這樣處理,需要將curFiles內容一項一項拆開,即單個文件信息
var fd = new FormData($('#form')[0]); for (var i = 0, j = curFiles.length; i < j; ++i) { fd.append('myFileTest[]', curFiles[i]); } $files = $_FILES['myFileTest']; var_dump($files);
文件接收成功,接下來就可以按需進行文件的操作了
2)後端獲取文件信息的時候,是直接通過原始$_FILES獲取的,其他一般的信息才用$_REQUEST獲取
換成$files = $_REQUEST['myFileTest'];試試,直接就是出現找不到myFileTest的問題
試試添加一般的文件再提交
var fd = new FormData($('#form')[0]); for (var i = 0, j = curFiles.length; i < j; ++i) { fd.append('myFileTest[]', curFiles[i]); } fd.append('myTest', [1, 2, 3]); $files = $_FILES['myFileTest']; $test = $_REQUEST['myTest']; var_dump($test); var_dump($files);
3)如果需要multiple的多文件上傳,則需要在文件項的文件後添加[]號,表示這是一個多文件的數組,以供後端處理解析
fd.append('myFileTest[]', curFiles[i]);
如果沒有後面的[],則連續的append會直接覆蓋原來的,最後後端獲取到的只是最後append進去的項
4)不要直接在JQ的ajax中實例化出一個FormData對象,會出問題
直接在data屬性中生成FormData對象,會被JQ忽略,所以後端什麼信息也拿不到
混合表單項簡單的例子:
在表單處理中,很多時候我們會進行文件上傳和其他基礎項的提交,簡單地多加一個input項目,看看是否處理成功
<input type="number" name="numberTest" value="100">
<?php $files = $_FILES['myFileTest']; $test = $_REQUEST['numberTest']; echo json_encode(array( 'len' => count($files['name']), 'num' => $test )); ?>
以下為全部的JS腳本:
<script type="text/javascript"> /** * 向文件列表元素中添加相應的文件項 * @param {Array} files 當前的文件列表數組對象 */ function addItem(files) { var fileTempItemTpl = $('#file-temp-item-tpl').html(), fileMoreItemTpl = $('#file-more-item-tpl').html() htmlTemp = [], htmlMoreTemp = [], // 文件列表中各文件坐標位置及所占空間 left = 2, width = 100, // 最多取前5個文件 fileTempLen = files.length > 5 ? 5 : files.length; for (var i = 0, j = files.length; i < j; ++i) { // 當i > 4,即第6個文件開始 if (i > 4) { htmlMoreTemp.push(fileMoreItemTpl.replace('{{name}}', files[i].name)); continue; } // 計算每一項坐標left、占寬width left = i === 0 ? 2 : 2 + i * (100 / fileTempLen); width = 100 / fileTempLen - 2; htmlTemp.push(fileTempItemTpl .replace('{{style}}', 'left: ' + left + '%;width: ' + width + '%;') .replace('{{name}}', files[i].name) ); } // 渲染相關元素內容 $('.file-temp').html('' + '<input type="text" style="background-color:#fff;" class="form-control" id="fileTemp" readonly>' + htmlTemp.join('') + (files.length > 5 ? '<span class="item-more-btn" title="查看更多">=</span>' : '' ) ); $('.item-more').html(htmlMoreTemp.join('')); } // 保存當前選擇的(更新後)文件列表 var curFiles = []; // 初始選擇文件時觸發 $('#fileTest').change(function() { var $this = $(this), $temp = $('.file-temp'), files = this.files; if (files && files.length) { // 原始FileList對象不可更改,所以將其賦予curFiles提供接下來的修改 Array.prototype.push.apply(curFiles, files); addItem(curFiles); $this.hide(); $temp.css('display', 'inline-block'); } }); $(document) // 取消選擇某個文件時,在文件列表數組對象中刪除這個值,並更新列表 .on('click', '.file-temp-btn, .file-item-more-btn', function() { $('.upload-tip').hide(); var name = $(this).prev().text(); // 去除該文件 curFiles = curFiles.filter(function(file) { return file.name !== name; }); // 文件列表數組對象長度大於5才顯示“更多文件列表”下拉項 if (curFiles.length <= 5) { $('.item-more').hide(); } // 文件列表數組被清空則重置文件選擇表單項 if (!curFiles.length) { $('#fileTest').val('').show(); $('.file-temp').css('display', 'none'); } else { addItem(curFiles); } console.log(curFiles) }) // 顯示“更多文件列表”下拉項 .on('click', '.item-more-btn', function() { $('.upload-tip').hide(); $('.item-more').show('normal'); }); // 上傳操作 $('#uploadBtn').click(function() { $('.upload-tip').hide(); // 構建FormData對象 var fd = new FormData($('#form')[0]); for (var i = 0, j = curFiles.length; i < j; ++i) { fd.append('myFileTest[]', curFiles[i]); } $.ajax({ url: 'fileTest.php', type: 'post', data: fd, processData: false, contentType: false, success: function(rs) { rs = JSON.parse(rs); $('.upload-tip') .addClass('text-success') .removeClass('text-error') .text(rs.len + '個文件上傳成功, number項值為' + rs.num) .show(); }, error: function(err) { } }); }); </script>
以上所述是小編給大家介紹的JS實現表單多文件上傳樣式美化支持選中文件後刪除相關項,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對網站的支持!