純js實現(xiàn)上傳文件小工具

在前端開發(fā)工作中,上傳文件是一個很常見的功能,尤其是后臺管理方面的項目,經(jīng)常需要用到。而目前單頁面應(yīng)用(SPA)框架的流行,使得以往上傳文件的方式略顯笨拙,給開發(fā)帶來不便。那么今天我們就一起來實現(xiàn)一個非常簡潔易用的上傳工具。

如同你看到的標(biāo)題一樣,為什么說是純js呢?因為我們的目標(biāo)是:

  1. 不引入任何三方工具(jquery也不用)
  2. 不需要寫任何標(biāo)簽和css(反感寫樣式的童鞋,有沒有很期待?)
  3. 支持任何主流前端框架引入使用(這也是純js實現(xiàn)的好處)
老夫就是任性

明確了目標(biāo),接下來我們需要知道為什么要做這樣一個工具,能解決現(xiàn)實工作中的什么問題。

先回顧一下傳統(tǒng)上傳文件的方式:

  1. 首先需要寫一個表單標(biāo)簽 form,指定請求方式為 post,指定 enctypemultipart/form-data,定義好處理上傳文件的 action;
<form method="POST" action="https://your.domain.com/upload"></form>
  1. 在寫好的form標(biāo)簽里添加input標(biāo)簽,typefile,用來選擇要上傳的文件。
<input type="file" value="請選擇文件" />
  1. 繼續(xù)添加一個按鈕標(biāo)簽,類型為submit,用來提交表單。
<button type="submit">提交</button>

完整代碼如下

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

最終界面可能長這個樣子


還能再丑一點么

由于form表單的提交會導(dǎo)致頁面重定向,當(dāng)我們點擊選擇文件,然后點擊提交按鈕后,當(dāng)前頁面重定向到了action所指定的頁面,再加上目前很多處理文件上傳的action都是基于微服務(wù)架構(gòu)的RESTful接口,你會發(fā)現(xiàn)當(dāng)前頁面被刷新變成了接口返回的數(shù)據(jù)!

現(xiàn)在我們給form的 target 屬性設(shè)置 _blank,經(jīng)過測試發(fā)現(xiàn),當(dāng)前頁面雖然沒有刷新,但是action指向的頁面在一個新窗口中打開了,而實際需求往往是我們需要在當(dāng)前頁面接收返回的數(shù)據(jù),明確告訴用戶上傳是否成功,很多情況下還需要把上傳的文件回顯出來(比如圖片或者音頻資源),因此,我們必須在當(dāng)前頁面處理上傳結(jié)果。

我們在剛才的form下面寫一個iframe標(biāo)簽,指定id和name,然后將form的target屬性設(shè)置為這個iframe的id,這樣做的目的是將form表單的提交目標(biāo)指向這個iframe,當(dāng)提交表單之后,接口返回值便會顯示在iframe里面,代碼如下。

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

但這樣做依然沒有達(dá)到我們的預(yù)期,然而事情似乎變得容易了,我們把iframe隱藏起來,然后用js讀取iframe里面的內(nèi)容(接口返回值)即可,那我們什么時候去讀取呢?好在iframe每次加載成功后都會派發(fā)onload事件,而上傳文件接口的返回值指向了iframe會觸發(fā)它的onload事件,因此我們只需要為onload事件添加回調(diào)函數(shù)即可。

......
</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>

通過上面的傳統(tǒng)方式我們已經(jīng)基本實現(xiàn)了無刷新上傳文件,并且能相對容易的獲取到上傳接口的返回數(shù)據(jù)了。但我們發(fā)現(xiàn)這種實現(xiàn)方式的成本還是比較大的,尤其是在如今前端工程化,模塊化,組件化要求必不可少的情況下,這樣的實現(xiàn)方式顯然不能被接受。

接下來正式進(jìn)入本文的正題,不寫標(biāo)簽,不引入其他工具,用最純粹的方式實現(xiàn)上面的步驟。


首先讓我們分析一下剛才的示例,其中類型為file的input標(biāo)簽必不可少,因為它是用戶訪問本地磁盤文件的入口,但基于我們的目標(biāo)是不讓開發(fā)者寫任何標(biāo)簽,因此這個input必須由我們的工具動態(tài)創(chuàng)建,現(xiàn)在我們就來實現(xiàn)這微不足道但又必不可少的第一步。

//先給我們的目標(biāo)功能起一個名字 easyUpload
function easyUpload(){
  var input = document.createElement("input");
  input.type = "file";
}

接下來我們需要讓這個input去瀏覽本地磁盤以便讓用戶去選擇想要上傳的文件,但是問題來了,由于我們的工具不提供任何界面,那如何讓用戶去點擊input呢?很簡單,我們只需要調(diào)用這個動態(tài)input的click方法即可,但是,瀏覽器出于安全的考慮,選擇本地文件必須由用戶行為觸發(fā),因此我們定義的easyUpload函數(shù)也必須在用戶事件(如click回調(diào)函數(shù))里去調(diào)用,這一點請務(wù)必遵守。

input.click(); //代碼執(zhí)行到這里,本地文件選擇框便會打開
//加油,離目標(biāo)似乎很近了!

現(xiàn)在假設(shè)用戶已經(jīng)選擇了想要上傳的文件,那我們該如何去獲取這個文件呢?很簡單,input標(biāo)簽在每次選擇了文件之后都會觸發(fā)onchange事件,我們在這個回調(diào)里獲取文件即可。通過訪問input的files屬性,我們可以獲取到用戶選擇的文件集合,這里我們只實現(xiàn)單文件上傳,因此我們獲取files下標(biāo)為0的對象。這個對象包含了文件的一些常見屬性,如name(文件名)、size(文件大?。?、type(文件類型)等等,利用這些屬性,我們可以對小工具進(jìn)行功能的擴展,比如利用size和type可以控制上傳文件的大小限制和文件類型要求。這個不在本文討論的范圍內(nèi)。

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

拿到文件之后,我們就需要把它提交給服務(wù)端了,在上面的傳統(tǒng)方式中,提交文件我們用到了form標(biāo)簽,但由于我們的工具想要盡可能的利用編程式思路去實現(xiàn)文件上傳,如果還是像input一樣去動態(tài)創(chuàng)建dom的話,會覺得不夠優(yōu)雅,雖然一樣能解決我們的問題,而且原理也是完全相同的,但我們總是希望自己的代碼足夠清晰和簡介明了。所以,在這里我們放棄動態(tài)創(chuàng)建form的方式,而使用FormData來實現(xiàn)。

讓我們先了解一下FormData的定義。

FormData對象用以將數(shù)據(jù)編譯成鍵值對,以便用XMLHttpRequest來發(fā)送數(shù)據(jù)。其主要用于發(fā)送表單數(shù)據(jù),但亦可用于發(fā)送帶鍵數(shù)據(jù)(keyed data),而獨立于表單使用。如果表單enctype屬性設(shè)為multipart/form-data ,則會使用表單的submit()方法來發(fā)送數(shù)據(jù),從而,發(fā)送數(shù)據(jù)具有同樣形式。

相信你已經(jīng)發(fā)現(xiàn)了它的好處,F(xiàn)ormData完全就是form標(biāo)簽的對象形式,有了它,我們就可以用編程的方式去操作form了,然后通過XMLHttpRequest來提交表單,最后在它的異步回調(diào)里輕松的拿到接口數(shù)據(jù)!廢話少說,秀代碼!

var form = new FormData();
form.append("file", file); //第一個參數(shù)是后臺讀取的請求key值
form.append("fileName", file.name);
form.append("other", "666666"); //實際業(yè)務(wù)的其他請求參數(shù)
var xhr = new XMLHttpRequest();
var action = "http://localhost:8080/upload.do"; //上傳服務(wù)的接口地址
xhr.open("POST", action);
xhr.send(form); //發(fā)送表單數(shù)據(jù)
xhr.onreadystatechange = function(){
  if(xhr.readyState==4 && xhr.status==200){
    var resultObj = JSON.parse(xhr.responseText);
    //處理返回的數(shù)據(jù)......
  }
}

通過這段示例代碼我們不難發(fā)現(xiàn),使用FormData我們可以很輕松的把文件添加到表單里,并為這個文件指定參數(shù)名,文件名,以及其他跟實際上傳業(yè)務(wù)相關(guān)的附加參數(shù)。更重要的是,利用FormData我們還可以實現(xiàn)在線錄音,在線簽字,在線截圖以及你能想到的更多可能,這方面的內(nèi)容也不在本文討論的范圍內(nèi),如果你感興趣,歡迎在評論區(qū)域留言,我會抽時間來寫這方面的文章。

上面的代碼也為我們展示了如何用XMLHttpRequest來提交表單,以及如何獲取接口數(shù)據(jù),其中有幾個關(guān)鍵點需要注意,action變量和傳統(tǒng)方式表單的action屬性值是一樣的,是用來處理上傳文件的接口地址,這個變量我們可以作為參數(shù)傳遞給小工具,然后我們需要調(diào)用XMLHttpRequest對象的open方法,open方法的第一個參數(shù)定義了請求類型,這里我們務(wù)必要使用post類型,open方法的第二個參數(shù)就是上面定義的action,最后我們調(diào)用send方法將表單對象作為參數(shù)發(fā)送出去,別忘了在onreadystatechange回調(diào)函數(shù)里接收接口數(shù)據(jù),像這樣,一個最原始的Ajax請求就完成了,雖然原始,但它是解決問題最簡單,最直接有效的方式。固然npm上有豐富的工具,幾乎涵蓋了我們能遇到的所有問題,但有時候解決問題可能只需要很簡單的幾步,在這種情況下,如果還是去安裝三方依賴的話,反而把簡單問題復(fù)雜化了,這個話題仁者見仁,不再過多談?wù)摗?/p>

下面是全部示例代碼:

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); //第一個參數(shù)是后臺讀取的請求key值
    form.append("fileName", file.name);
    form.append("other", "666666"); //實際業(yè)務(wù)的其他請求參數(shù)
    var xhr = new XMLHttpRequest();
    var action = "http://localhost:8080/upload.do"; //上傳服務(wù)的接口地址
    xhr.open("POST", action);
    xhr.send(form); //發(fā)送表單數(shù)據(jù)
    xhr.onreadystatechange = function(){
      if(xhr.readyState==4 && xhr.status==200){
        var resultObj = JSON.parse(xhr.responseText);
        //處理返回的數(shù)據(jù)......
      }
    }
  }
}

本文重在討論解決問題的思路,其中展示的示例代碼僅供參考,如果需要使用這個小工具,歡迎安裝我的npm包。
包名:zq-easy-uploader
在線地址:zq-easy-uploader
Github地址:https://github.com/VirgilZhang/easyuploader

如果對本文中的任何內(nèi)容有異議,或者發(fā)現(xiàn)有錯誤的地方,歡迎在評論區(qū)域留言,筆者會及時回復(fù)大家。如果你喜歡筆者的文章,記得點贊哦,你們的支持是我寫作的動力!

[ 原創(chuàng)文章,轉(zhuǎn)載請注明出處 ]

我的其他文章

反饋在人機交互中的重要性
聊一聊CSS文本兩端對齊


歡迎大家在評論區(qū)留言自己想了解的前端話題,我會繼續(xù)推出更多精彩的文章!

原創(chuàng)不易,有錢的捧個錢場,給個打賞

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容