精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

文件上傳,搞懂這8種場景就夠了

開發(fā) 前端
在日常工作中,文件上傳是一個很常見的功能。在項目開發(fā)過程中,我們通常都會使用一些成熟的上傳組件來實現(xiàn)對應(yīng)的功能。一般來說,成熟的上傳組件不僅會提供漂亮 UI 或好的交互體驗,而且還會提供多種不同的上傳方式,以滿足不同的場景需求。

[[410449]]

本文轉(zhuǎn)載自微信公眾號「全棧修仙之路」,作者阿寶哥 。轉(zhuǎn)載本文請聯(lián)系全棧修仙之路公眾號。

在日常工作中,文件上傳是一個很常見的功能。在項目開發(fā)過程中,我們通常都會使用一些成熟的上傳組件來實現(xiàn)對應(yīng)的功能。一般來說,成熟的上傳組件不僅會提供漂亮 UI 或好的交互體驗,而且還會提供多種不同的上傳方式,以滿足不同的場景需求。

一般在我們工作中,主要會涉及到 8 種文件上傳的場景,每一種場景背后都使用不同的技術(shù),其中也有很多細節(jié)需要我們額外注意。今天阿寶哥就來帶大家總結(jié)一下這 8 種場景,讓大家能更好地理解成熟上傳組件所提供的功能。閱讀本文后,你將會了解以下的內(nèi)容:

  • 單文件上傳:利用 input 元素的 accept 屬性限制上傳文件的類型、利用 JS 檢測文件的類型及使用 Koa 實現(xiàn)單文件上傳的功能;
  • 多文件上傳:利用 input 元素的 multiple 屬性支持選擇多文件及使用 Koa 實現(xiàn)多文件上傳的功能;
  • 目錄上傳:利用 input 元素上的 webkitdirectory 屬性支持目錄上傳的功能及使用 Koa 實現(xiàn)目錄上傳并按文件目錄結(jié)構(gòu)存放的功能;
  • 壓縮目錄上傳:在目錄上傳的基礎(chǔ)上,利用 JSZip 實現(xiàn)壓縮目錄上傳的功能;
  • 拖拽上傳:利用拖拽事件和 DataTransfer 對象實現(xiàn)拖拽上傳的功能;
  • 剪貼板上傳:利用剪貼板事件和 Clipboard API 實現(xiàn)剪貼板上傳的功能;
  • 大文件分塊上傳:利用 Blob.slice、SparkMD5 和第三方庫 async-pool 實現(xiàn)大文件并發(fā)上傳的功能;
  • 服務(wù)端上傳:利用第三方庫 form-data 實現(xiàn)服務(wù)端文件流式上傳的功能。

一、單文件上傳

對于單文件上傳的場景來說,最常見的是圖片上傳的場景,所以我們就以圖片上傳為例,先來介紹單文件上傳的基本流程。

1.1 前端代碼

html

在以下代碼中,我們通過 input 元素的 accept 屬性限制了上傳文件的類型。這里使用 image/* 限制只能選擇圖片文件,當然你也可以設(shè)置特定的類型,比如 image/png 或 image/png,image/jpeg。

  1. <input id="uploadFile" type="file" accept="image/*" /> 
  2. <button id="submit" onclick="uploadFile()">上傳文件</button> 

需要注意的是,雖然我們把 input 元素的 accept 屬性設(shè)置為 image/png。但如果用戶把 jpg/jpeg 格式的圖片后綴名改為 .png,就可以成功繞過這個限制。要解決這個問題,我們可以通過讀取文件中的二進制數(shù)據(jù)來識別正確的文件類型。

要查看圖片對應(yīng)的二進制數(shù)據(jù),我們可以借助一些現(xiàn)成的編輯器,比如 Windows 平臺下的 WinHex 或 macOS 平臺下的 Synalyze It! Pro 十六進制編輯器。這里我們使用 Synalyze It! Pro 這個編輯器,來查看阿寶哥頭像對應(yīng)的二進制數(shù)據(jù)。

那么在前端能否不借助工具,讀取文件的二進制數(shù)據(jù)呢?答案是可以的,這里阿寶哥就不展開介紹了。感興趣的話,你可以閱讀 JavaScript 如何檢測文件的類型? 這篇文章。另外,需要注意的是 input 元素 accept 屬性有存在兼容性問題。比如 IE 9 以下不支持,具體如下圖所示:

(圖片來源 —— https://caniuse.com/input-file-accept)

js

  1. const uploadFileEle = document.querySelector("#uploadFile"); 
  2.  
  3. const request = axios.create({ 
  4.   baseURL: "http://localhost:3000/upload"
  5.   timeout: 60000,  
  6. }); 
  7.  
  8. async function uploadFile() { 
  9.   if (!uploadFileEle.files.length) return
  10.   const file = uploadFileEle.files[0]; // 獲取單個文件 
  11.   // 省略文件的校驗過程,比如文件類型、大小校驗 
  12.   upload({ 
  13.     url: "/single"
  14.     file, 
  15.   }); 
  16.  
  17. function upload({ url, file, fieldName = "file" }) { 
  18.   let formData = new FormData(); 
  19.   formData.set(fieldName, file); 
  20.   request.post(url, formData, { 
  21.     // 監(jiān)聽上傳進度 
  22.     onUploadProgress: function (progressEvent) { 
  23.       const percentCompleted = Math.round( 
  24.         (progressEvent.loaded * 100) / progressEvent.total 
  25.       ); 
  26.       console.log(percentCompleted); 
  27.      }, 
  28.   }); 

在以上代碼中,我們先把讀取的 File 對象封裝成 FormData 對象,然后利用 Axios 實例的 post 方法實現(xiàn)文件上傳的功能。在上傳前,通過設(shè)置請求配置對象的 onUploadProgress 屬性,就可以獲取文件的上傳進度。

1.2 服務(wù)端代碼

Koa 是一個簡單易用的 Web 框架,它的特點是優(yōu)雅、簡潔、輕量、自由度高。所以我們選擇它來搭建文件服務(wù),并使用以下中間件來實現(xiàn)相應(yīng)的功能:

  • koa-static:處理靜態(tài)資源的中間件;
  • @koa/cors:處理跨域請求的中間件;
  • @koa/multer:處理 multipart/form-data 的中間件;
  • @koa/router:處理路由的中間件。
  1. const path = require("path"); 
  2. const Koa = require("koa"); 
  3. const serve = require("koa-static"); 
  4. const cors = require("@koa/cors"); 
  5. const multer = require("@koa/multer"); 
  6. const Router = require("@koa/router"); 
  7.  
  8. const app = new Koa(); 
  9. const router = new Router(); 
  10. const PORT = 3000; 
  11. // 上傳后資源的URL地址 
  12. const RESOURCE_URL = `http://localhost:${PORT}`; 
  13. // 存儲上傳文件的目錄 
  14. const UPLOAD_DIR = path.join(__dirname, "/public/upload"); 
  15.  
  16. const storage = multer.diskStorage({ 
  17.   destination: async function (req, file, cb) { 
  18.     // 設(shè)置文件的存儲目錄 
  19.     cb(null, UPLOAD_DIR); 
  20.   }, 
  21.   filename: function (req, file, cb) { 
  22.     // 設(shè)置文件名 
  23.     cb(null, `${file.originalname}`); 
  24.   }, 
  25. }); 
  26.  
  27. const multerUpload = multer({ storage }); 
  28.  
  29. router.get("/", async (ctx) => { 
  30.   ctx.body = "歡迎使用文件服務(wù)(by 阿寶哥)"
  31. }); 
  32.  
  33. router.post( 
  34.   "/upload/single"
  35.   async (ctx, next) => { 
  36.     try { 
  37.       await next(); 
  38.       ctx.body = { 
  39.         code: 1, 
  40.         msg: "文件上傳成功"
  41.         url: `${RESOURCE_URL}/${ctx.file.originalname}`, 
  42.       }; 
  43.     } catch (error) { 
  44.       ctx.body = { 
  45.         code: 0, 
  46.         msg: "文件上傳失敗" 
  47.       }; 
  48.     } 
  49.   }, 
  50.   multerUpload.single("file"
  51. ); 
  52.  
  53. // 注冊中間件 
  54. app.use(cors()); 
  55. app.use(serve(UPLOAD_DIR)); 
  56. app.use(router.routes()).use(router.allowedMethods()); 
  57.  
  58. app.listen(PORT, () => { 
  59.   console.log(`app starting at port ${PORT}`); 
  60. }); 

以上代碼相對比較簡單,我們就不展開介紹了。Koa 內(nèi)核很簡潔,擴展功能都是通過中間件來實現(xiàn)。比如示例中使用到的路由、CORS、靜態(tài)資源處理等功能都是通過中間件實現(xiàn)。因此要想掌握 Koa 這個框架,核心是掌握它的中間件機制。如果你想深入了解的話,可以閱讀 如何更好地理解中間件和洋蔥模型 這篇文章。其實除了單文件上傳外,在文件上傳的場景中,我們也可以同時上傳多個文件。

單文件上傳示例:single-file-upload

https://github.com/semlinker/file-upload-demos/tree/master/single-file-upload

二、多文件上傳

要上傳多個文件,首先我們需要允許用戶同時選擇多個文件。要實現(xiàn)這個功能,我們可以利用 input 元素的 multiple 屬性。跟前面介紹的 accept 屬性一樣,該屬性也存在兼容性問題,具體如下圖所示:

(圖片來源 —— https://caniuse.com/mdn-api_htmlinputelement_multiple)

2.1 前端代碼

html

相比單文件上傳的代碼,多文件上傳場景下的 input 元素多了一個 multiple 屬性:

  1. <input id="uploadFile" type="file" accept="image/*" multiple /> 
  2. <button id="submit" onclick="uploadFile()">上傳文件</button> 

js

在單文件上傳的代碼中,我們通過 uploadFileEle.files[0] 獲取單個文件,而對于多文件上傳來說,我們需要獲取已選擇的文件列表,即通過 uploadFileEle.files 來獲取,它返回的是一個 FileList 對象。

  1. async function uploadFile() { 
  2.   if (!uploadFileEle.files.length) return
  3.   const files = Array.from(uploadFileEle.files); 
  4.   upload({ 
  5.     url: "/multiple"
  6.     files, 
  7.   }); 

因為要支持上傳多個文件,所以我們需要同步更新一下 upload 函數(shù)。對應(yīng)的處理邏輯就是遍歷文件列表,然后使用 FormData 對象的 append 方法來添加多個文件,具體代碼如下所示:

  1. function upload({ url, files, fieldName = "file" }) { 
  2.   let formData = new FormData(); 
  3.   files.forEach((file) => { 
  4.     formData.append(fieldName, file); 
  5.   }); 
  6.   request.post(url, formData, { 
  7.     // 監(jiān)聽上傳進度 
  8.     onUploadProgress: function (progressEvent) { 
  9.       const percentCompleted = Math.round( 
  10.         (progressEvent.loaded * 100) / progressEvent.total 
  11.       ); 
  12.       console.log(percentCompleted); 
  13.     }, 
  14.   }); 

2.2 服務(wù)端代碼

在以下代碼中,我們定義了一個新的路由 —— /upload/multiple 來處理多文件上傳的功能。當所有文件都成功上傳后,就會返回一個已上傳文件的 url 地址列表:

  1. router.post( 
  2.   "/upload/multiple"
  3.   async (ctx, next) => { 
  4.     try { 
  5.       await next(); 
  6.       urls = ctx.files.file.map(file => `${RESOURCE_URL}/${file.originalname}`); 
  7.       ctx.body = { 
  8.         code: 1, 
  9.         msg: "文件上傳成功"
  10.         urls 
  11.       }; 
  12.     } catch (error) { 
  13.       ctx.body = { 
  14.         code: 0, 
  15.         msg: "文件上傳失敗"
  16.       }; 
  17.     } 
  18.   }, 
  19.   multerUpload.fields([ 
  20.     { 
  21.       name"file", // 與FormData表單項的fieldName相對應(yīng) 
  22.     }, 
  23.   ]) 
  24. ); 

介紹完單文件和多文件上傳的功能,接下來我們來介紹目錄上傳的功能。

多文件上傳示例:multiple-file-upload

https://github.com/semlinker/file-upload-demos/tree/master/multiple-file-upload

三、目錄上傳

可能你還不知道,input 元素上還有一個的 webkitdirectory 屬性。當設(shè)置了 webkitdirectory 屬性之后,我們就可以選擇目錄了。

  1. <input id="uploadFile" type="file" accept="image/*" webkitdirectory /> 

當我們選擇了指定目錄之后,比如阿寶哥桌面上的 images 目錄,就會顯示以下確認框:

點擊上傳按鈕之后,我們就可以獲取文件列表。列表中的文件對象上含有一個 webkitRelativePath 屬性,用于表示當前文件的相對路徑。

雖然通過 webkitdirectory 屬性可以很容易地實現(xiàn)選擇目錄的功能,但在實際項目中我們還需要考慮它的兼容性。比如在 IE 11 以下的版本就不支持該屬性,其它瀏覽器的兼容性如下圖所示:

(圖片來源 —— https://caniuse.com/?search=webkitdirectory)

了解完 webkitdirectory 屬性的兼容性,我們先來介紹前端的實現(xiàn)代碼。

3.1 前端代碼

為了讓服務(wù)端能按照實際的目錄結(jié)構(gòu)來存放對應(yīng)的文件,在添加表單項時我們需要把當前文件的路徑提交到服務(wù)端。此外,為了確保@koa/multer 能正確處理文件的路徑,我們需要對路徑進行特殊處理。即把 / 斜杠替換為 @ 符號。對應(yīng)的處理方式如下所示:

  1. function upload({ url, files, fieldName = "file" }) { 
  2.   let formData = new FormData(); 
  3.   files.forEach((file, i) => { 
  4.     formData.append( 
  5.       fieldName,  
  6.       files[i], 
  7.       files[i].webkitRelativePath.replace(/\//g, "@"); 
  8.     ); 
  9.   }); 
  10.   request.post(url, formData); // 省略上傳進度處理 

3.2 服務(wù)端代碼

目錄上傳與多文件上傳,服務(wù)端代碼的主要區(qū)別就是 @koa/multer 中間件的配置對象不一樣。在 destination 屬性對應(yīng)的函數(shù)中,我們需要把文件名中 @ 還原成 /,然后根據(jù)文件的實際路徑來生成目錄。

  1. const fse = require("fs-extra"); 
  2. const storage = multer.diskStorage({ 
  3.   destination: async function (req, file, cb) { 
  4.     // images@image-1.jpeg => images/image-1.jpeg 
  5.     let relativePath = file.originalname.replace(/@/g, path.sep); 
  6.     let index = relativePath.lastIndexOf(path.sep); 
  7.     let fileDir = path.join(UPLOAD_DIR, relativePath.substr(0, index)); 
  8.     // 確保文件目錄存在,若不存在的話,會自動創(chuàng)建 
  9.     await fse.ensureDir(fileDir);  
  10.     cb(null, fileDir); 
  11.   }, 
  12.   filename: function (req, file, cb) { 
  13.     let parts = file.originalname.split("@"); 
  14.     cb(null, `${parts[parts.length - 1]}`);  
  15.   }, 
  16. }); 

現(xiàn)在我們已經(jīng)實現(xiàn)了目錄上傳的功能,那么能否把目錄下的文件壓縮成一個壓縮包后再上傳呢?答案是可以的,接下來我們來介紹如何實現(xiàn)壓縮目錄上傳的功能。

目錄上傳示例:directory-upload

https://github.com/semlinker/file-upload-demos/tree/master/directory-upload

四、壓縮目錄上傳

在 JavaScript 如何在線解壓 ZIP 文件? 這篇文章中,介紹了在瀏覽器端如何使用 JSZip 這個庫實現(xiàn)在線解壓 ZIP 文件的功能。JSZip 這個庫除了可以解析 ZIP 文件之外,它還可以用來 創(chuàng)建和編輯 ZIP 文件。利用 JSZip 這個庫提供的 API,我們就可以把目錄下的所有文件壓縮成 ZIP 文件,然后再把生成的 ZIP 文件上傳到服務(wù)器。

4.1 前端代碼

JSZip 實例上的 file(name, data [,options]) 方法,可以把文件添加到 ZIP 文件中?;谠摲椒ㄎ覀兛梢苑庋b了一個 generateZipFile 函數(shù),用于把目錄下的文件列表壓縮成一個 ZIP 文件。以下是 generateZipFile 函數(shù)的具體實現(xiàn):

  1. function generateZipFile( 
  2.   zipName, files, 
  3.   options = { type: "blob", compression: "DEFLATE" } 
  4. ) { 
  5.   return new Promise((resolve, reject) => { 
  6.     const zip = new JSZip(); 
  7.     for (let i = 0; i < files.length; i++) { 
  8.       zip.file(files[i].webkitRelativePath, files[i]); 
  9.     } 
  10.     zip.generateAsync(options).then(function (blob) { 
  11.       zipName = zipName || Date.now() + ".zip"
  12.       const zipFile = new File([blob], zipName, { 
  13.         type: "application/zip"
  14.       }); 
  15.       resolve(zipFile); 
  16.     }); 
  17.   }); 

在創(chuàng)建完 generateZipFile 函數(shù)之后,我們需要更新一下前面已經(jīng)介紹過的 uploadFile 函數(shù):

  1. async function uploadFile() { 
  2.   let fileList = uploadFileEle.files; 
  3.   if (!fileList.length) return
  4.   let webkitRelativePath = fileList[0].webkitRelativePath; 
  5.   let zipFileName = webkitRelativePath.split("/")[0] + ".zip"
  6.   let zipFile = await generateZipFile(zipFileName, fileList); 
  7.   upload({ 
  8.     url: "/single"
  9.     file: zipFile, 
  10.     fileName: zipFileName 
  11.   }); 

在以上的 uploadFile 函數(shù)中,我們會對返回的 FileList 對象進行處理,即調(diào)用 generateZipFile 函數(shù)來生成 ZIP 文件。此外,為了在服務(wù)端接收壓縮文件時,能獲取到文件名,我們?yōu)?upload 函數(shù)增加了一個 fileName 參數(shù),該參數(shù)用于調(diào)用 formData.append 方法時,設(shè)置上傳文件的文件名:

  1. function upload({ url, file, fileName, fieldName = "file" }) { 
  2.   if (!url || !file) return
  3.   let formData = new FormData(); 
  4.   formData.append( 
  5.     fieldName, file, fileName 
  6.   ); 
  7.   request.post(url, formData); // 省略上傳進度跟蹤 

以上就是壓縮目錄上傳,前端部分的 JS 代碼,服務(wù)端的代碼可以參考前面單文件上傳的相關(guān)代碼。

壓縮目錄上傳示例:directory-compress-upload

https://github.com/semlinker/file-upload-demos/tree/master/directory-compress-upload

五、拖拽上傳

要實現(xiàn)拖拽上傳的功能,我們需要先了解與拖拽相關(guān)的事件。比如 drag、dragend、dragenter、dragover 或 drop 事件等。這里我們只介紹接下來要用到的拖拽事件:

  • dragenter:當拖拽元素或選中的文本到一個可釋放目標時觸發(fā);
  • dragover:當元素或選中的文本被拖到一個可釋放目標上時觸發(fā)(每100毫秒觸發(fā)一次);
  • dragleave:當拖拽元素或選中的文本離開一個可釋放目標時觸發(fā);
  • drop:當元素或選中的文本在可釋放目標上被釋放時觸發(fā)。

基于上面的這些事件,我們就可以提高用戶拖拽的體驗。比如當用戶拖拽的元素進入目標區(qū)域時,對目標區(qū)域進行高亮顯示。當用戶拖拽的元素離開目標區(qū)域時,移除高亮顯示。很明顯當 drop 事件觸發(fā)后,拖拽的元素已經(jīng)放入目標區(qū)域了,這時我們就需要獲取對應(yīng)的數(shù)據(jù)。

那么如何獲取拖拽對應(yīng)的數(shù)據(jù)呢?這時我們需要使用 DataTransfer 對象,該對象用于保存拖動并放下過程中的數(shù)據(jù)。它可以保存一項或多項數(shù)據(jù),這些數(shù)據(jù)項可以是一種或者多種數(shù)據(jù)類型。若拖動操作涉及拖動文件,則我們可以通過 DataTransfer 對象的 files 屬性來獲取文件列表。

介紹完拖拽上傳相關(guān)的知識后,我們來看一下具體如何實現(xiàn)拖拽上傳的功能。

5.1 前端代碼

html

  1. <div id="dropArea"
  2.    <p>拖拽上傳文件</p> 
  3.    <div id="imagePreview"></div> 
  4. </div> 

 

 

 

 

 

 

css

  1. #dropArea { 
  2.   width: 300px; 
  3.   height: 300px; 
  4.   border: 1px dashed gray; 
  5.   margin-bottom: 20px; 
  6. #dropArea p { 
  7.   text-align: center; 
  8.   color: #999; 
  9. #dropArea.highlighted { 
  10.   background-color: #ddd; 
  11. #imagePreview { 
  12.   max-height: 250px; 
  13.   overflow-y: scroll
  14. #imagePreview img { 
  15.   width: 100%; 
  16.   display: block; 
  17.   margin: auto; 

js

為了讓大家能夠更好地閱讀拖拽上傳的相關(guān)代碼,我們把代碼拆成 4 部分來講解:

1、阻止默認拖拽行為

  1. const dropAreaEle = document.querySelector("#dropArea"); 
  2. const imgPreviewEle = document.querySelector("#imagePreview"); 
  3. const IMAGE_MIME_REGEX = /^image\/(jpe?g|gif|png)$/i; 
  4.  
  5. ["dragenter""dragover""dragleave""drop"].forEach((eventName) => { 
  6.    dropAreaEle.addEventListener(eventName, preventDefaults, false); 
  7.    document.body.addEventListener(eventName, preventDefaults, false); 
  8. }); 
  9.  
  10. function preventDefaults(e) { 
  11.   e.preventDefault(); 
  12.   e.stopPropagation(); 

2、切換目標區(qū)域的高亮狀態(tài)

  1. ["dragenter""dragover"].forEach((eventName) => { 
  2.     dropAreaEle.addEventListener(eventName, highlight, false); 
  3. }); 
  4. ["dragleave""drop"].forEach((eventName) => { 
  5.     dropAreaEle.addEventListener(eventName, unhighlight, false); 
  6. }); 
  7.  
  8. // 添加高亮樣式 
  9. function highlight(e) { 
  10.   dropAreaEle.classList.add("highlighted"); 
  11.  
  12. // 移除高亮樣式 
  13. function unhighlight(e) { 
  14.   dropAreaEle.classList.remove("highlighted"); 

3、處理圖片預(yù)覽

  1. dropAreaEle.addEventListener("drop", handleDrop, false); 
  2.  
  3. function handleDrop(e) { 
  4.   const dt = e.dataTransfer; 
  5.   const files = [...dt.files]; 
  6.   files.forEach((file) => { 
  7.     previewImage(file, imgPreviewEle); 
  8.   }); 
  9.   // 省略文件上傳代碼 
  10.  
  11. function previewImage(file, container) { 
  12.   if (IMAGE_MIME_REGEX.test(file.type)) { 
  13.     const reader = new FileReader(); 
  14.     reader.onload = function (e) { 
  15.       let img = document.createElement("img"); 
  16.       img.src = e.target.result; 
  17.       container.append(img); 
  18.     }; 
  19.     reader.readAsDataURL(file); 
  20.   } 

4、文件上傳

  1. function handleDrop(e) { 
  2.   const dt = e.dataTransfer; 
  3.   const files = [...dt.files]; 
  4.   // 省略圖片預(yù)覽代碼 
  5.   files.forEach((file) => { 
  6.     upload({ 
  7.       url: "/single"
  8.       file, 
  9.     }); 
  10.   }); 
  11.  
  12. const request = axios.create({ 
  13.   baseURL: "http://localhost:3000/upload"
  14.   timeout: 60000, 
  15. }); 
  16.  
  17. function upload({ url, file, fieldName = "file" }) { 
  18.   let formData = new FormData(); 
  19.   formData.set(fieldName, file); 
  20.   request.post(url, formData, { 
  21.     // 監(jiān)聽上傳進度 
  22.     onUploadProgress: function (progressEvent) { 
  23.       const percentCompleted = Math.round( 
  24.         (progressEvent.loaded * 100) / progressEvent.total 
  25.       ); 
  26.       console.log(percentCompleted); 
  27.     }, 
  28.   }); 

拖拽上傳算是一個比較常見的場景,很多成熟的上傳組件都支持該功能。其實除了拖拽上傳外,還可以利用剪貼板實現(xiàn)復(fù)制上傳的功能。

拖拽上傳示例:drag-drop-upload

https://github.com/semlinker/file-upload-demos/tree/master/drag-drop-upload

六、剪貼板上傳

在介紹如何實現(xiàn)剪貼板上傳的功能前,我們需要了解一下 Clipboard API。Clipboard 接口實現(xiàn)了 Clipboard API,如果用戶授予了相應(yīng)的權(quán)限,就能提供系統(tǒng)剪貼板的讀寫訪問。在 Web 應(yīng)用程序中,Clipboard API 可用于實現(xiàn)剪切、復(fù)制和粘貼功能。該 API 用于取代通過 document.execCommand API 來實現(xiàn)剪貼板的操作。

在實際項目中,我們不需要手動創(chuàng)建 Clipboard 對象,而是通過 navigator.clipboard 來獲取 Clipboard 對象:

在獲取 Clipboard 對象之后,我們就可以利用該對象提供的 API 來訪問剪貼板,比如:

  1. navigator.clipboard.readText().then
  2.   clipText => document.querySelector(".editor").innerText = clipText 
  3. ); 

以上代碼將 HTML 中含有 .editor 類的第一個元素的內(nèi)容替換為剪貼板的內(nèi)容。如果剪貼板為空,或者不包含任何文本,則元素的內(nèi)容將被清空。這是因為在剪貼板為空或者不包含文本時,readText 方法會返回一個空字符串。

利用 Clipboard API 我們可以很方便地操作剪貼板,但實際項目使用過程中也得考慮它的兼容性:

(圖片來源 —— https://caniuse.com/async-clipboard)

要實現(xiàn)剪貼板上傳的功能,可以分為以下 3 個步驟:

  • 監(jiān)聽容器的粘貼事件;
  • 讀取并解析剪貼板中的內(nèi)容;
  • 動態(tài)構(gòu)建 FormData 對象并上傳。

了解完上述步驟,接下來我們來分析一下具體實現(xiàn)的代碼。

6.1 前端代碼

html

  1. <div id="uploadArea"
  2.    <p>請先復(fù)制圖片后再執(zhí)行粘貼操作</p> 
  3. </div> 

 

 

 

css

  1. #uploadArea { 
  2.    width: 400px; 
  3.    height: 400px; 
  4.    border: 1px dashed gray; 
  5.    display: table-cell; 
  6.    vertical-align: middle; 
  7. #uploadArea p { 
  8.    text-align: center; 
  9.    color: #999; 
  10. #uploadArea img { 
  11.    max-width: 100%; 
  12.    max-height: 100%; 
  13.    display: block; 
  14.    margin: auto; 

js

在以下代碼中,我們使用 addEventListener 方法為 uploadArea 容器添加 paste 事件。在對應(yīng)的事件處理函數(shù)中,我們會優(yōu)先判斷當前瀏覽器是否支持異步 Clipboard API。如果支持的話,就會通過 navigator.clipboard.read 方法來讀取剪貼板中的內(nèi)容。在讀取內(nèi)容之后,我們會通過正則判斷剪貼板項中是否包含圖片資源,如果有的話會調(diào)用 previewImage 方法執(zhí)行圖片預(yù)覽操作并把返回的 blob 對象保存起來,用于后續(xù)的上傳操作。

  1. const IMAGE_MIME_REGEX = /^image\/(jpe?g|gif|png)$/i; 
  2. const uploadAreaEle = document.querySelector("#uploadArea"); 
  3.  
  4. uploadAreaEle.addEventListener("paste", async (e) => { 
  5.   e.preventDefault(); 
  6.   const files = []; 
  7.   if (navigator.clipboard) { 
  8.     let clipboardItems = await navigator.clipboard.read(); 
  9.     for (const clipboardItem of clipboardItems) { 
  10.       for (const type of clipboardItem.types) { 
  11.         if (IMAGE_MIME_REGEX.test(type)) { 
  12.            const blob = await clipboardItem.getType(type); 
  13.            insertImage(blob, uploadAreaEle); 
  14.            files.push(blob); 
  15.          } 
  16.        } 
  17.      } 
  18.   } else { 
  19.       const items = e.clipboardData.items; 
  20.       for (let i = 0; i < items.length; i++) { 
  21.         if (IMAGE_MIME_REGEX.test(items[i].type)) { 
  22.           let file = items[i].getAsFile(); 
  23.           insertImage(file, uploadAreaEle); 
  24.           files.push(file); 
  25.         } 
  26.       } 
  27.   } 
  28.   if (files.length > 0) { 
  29.     confirm("剪貼板檢測到圖片文件,是否執(zhí)行上傳操作?")  
  30.       && upload({ 
  31.            url: "/multiple"
  32.            files, 
  33.       }); 
  34.    } 
  35. }); 

若當前瀏覽器不支持異步 Clipboard API,則我們會嘗試通過 e.clipboardData.items 來訪問剪貼板中的內(nèi)容。需要注意的是,在遍歷剪貼板內(nèi)容項的時候,我們是通過 getAsFile 方法來獲取剪貼板的內(nèi)容。當然該方法也存在兼容性問題,具體如下圖所示:

(圖片來源 —— https://caniuse.com/mdn-api_datatransferitem_getasfile)

前面已經(jīng)提到,當從剪貼板解析到圖片資源時,會讓用戶進行預(yù)覽,該功能是基于 FileReader API 來實現(xiàn)的,對應(yīng)的代碼如下所示:

  1. function previewImage(file, container) { 
  2.   const reader = new FileReader(); 
  3.   reader.onload = function (e) { 
  4.     let img = document.createElement("img"); 
  5.     img.src = e.target.result; 
  6.     container.append(img); 
  7.   }; 
  8.   reader.readAsDataURL(file); 

當用戶預(yù)覽完成后,如果確認上傳我們就會執(zhí)行文件的上傳操作。因為文件是從剪貼板中讀取的,所以在上傳前我們會根據(jù)文件的類型,自動為它生成一個文件名,具體是采用時間戳加文件后綴的形式:

  1. function upload({ url, files, fieldName = "file" }) { 
  2.   let formData = new FormData(); 
  3.   files.forEach((file) => { 
  4.     let fileName = +new Date() + "." + IMAGE_MIME_REGEX.exec(file.type)[1]; 
  5.     formData.append(fieldName, file, fileName); 
  6.   }); 
  7.   request.post(url, formData); 

前面我們已經(jīng)介紹了文件上傳的多種不同場景,接下來我們來介紹一個 “特殊” 的場景 —— 大文件上傳。

剪貼板上傳示例:clipboard-upload

https://github.com/semlinker/file-upload-demos/tree/master/clipboard-upload

七、大文件分塊上傳

相信你可能已經(jīng)了解大文件上傳的解決方案,在上傳大文件時,為了提高上傳的效率,我們一般會使用 Blob.slice 方法對大文件按照指定的大小進行切割,然后通過多線程進行分塊上傳,等所有分塊都成功上傳后,再通知服務(wù)端進行分塊合并。具體處理方案如下圖所示:

因為在 JavaScript 中如何實現(xiàn)大文件并發(fā)上傳? 這篇文章中,阿寶哥已經(jīng)詳細介紹了大文件并發(fā)上傳的方案,所以這里就不展開介紹了。我們只回顧一下大文件并發(fā)上傳的完整流程:

前面我們都是介紹客戶端文件上傳的場景,其實也有服務(wù)端文件上傳的場景。比如在服務(wù)端動態(tài)生成海報后,上傳到另外一臺服務(wù)器或云廠商的 OSS(Object Storage Service)。下面我們就以 Node.js 為例來介紹在服務(wù)端如何上傳文件。

大文件分塊上傳示例:big-file-upload

https://github.com/semlinker/file-upload-demos/tree/master/big-file-upload

八、服務(wù)端上傳

服務(wù)器上傳就是把文件從一臺服務(wù)器上傳到另外一臺服務(wù)器。借助 Github 上 form-data 這個庫提供的功能,我們可以很容易地實現(xiàn)服務(wù)器上傳的功能。下面我們來簡單介紹一下單文件和多文件上傳的功能:

8.1 單文件上傳

  1. const fs = require("fs"); 
  2. const path = require("path"); 
  3. const FormData = require("form-data"); 
  4.  
  5. const form1 = new FormData(); 
  6. form1.append("file", fs.createReadStream(path.join(__dirname, "images/image-1.jpeg"))); 
  7. form1.submit("http://localhost:3000/upload/single", (error, response) => { 
  8.   if(error) { 
  9.     console.log("單圖上傳失敗"); 
  10.     return
  11.   } 
  12.   console.log("單圖上傳成功"); 
  13. }); 

8.2 多文件上傳

  1. const form2 = new FormData(); 
  2. form2.append("file", fs.createReadStream(path.join(__dirname, "images/image-2.jpeg"))); 
  3. form2.append("file", fs.createReadStream(path.join(__dirname, "images/image-3.jpeg"))); 
  4. form2.submit("http://localhost:3000/upload/multiple", (error, response) => { 
  5.   if(error) { 
  6.     console.log("多圖上傳失敗"); 
  7.     return
  8.   } 
  9.   console.log("多圖上傳成功"); 
  10. }); 

觀察以上代碼可知,創(chuàng)建完 FormData 對象之后,我們只需要通過 fs.createReadStream API 創(chuàng)建可讀流,然后調(diào)用 FormData 對象的 append 方法添加表單項,最后再調(diào)用 submit 方法執(zhí)行提交操作即可。

其實除了 ReadableStream 之外,F(xiàn)ormData 對象的 append 方法還支持以下類型:

  1. const FormData = require('form-data'); 
  2. const http = require('http'); 
  3.  
  4. const form = new FormData(); 
  5. http.request('http://nodejs.org/images/logo.png'function(response) { 
  6.   form.append('my_field''my value'); 
  7.   form.append('my_buffer', new Buffer(10)); 
  8.   form.append('my_logo', response); 
  9. }); 

服務(wù)端文件上傳的內(nèi)容就介紹到這里,關(guān)于 form-data 這個庫的其他用法,感興趣的話,可以閱讀對應(yīng)的使用文檔。其實除了以上介紹的八種場景外,在日常工作中,你也可能會使用一些同步工具,比如 Syncthing 文件同步工具實現(xiàn)文件傳輸。好的,本文的所有內(nèi)容都已經(jīng)介紹完了,最后我們來做一個總結(jié)。

服務(wù)端上傳示例:server-upload

https://github.com/semlinker/file-upload-demos/tree/master/server-upload

九、總結(jié)

本文阿寶哥詳細介紹了文件上傳的八種場景,希望閱讀完本文后,你對八種場景背后使用的技術(shù)有一定的了解。由于篇幅有限,阿寶哥就沒有展開介紹與 multipart/form-data 類型相關(guān)的內(nèi)容,感興趣的小伙伴可以自行了解一下。

此外,在實際項目中,你可以考慮直接使用成熟的第三方組件,比如 Github 上的 Star 數(shù) 11K+ 的 filepond。該組件采用插件化的架構(gòu),以插件的方式,提供了非常多的功能,比如 File encode、File rename、File poster、Image preview 和 Image crop 等。總之,它是一個很不錯的組件,以后有機會的話,大家可以嘗試一下。

十、參考資源

MDN- Clipboard

MDN - DataTransfer

JSZip- API

JavaScript 如何檢測文件的類型?

JavaScript 中如何實現(xiàn)大文件并發(fā)上傳?

 

責任編輯:武曉燕 來源: 全棧修仙之路
相關(guān)推薦

2021-08-04 00:10:49

場景版本大文件

2019-08-20 14:40:35

Redis數(shù)據(jù)庫

2020-09-10 09:31:34

Nginx HTTP代理服務(wù)器

2020-09-09 12:55:28

Nginx高并發(fā)性能

2019-08-13 15:36:57

限流算法令牌桶

2025-11-10 01:35:00

2020-11-06 10:01:06

Nginx

2021-01-18 11:41:22

SQL數(shù)據(jù)庫編程語言

2021-06-30 00:14:24

JS代碼數(shù)組

2019-07-31 15:56:57

Jvm虛擬機Content

2024-07-05 11:01:13

2018-09-12 15:16:19

數(shù)據(jù)中心網(wǎng)絡(luò)機房

2021-06-21 09:22:53

按鈕設(shè)計UI標簽

2021-07-14 23:57:26

Vue高級技巧

2022-04-12 08:46:30

for 循環(huán)遍歷字符串

2020-05-08 11:14:33

Vue開發(fā)代碼

2018-07-06 15:25:50

程序員編程python

2025-07-24 09:45:43

2017-09-15 16:37:27

2019-03-04 08:43:29

LeaderTL職責
點贊
收藏

51CTO技術(shù)棧公眾號

欧美日韩精品免费看| 91精品国产成人www| 精产国品一区二区三区| h片在线观看下载| 久久免费精品国产久精品久久久久 | 午夜精品理论片| 特级西西www444人体聚色 | 国产精品18毛片一区二区| 性无码专区无码| 天天综合亚洲| 精品视频在线观看日韩| 欧美成人乱码一二三四区免费| 污视频网站在线免费| 国产亚洲一区二区三区四区 | 国产富婆一级全黄大片| 大桥未久在线播放| 久久精品欧美一区二区三区不卡| 亚洲jizzjizz日本少妇| 探花视频在线观看| 欧美激情视频一区二区三区在线播放| 日韩精品在线免费| 美女被艹视频网站| 日本美女久久| 精品色蜜蜜精品视频在线观看| 亚洲国产一区在线| 亚洲色图狠狠干| 国产精品一区二区在线看| 热久久免费国产视频| 久久高清无码视频| 97久久视频| 国产午夜精品免费一区二区三区| 精品人妻二区中文字幕| 国产激情欧美| 一本高清dvd不卡在线观看| 国产 国语对白 露脸| 午夜毛片在线| 国产性色一区二区| 久久亚洲免费| 天天操天天干天天操| 国产精品一品视频| 147欧美人体大胆444| 一区二区 亚洲| 青椒成人免费视频| 国产精国产精品| 亚洲 欧美 成人| av一区二区三区黑人| 69av在线视频| 精品午夜福利视频| 欧美一区二区三区免费看| 精品一区二区三区三区| 91av在线免费| 久9re热视频这里只有精品| 日韩欧美国产系列| 久久精品久久99| 国产精品成人**免费视频| 欧美精品自拍偷拍动漫精品| 在线观看免费成人av| 三上悠亚国产精品一区二区三区| 欧美性感美女h网站在线观看免费| 搞av.com| 岛国av在线网站| 精品国产91久久久久久老师| 青青艹视频在线| 惠美惠精品网| 欧美网站大全在线观看| 中文字幕久久av| 美女精品视频在线| 欧美不卡在线视频| 成年人的黄色片| 亚洲素人在线| 在线观看亚洲视频| 在线观看黄网址| 黄色av成人| 7777免费精品视频| 男操女视频网站| 久久国产免费看| http;//www.99re视频| 人妻一区二区三区免费| 久久久影院官网| 色涩成人影视在线播放| 日本www在线观看视频| 一区二区三区四区在线| 国产伦精品一区二区三区四区视频_| 亚洲午夜天堂| 欧美绝品在线观看成人午夜影视| 深夜视频在线观看| 国产成人手机高清在线观看网站| 色播久久人人爽人人爽人人片视av| 午夜剧场免费在线观看| 日韩午夜在线电影| 91精品国产综合久久香蕉| 欧美熟妇另类久久久久久不卡 | 国产亚洲精品自在久久| 免费在线观看污视频| 国产精品久久久久一区| 91精品国产91久久久久麻豆 主演| 涩涩视频在线| 欧美久久久影院| www.超碰97| 先锋资源久久| 欧美专区在线播放| 国产精品区在线观看| 99久久伊人精品| 亚洲国产精品一区二区第四页av| 麻豆av在线免费观看| 色播五月激情综合网| 男男受被啪到高潮自述| 亚洲小说图片视频| 欧美大片在线免费观看| 一级黄色在线观看| 成人国产精品免费| 久久免费看毛片| 成人亚洲欧美| 精品伦理精品一区| 69夜色精品国产69乱| 校园激情久久| 国产专区一区二区| 欧美18一19xxx性| 日韩欧美亚洲国产一区| 年下总裁被打光屁股sp| 999久久久精品国产| 欧美性视频精品| 亚洲经典一区二区| 亚洲日本在线a| 亚洲xxxx2d动漫1| 亚洲电影男人天堂| 午夜精品久久久久久久男人的天堂 | 影音先锋中文在线视频| 欧美视频日韩视频| 精品夜夜澡人妻无码av| 欧美久久99| 91中文字幕一区| 免费网站免费进入在线| 欧美亚洲一区三区| caopeng视频| 亚洲一区日韩在线| 精品国产一区二区三区日日嗨| 中文字幕中文字幕在线十八区 | 日韩专区一区二区| 天天影视色香欲综合网老头| 91porn在线| 黄色亚洲在线| 国产免费一区| 51精品在线| 亚洲精品福利免费在线观看| 国产精品第一页在线观看| 高清日韩电视剧大全免费| 成年人深夜视频| 91蜜桃臀久久一区二区| 欧美精品激情在线| 欧美熟妇另类久久久久久不卡| 亚洲国产欧美一区二区三区丁香婷| 国产精品一级无码| 亚洲第一网站| 欧美黄色直播| 日韩欧美一区二区三区在线观看| 在线看日韩欧美| 亚洲视频久久久| 一区二区中文视频| 精品人妻一区二区三区免费| 精久久久久久| 久久精精品视频| 成人性生交大片免费观看网站| 亚洲一区二区福利| 国产精品视频久久久久久| 亚洲精品国产精华液| 久久av一区二区三| 99热在线精品观看| 色姑娘综合网| 国产精一区二区| 久久久综合av| 男操女在线观看| 91 com成人网| 91蜜桃视频在线观看| 久久久久久久久97黄色工厂| 我要看一级黄色大片| 中文不卡在线| 久久99欧美| 欧美日韩视频免费看| 欧美大片免费看| 国产永久免费高清在线观看| 欧美日本乱大交xxxxx| 欧美精品一级片| 91日韩一区二区三区| 性欧美极品xxxx欧美一区二区| 亚洲综合专区| 蜜桃精品久久久久久久免费影院| 成人网ww555视频免费看| 九九九久久久久久| 免费a在线观看| 欧美一区二区三区系列电影| 久久青青草视频| 综合网在线视频| 色天使在线视频| 九九九久久久精品| 成人在线免费在线观看| 亚洲精品在线观看91| 久久久影院一区二区三区 | 欧美剧在线免费观看网站| 国产午夜精品无码一区二区| 国产精品丝袜一区| 少妇被狂c下部羞羞漫画| 日本va欧美va精品| 国产视频九色蝌蚪| 在线电影一区二区| 日韩精品欧美专区| 清纯唯美亚洲经典中文字幕| 亚洲va久久久噜噜噜| 国产超碰精品| 91精品国产高清久久久久久| 91亚洲天堂| 尤物九九久久国产精品的分类| 蜜桃91麻豆精品一二三区| 欧美亚洲自拍偷拍| 国产精品免费精品一区| 亚洲午夜三级在线| 污污的视频在线免费观看| 久久久久久久性| 亚洲图片综合网| 国产精品系列在线播放| 不用播放器的免费av| 天堂影院一区二区| a级黄色一级片| 精品白丝av| 免费看日本黄色| 香蕉av一区二区| 亚洲激情一区二区| 精品视频久久| 日本一区二区三区四区在线观看 | 精品国产乱码久久久久久蜜臀网站| 色综合久久久网| 精品成人久久久| 一区二区三区91| 九九热国产在线| 亚洲精品视频在线| 精品国产视频在线观看| 国产精品不卡在线| 中文字幕第69页| 中文字幕成人av| 久久久精品成人| 中文字幕第一页久久| 中文字幕第24页| 欧美国产一区二区| 国产精品av久久久久久无| 久久久亚洲午夜电影| av电影在线不卡| 亚洲国产精品成人综合色在线婷婷 | 色综合久久天天综线观看| 亚洲综合影视| 欧美极品欧美精品欧美视频 | 中文字幕乱视频| 成年人国产精品| 国产精品久久无码| 99在线热播精品免费| 熟妇高潮精品一区二区三区| 久久美女高清视频| 日本猛少妇色xxxxx免费网站| 国产精品久久久久久久午夜片 | 亚洲欧美综合久久久| 欧美大片免费播放| 亚洲精品日本| 中文字幕乱码人妻综合二区三区| 每日更新成人在线视频| 日韩欧美在线免费观看视频| 久久精品国产亚洲一区二区三区| 三日本三级少妇三级99| 懂色av一区二区三区蜜臀| 国产网站无遮挡| 日本一区二区在线不卡| 黄色香蕉视频在线观看| 亚洲一区在线免费观看| 国产精品久久久久久久久久久久久久久久久 | 国产精品一区一区三区| 久久久国产精品无码| 久久精品人人做人人综合| 国产人与禽zoz0性伦| 亚洲国产综合91精品麻豆| 成人午夜淫片100集| 欧美美女激情18p| 深夜福利视频网站| 中文字幕av一区| 天堂va在线| 国产精品va在线播放| 久久久久久久久成人| 久久精品国产美女| 99精品全国免费观看视频软件| 欧美一级片免费播放| 美女一区二区视频| 亚洲无人区码一码二码三码| 国产日韩欧美麻豆| 久久久久无码国产精品不卡| 日本高清不卡aⅴ免费网站| av中文字幕第一页| 亚洲老板91色精品久久| 97caopron在线视频| 国产精品wwww| 欧美高清视频看片在线观看| 在线成人性视频| 在线一区视频| 在线观看视频你懂得| 国产欧美日韩不卡| 亚欧视频在线观看| 欧美一区二区三区系列电影| 国产综合视频一区二区三区免费| 欧美成人免费视频| 丁香婷婷久久| 蜜桃av噜噜一区二区三| 欧美日韩第一区| 亚洲黄色片免费| 国产精品素人视频| 天天干天天干天天| 亚洲精品福利在线| 免费电影网站在线视频观看福利| 成人www视频在线观看| 精品国产91| 黄色片视频在线免费观看| 国产成人一区在线| 久久久久久视频| 精品视频999| 成人性生交大片免费看午夜| 91精品成人久久| 盗摄牛牛av影视一区二区| 艳母动漫在线观看| 久久国产精品免费| 国产欧美一区二区三区在线观看视频| 欧美日韩免费网站| 四季av日韩精品一区| 欧美精品电影在线| 成人在线视频你懂的| 中文字幕の友人北条麻妃| 久久99精品国产麻豆婷婷| www.日本高清视频| 欧美曰成人黄网| yw193.com尤物在线| 日韩av三级在线观看| 一本色道久久综合亚洲精品酒店| 18禁网站免费无遮挡无码中文| 国产成人av电影| 麻豆91精品91久久久| 日韩一级片在线观看| av网站免费在线观看| 国产日产久久高清欧美一区| 成人免费av| 亚洲国产日韩欧美在线观看| 中文字幕久久午夜不卡| 中文字幕一区二区三区人妻四季| 伊人久久精品视频| 欧美视频在线视频精品| 在线成人av电影| 国产米奇在线777精品观看| 岛国毛片在线观看| 日韩你懂的在线观看| 欧美人体视频xxxxx| 精品国产一区二区三区免费| 欧美中文字幕| 性の欲びの女javhd| 欧美日韩国产片| 最近中文字幕免费mv2018在线| 国产高清在线一区二区| 一本色道久久综合| 色一情一交一乱一区二区三区 | 国产精品自拍区| 污版视频在线观看| 亚洲欧美另类小说| 人人妻人人澡人人爽久久av| 6080yy精品一区二区三区| 精品视频网站| caoporm在线视频| 亚洲高清在线精品| 色就是色亚洲色图| 国产精品专区第二| 国产精品v亚洲精品v日韩精品 | 怡红院亚洲色图| 亚洲精品中文在线观看| 天天干天天做天天操| 国产精品福利观看| 亚洲欧洲美洲一区二区三区| 人妻无码中文久久久久专区| 欧美三级在线播放| 大桥未久在线播放| 婷婷久久伊人| 福利一区二区在线观看| av毛片在线免费观看| 久久精品视频在线| 羞羞色国产精品网站| 日韩欧美亚洲另类| 福利一区福利二区微拍刺激| 欧美日韩在线资源| 精品日本一区二区三区在线观看| 日本va欧美va欧美va精品| 精品小视频在线观看| 一区二区三区视频免费| 清纯唯美激情亚洲| 好男人www社区| 亚洲蜜臀av乱码久久精品 | 大量国产精品视频| 亚洲欧洲色图| 在线观看你懂的视频| 欧美在线视频你懂得| 成人国产电影在线观看|