原生态HTML文件上传与下载
文件下載
傳統的文件下載有兩種方法:
這兩種方法效果一樣。但有個很大的問題,如果下載出現異常(連接路徑失效、文件不存在、網絡問題等),會導致原本的頁面被覆蓋掉,顯示404等錯誤信息。
大致的優化思路如下:
我們來逐一分析:
- 如果服務端沒有指定文件名,就以此屬性規定的名稱命名。
- 如果下載出現異常,該屬性的存在能夠保證頁面不會出問題。
- 如果服務端返回的不是文件、而是字符,如果download=‘’error.txt”,能夠通過打開此文件查看到返回的文本信息。
- 如果沒有異常,文件將會直接下載。
- 如果出現異常,iframe子頁面會報錯,父頁面不會受任何影響。
- 在網上看了看,大致的流程是:發送異步請求時設置responseType為blob,即接收流數據為blob對象保存在內存中。接收完成后,生成鏈接地址(1.通過FileReader對象將blob對象生成base64編碼 2.通過URL.createObjectURL生成指向文件內存的鏈接),寫入<a/>標簽的href屬性,然后模擬點擊<a/>按標簽實現下載。
- 此方法最大的問題是,因無法直接操作磁盤,故接收的文件必須先存放在內存中(且只有傳輸完成后才能構建blob對象),才能轉化成文件。因此,大文件的下載可能會把你的瀏覽器擠爆。
- 需要額外開啟websocket服務,此方法未做實踐。
總結以上方法,最推薦前兩種,方便簡單。
附上后端Django代碼(適用于前兩種方法):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | def?syncDownLoad(request): ????"文件下載" ????print("同步下載文件") ????startTime?=?time.time() ? ????def?file_iterator(file, chunk_size=1024): ????????with?open(file,?"rb") as f: ????????????while?True: ????????????????c?=?f.read(chunk_size) ????????????????if?c: ????????????????????yield?c ????????????????else: ????????????????????endTime?=?time.time() ????????????????????print("傳輸時間", endTime?-?startTime) ????????????????????break ? ????fileRoute?=?"/static/files/2018/12/18/第四章(1)學習動機概述.mp4" ????fileName?=?"第四章(1)學習動機概述.mp4" ????route?=?os.path.dirname(os.path.dirname(__file__))?+?fileRoute ????if?os.path.exists(route):??# 如果存在文件 ????????response?=?StreamingHttpResponse(file_iterator(route)) ????????# response['Content-Type'] = 'application/octet-stream' ????????response['Content-Type']?=?'text/html' ????????response['Content-Disposition']?=?'attachment;filename="{0}"'.format(fileName).encode("utf-8") ????????return?response ????else: ????????return?HttpResponse("cannot find file") |
參考鏈接:
https://scarletsky.github.io/2016/07/03/download-file-using-javascript/
https://my.oschina.net/watcher/blog/1525962
?
文件上傳
?概述
文件上傳需要處理的問題有:
1.多文件上傳? 2.異步上傳? 3.拖拽上傳? 4.上傳限制(限制大小、類型) 5.顯示上傳進度、上傳速度、中途取消上傳? 6.預覽文件
HTML DEMO
| 1 2 | <input?type="file" id="file" name="myfile" onchange="onchanges()" multiple="multiple"/> <input?type="button" onclick="SerialUploadFile()" value="上傳"/> |
一、多文件上傳
| 1 | <input?type="file" id="file" name="myfile" multiple="multiple"/>?<!-- multiple屬性 --> |
二、異步上傳
通過ajax等方式異步上傳,FormData對象支持傳輸文件。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | function UploadFile() { ??var fileObj = document.getElementById("file").files;??// js 獲取文件對象(FileList對象) ? ??// FormData 對象 ??var form =?new?FormData(); ??form.append("author",?"xueba");?????????????// 可以增加表單數據 ??for?(let i =?0; i < fileObj.length; i++) ??{ ?????form.append("file", fileObj[i]);????????// 文件對象 ??} ? ?????$.ajax({ ????????url:?"/file_upload/", ????????type:?"POST", ????????async:?true,??????// 異步上傳 ????????data: form, ????????contentType:?false,?// 必須false才會自動加上正確的Content-Type ????????processData:?false,?// 必須false才會避開jQuery對 formdata 的默認處理。XMLHttpRequest會對 formdata 進行正確的處理 ????????success: function (data) { ???????????data = JSON.parse(data); ???????????data.forEach((i)=>{ ??????????????console.log(i.code,i.file_url); ???????????}); ????????}, ????????error: function () { ???????????alert("aaa上傳失敗!"); ????????}, ?????}); ? } |
三、拖拽上傳
默認文本、圖像和鏈接可以被拖動。其它的元素想要被拖動,只需為標簽加一個draggable="true"屬性
| 1 | <div?draggable="true"><div/> |
?HTML5 API drag 和 drop
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 被拖動元素發生的事件 ????dragstart??? 被拖動元素開始拖動時 ????drag???????? 正在被拖動時 ????dragend????? 取消拖拽時 ????? 目標元素發生的事件(當某元素被綁定以下事件就變成了目標元素) ????dragenter??? 拖動元素進入目標上觸發 ????dragover???? 拖動元素在目標元素上移動觸發 ????dragleave??? 拖動元素離開目標時觸發 ????drop???????? 拖動元素在目標上釋放觸發,這時不會觸發dragleave ? 注意: ????1.目標元素默認不能夠被拖放drop,要在dragover事件中取消默認事件(e.preventDefault()) ????2.有些元素(img)被拖放后,默認以鏈接形式打開,要在drop事件中取消默認事件(e.preventDefault()) ????????【火狐瀏覽器可能不頂用,需要再加event.stopPropagation()】 ? dataTransfer(事件對象屬性(對象)) ????數據交換:只是簡單的拖拽沒有意義,我們還需要數據交換,即被拖動元素和目標元素之間的數據交換。 ????方法: ????????setData(key,value)? 設置數據(key和value都必須是string類型) ????????getData(key)??????? 獲取數據 ????????clearData()???????? 清除數據(不傳參清空所有數據) ????????setDragImage(imgElement,x,y)????? 設置元素移動過程中的圖像(參數:圖像元素,xy表示圖像內的偏移量) ????屬性: ????????dropEffect? 表示被拖動元素可以執行哪一種放置行為(一般在dragover事件內設置) ????????????none禁止放置(默認值)?? ????????????move移動到新的位置?? ????????????copy復制到新的位置 ????????????link ????????effectAllowed? 用來指定拖動時被允許的行為(一般無需設置) ????????????copy,move,link,copyLink,copyMove,linkMove,all,none,uninitialized默認值,相當于all. ????????files??? FileList對象。如果拖動的不是文件,此為空列表???? ????????items??? 返回DataTransferItems對象,該對象代表了拖動數據。 ????????types??? 返回一個DOMStringList對象,該對象包括了存入dataTransfer中數據的所有類型。 ????? ????? ????注意: ????????1.如果拖拽了文本,瀏覽器會自動調用setData(),設置對應文本數據 |
參考鏈接:
https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_Drag_and_Drop_API
https://developer.mozilla.org/zh-CN/docs/Web/API/DataTransfer
https://www.zhangxinxu.com/wordpress/2018/09/drag-drop-datatransfer-js/
http://www.sohu.com/a/198973397_291052
四、上傳限制
| 1 | <input?type="file"? accept="image/*" /> 接收全部格式的圖片 |
此外,獲取到的File對象中有type屬性可以得知文件類型,size屬性的得知文件大小
五、上傳進度、上傳速度、中途取消上傳
原生API
| 1 2 3 4 5 6 7 8 9 10 11 12 | xhr.onload =?function(e){};//上傳請求完成 xhr.onerror =?function(e){};//上傳異常 xhr.upload.onloadstart =?function(e){};//開始上傳 xhr.upload.onprogress =function(e){};//上傳進度? 這個方法會在文件每上傳一定字節時調用 ? e.loaded//表示已經上傳了多少byte的文件大小 e.total//表示文件總大小為多少byte 通過這兩個關鍵的屬性就可以去計算 上傳進度與速度 ? xhr.onreadystatechange =?function(){}//當xhr的狀態(上傳開始,結束,失敗)變化時會調用 該方法可以用來接收服務器返回的數據 ? 中途取消上傳 xhr.abort(); |
單文件上傳 或 多文件串行上傳 Demo:(該Demo只會有一個進度條,顯示上傳總進度。對應“異步上傳”的代碼)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | xhr.upload.addEventListener("progess",progessSFunction,false);?// 上傳過程中顯示進度和速度 ? function?progressSFunction(e) { ??????var?progressBar = document.getElementById(`pro`); ??????var?percentageDiv = document.getElementById(`per`); ??????if?(e.lengthComputable)?// lengthComputable表示進度信息是否可用 ??????{ ?????????progressBar.max = e.total; ?????????progressBar.value = e.loaded; ?????????let?speed = (e.loaded - progress[0].last_laoded) / (e.timeStamp - progress[0].last_time) +?" bytes/s"; ?????????let?percent = Math.round(e.loaded / e.total * 100) +?"%"; ?????????progress[0].last_laoded = e.loaded, progress[0].last_time = e.timeStamp; ?????????percentageDiv.innerHTML = percent +?" "?+ speed; ??????} ???} |
多文件并行上傳進度顯示:(多個進度條,分別上傳)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | // 多文件并行上傳 ????function?ParallelUploadFile() { ??????last_laoded = 0; ??????last_time = (new?Date()).getTime(); ? ??????var?fileObj = document.getElementById("file").files;??// js 獲取文件對象 ??????for?(let?k = 0; k < fileObj.length; k++) ??????{ ? ?????????let?domStr = `<div> ${fileObj[k].name},大小${fileObj[k].size}字節 ????????????????????????<progress?class='progressBar'?id='pro${k}'?value=''?max=''></progress> ????????????????????????<span?class='percentage'?id='per${k}'></span> ????????????????????????</div>`; ?????????$("body").append(domStr); ? ?????????// FormData 對象 ?????????var?form =?new?FormData(); ?????????form.append("author",?"xueba");?????????????// 可以增加表單數據 ?????????form.append("csrfmiddlewaretoken", $("[name = 'csrfmiddlewaretoken']").val()); ?????????form.append("file", fileObj[k]); ? ? ?????????// XMLHttpRequest 對象 ?????????{#var xhr = new XMLHttpRequest();#} ?????????{#xhr.open("post", "/file_upload/", true);#} ?????????{#xhr.onload = function () {#} ?????????{#?? alert("上傳完成!");#} ?????????{# };#} ?????????{#xhr.upload.addEventListener("progress", progressFunction, false);#} ?????????{#xhr.send(form);#} ? ?????????// jQuery ajax ?????????$.ajax({ ????????????url:?"/file_upload/", ????????????type:?"POST", ????????????async:?true,??????// 異步上傳 ????????????data: form, ????????????contentType:?false,?// 必須false才會自動加上正確的Content-Type ????????????processData:?false,?// 必須false才會避開jQuery對 formdata 的默認處理。XMLHttpRequest會對 formdata 進行正確的處理 ????????????xhr:?function?() { ???????????????let?xhr = $.ajaxSettings.xhr(); ???????????????xhr.upload.addEventListener("progress", (e) => {progressPFunction(e, k)},?false); ???????????????xhr.upload.onloadstart = (e) => { ??????????????????progress[k] = { ?????????????????????last_laoded: 0, ?????????????????????last_time: e.timeStamp, ??????????????????}; ???????????????}; ???????????????xhr.upload.onloadend = () => { ??????????????????delete?progress[k]; ???????????????}; ???????????????return?xhr; ????????????}, ????????????success:?function?(data) { ???????????????data = JSON.parse(data); ???????????????data.forEach((i) => { ??????????????????console.log(i.code, i.file_url); ???????????????}); ????????????}, ????????????error:?function?() { ???????????????alert("aaa上傳失敗!"); ????????????}, ?????????}); ??????} ? ????} |
六、預覽文件
預覽圖片
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | function?onchanges() {?// input file綁定onchange事件 ???let?files = document.getElementById("file").files; ???if(files[0].type.indexOf("image")>-1) ???{ ??????let?read =?new?FileReader(); ??????read.onload =?function(e) {?// 讀取操作完成時觸發 ?????????let?img =?new?Image(); ?????????img.src = e.target.result;?// 將base64編碼賦給src屬性 $("body")[0].appendChild(img); ??????}; ??????read.readAsDataURL(files[0]);?// 讀取文件轉化成base64編碼 ???} }? |
七、前后端匯總Demo
前端
HTML
| 1 2 | <input?type="file" id="file" name="myfile" onchange="onchanges()" multiple="multiple"/> <input?type="button" onclick="SerialUploadFile()" value="上傳"/> |
JavaScript
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | let?progress = {}; let?last_laoded; let?last_time; ? function?onchanges() { ???let?files = document.getElementById("file").files; ???console.log(`共${files.length}個文件`); ???let?countSize = 0; ???for?(let?i = 0; i < files.length; i++) { ??????console.log(`${files[i].name}? 大小${files[i].size}`); ??????countSize += files[i].size; ???} ???console.log(`共計占用${countSize}字節`); ???if?(files[0].type.indexOf("image") > -1) ???{ ??????let?read =?new?FileReader(); ??????read.onload =?function?(e) {?// 讀取操作完成時觸發 ?????????let?img =?new?Image(); ?????????img.src = e.target.result;?// 將base64編碼賦給src屬性 ?????????$("body")[0].appendChild(img); ? ??????}; ??????read.readAsDataURL(files[0]);?// 讀取文件轉化成base64編碼 ???} } ? ?// 多文件并行上傳 ?function?ParallelUploadFile() { ???last_laoded = 0; ???last_time = (new?Date()).getTime(); ? ???var?fileObj = document.getElementById("file").files;??// js 獲取文件對象 ???for?(let?k = 0; k < fileObj.length; k++) ???{ ? ??????let?domStr = `<div> ${fileObj[k].name},大小${fileObj[k].size}字節 ?????????????????????<progress?class='progressBar'?id='pro${k}'?value=''?max=''></progress> ?????????????????????<span?class='percentage'?id='per${k}'></span> ?????????????????????</div>`; ??????$("body").append(domStr); ? ??????// FormData 對象 ??????var?form =?new?FormData(); ??????form.append("author",?"xueba");?????????????// 可以增加表單數據 ??????form.append("csrfmiddlewaretoken", $("[name = 'csrfmiddlewaretoken']").val()); ??????form.append("file", fileObj[k]); ? ? ??????// XMLHttpRequest 對象 ??????{#var xhr = new XMLHttpRequest();#} ??????{#xhr.open("post", "/file_upload/", true);#} ??????{#xhr.onload = function () {#} ??????{#?? alert("上傳完成!");#} ??????{# };#} ??????{#xhr.upload.addEventListener("progress", progressFunction, false);#} ??????{#xhr.send(form);#} ? ??????// jQuery ajax ??????$.ajax({ ?????????url:?"/file_upload/", ?????????type:?"POST", ?????????async:?true,??????// 異步上傳 ?????????data: form, ?????????contentType:?false,?// 必須false才會自動加上正確的Content-Type ?????????processData:?false,?// 必須false才會避開jQuery對 formdata 的默認處理。XMLHttpRequest會對 formdata 進行正確的處理 ?????????xhr:?function?() { ????????????let?xhr = $.ajaxSettings.xhr(); ????????????xhr.upload.addEventListener("progress", (e) => {progressPFunction(e, k)},?false); ????????????xhr.upload.onloadstart = (e) => { ???????????????progress[k] = { ??????????????????last_laoded: 0, ??????????????????last_time: e.timeStamp, ???????????????}; ????????????}; ????????????xhr.upload.onloadend = () => { ???????????????delete?progress[k]; ????????????}; ????????????return?xhr; ?????????}, ?????????success:?function?(data) { ????????????data = JSON.parse(data); ????????????data.forEach((i) => { ???????????????console.log(i.code, i.file_url); ????????????}); ?????????}, ?????????error:?function?() { ????????????alert("aaa上傳失敗!"); ?????????}, ??????}); ???} ? ?} ? // 多文件串行上傳 function?SerialUploadFile() { ? ? ???var?fileObj = document.getElementById("file").files;??// js 獲取文件對象 ? ???let?domStr = `<div> ?????????????????????<progress?class='progressBar'?id='pro'?value=''?max=''></progress> ?????????????????????<span?class='percentage'?id='per'></span> ?????????????????</div>`; ???$("body").append(domStr); ? ???// FormData 對象 ???var?form =?new?FormData(); ???form.append("author",?"xueba");?????????????// 可以增加表單數據 ???for?(let?i = 0; i < fileObj.length; i++) ???{ ??????form.append("file", fileObj[i]);????????// 文件對象 ???} ? ???// jQuery ajax ???$.ajax({ ??????url:?"/file_upload/", ??????type:?"POST", ??????async:?true,??????// 異步上傳 ??????data: form, ??????contentType:?false,?// 必須false才會自動加上正確的Content-Type ??????processData:?false,?// 必須false才會避開jQuery對 formdata 的默認處理。XMLHttpRequest會對 formdata 進行正確的處理 ??????xhr:?function?() { ?????????let?xhr = $.ajaxSettings.xhr(); ?????????xhr.upload.addEventListener("progress", progressSFunction,?false); ?????????xhr.upload.onloadstart = (e) => { ????????????progress[0] = { ??????????????last_laoded: 0, ??????????????last_time: e.timeStamp, ???????????}; ????????????console.log("開始上傳",progress); ?????????}; ?????????xhr.upload.onloadend = () => { ????????????delete?progress[0]; ????????????console.log("結束上傳",progress); ?????????}; ?????????return?xhr; ??????}, ??????success:?function?(data) { ?????????data = JSON.parse(data); ?????????data.forEach((i) => { ????????????console.log(i.code, i.file_url); ?????????}); ??????}, ??????error:?function?() { ?????????alert("aaa上傳失敗!"); ??????}, ???}); ? } ? // jQuery版本進度條 function?Progressbar(e) { ???var?bar = $("#progressBar");?// 進度條 ???var?num = $("#percentage");??// 百分比 ???if?(e.lengthComputable) { ??????bar.attr("max", e.total); ??????bar.attr("value", e.loaded); ??????num.text(Math.round(e.loaded / e.total * 100) +?"%"); ???} ? } ? ? ?// 原生js版 并行進度條 ?function?progressPFunction(e, k) { ???var?progressBar = document.getElementById(`pro${k}`); ???var?percentageDiv = document.getElementById(`per${k}`); ???if?(e.lengthComputable) { ??????progressBar.max = e.total; ??????progressBar.value = e.loaded; ??????let?speed = (e.loaded - progress[k].last_laoded) / (e.timeStamp - progress[k].last_time) +?" bytes/s"; ??????let?percent = Math.round(e.loaded / e.total * 100) +?"%"; ??????progress[k].last_laoded = e.loaded, progress[k].last_time = e.timeStamp; ??????percentageDiv.innerHTML = percent +?" "?+ speed; ??????console.log(speed); ???} ?} ? // 原生js 串行進度條 function?progressSFunction(e) { ???var?progressBar = document.getElementById(`pro`); ???var?percentageDiv = document.getElementById(`per`); ???if?(e.lengthComputable)?// lengthComputable表示進度信息是否可用 ???{ ??????progressBar.max = e.total; ??????progressBar.value = e.loaded; ??????let?speed = (e.loaded - progress[0].last_laoded) / (e.timeStamp - progress[0].last_time) +?" bytes/s"; ??????let?percent = Math.round(e.loaded / e.total * 100) +?"%"; ??????progress[0].last_laoded = e.loaded, progress[0].last_time = e.timeStamp; ??????percentageDiv.innerHTML = percent +?" "?+ speed; ???} } |
Django后端
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | def?file_upload(request): ????"ajax文件上傳功能" ????resList, fileList?=?[], request.FILES.getlist("file") ????dir_path?=?'static/files/{0}/{1}/{2}'.format(time.strftime("%Y"),time.strftime("%m"),time.strftime("%d")) ????if?os.path.exists(dir_path)?is?False: ????????os.makedirs(dir_path) ????for?file?in?fileList: ????????file_path?=?'%s/%s'?%?(dir_path,?file.name) ????????file_url?=?'/%s/%s'?%?(dir_path,?file.name) ????????res?=?{"code":?0,?"file_url": ""} ????????with?open(file_path,?'wb') as f: ????????????if?f?==?False: ????????????????res['code']?=?1 ????????????for?chunk?in?file.chunks():?# chunks()代替read(),如果文件很大,可以保證不會拖慢系統內存 ????????????????f.write(chunk) ????????res['file_url']?=?file_url ????????resList.append(res) ????return?HttpResponse(json.dumps(resList)) |
參考:
https://www.cnblogs.com/potatog/p/9342448.html
https://www.w3cmm.com/ajax/progress-events.html
文章來源:https://www.cnblogs.com/V587Chinese/p/11371380.html
總結
以上是生活随笔為你收集整理的原生态HTML文件上传与下载的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 12.MYSQL高级(二)
- 下一篇: Python中的Number(数字)