JS實現表單多檔案上傳樣式美化支援選中檔案後刪除相關項目,js檔案上傳
開發中會經常涉及到檔案上傳的需求,根據業務不同的需求,有不同的檔案上傳情況。
有簡單的單檔案上傳,有多檔案上傳,因瀏覽器原生的檔案上傳樣式及功能的支援度不算太高,很多時候我們會對樣式進行美化,對功能進行完善。
本文根據一個例子,對多檔案的上傳樣式做了一些簡單的美化(其實也沒怎麼美化。。),同時支援選擇檔案後自訂刪除相關的檔案,最後再上傳
文章篇幅較長,先簡單看看圖示:
一、檔案上傳基礎
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、占寬widthleft = 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、占寬widthleft = 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實現表單多檔案上傳樣式美化支援選中檔案後刪除相關項目,希望對大家有所協助,如果大家有任何疑問請給我留言,小編會及時回複大家的。在此也非常感謝大家對幫客之家網站的支援!