自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從 23.9K 的前端開(kāi)源項(xiàng)目我學(xué)到了啥?

開(kāi)發(fā) 開(kāi)發(fā)工具
在 7年技術(shù)寫(xiě)作,分享6點(diǎn)心得體會(huì) 這篇文章中,阿寶哥介紹了自己經(jīng)常使用的一款不錯(cuò)的在線繪圖工具 — Excalidraw。

[[430695]]

在 7年技術(shù)寫(xiě)作,分享6點(diǎn)心得體會(huì) 這篇文章中,阿寶哥介紹了自己經(jīng)常使用的一款不錯(cuò)的在線繪圖工具 — Excalidraw。使用它你可以輕松地繪制各種漂亮的手繪示意圖,目前在 Github 上 Excalidraw 的 Star 數(shù)已達(dá) 23.9 K,因此它也是一個(gè)很不錯(cuò)的開(kāi)源項(xiàng)目。

在平時(shí)使用 Excalidraw 的時(shí)候,阿寶哥發(fā)現(xiàn)了該在線工具提供了一些不錯(cuò)的功能。比如保存 *.excalidraw 文件到指定目錄、拖拽打開(kāi) *.excalidraw 文件并保存至當(dāng)前文件、復(fù)制圖片到剪貼板、分享只讀鏈接和實(shí)時(shí)協(xié)作等功能。

提示:上圖演示了拖拽打開(kāi) *.excalidraw 文件并保存至當(dāng)前文件的功能

上述的這些功能,很多都是跟文件操作相關(guān)。關(guān)于文件處理,阿寶哥之前寫(xiě)了 文件上傳,搞懂這8種場(chǎng)景就夠了 和 文件下載,搞懂這9種場(chǎng)景就夠了 這兩篇文章。而第三篇文章,阿寶哥就帶大家來(lái)分析一下 Excalidraw 背后與文件操作相關(guān)的技術(shù)。

了解并掌握了這些相關(guān)技術(shù)之后,在今后的工作中也許就會(huì)有用武之地,特別是對(duì)于一些在線 Web 編輯器的場(chǎng)景,利用這些技術(shù)將會(huì)大大提高產(chǎn)品的用戶體驗(yàn)。比如在支持相關(guān) Web 技術(shù)的平臺(tái)上,你們開(kāi)發(fā)的在線編輯器就能完美支持 打開(kāi)->編輯->保存 這個(gè)常見(jiàn)的文件處理流程。

話不多說(shuō),我們馬上步入正題,這里我們先來(lái)分析 保存 .excalidraw 文件到指定目錄 的功能。

一、保存文件到指定目錄

圖片

提示:本文所有演示示例使用的 Chrome 版本為:版本 92.0.4515.159(正式版本) (x86_64)

以上 Gif 動(dòng)圖演示了保存文件到指定目錄的過(guò)程,因?yàn)?Excalidraw 這個(gè)在線工具是開(kāi)源的,所以通過(guò)分析它的源碼,我們找到了實(shí)現(xiàn) 保存文件到指定目錄 功能的實(shí)現(xiàn)函數(shù):

  1. // https://github.com/excalidraw/excalidraw/blob/master/src/data/json.ts#L31 
  2. import { fileOpen, fileSave } from "browser-fs-access"
  3.  
  4. export const saveAsJSON = async ( 
  5.   elements: readonly ExcalidrawElement[], 
  6.   appState: AppState, 
  7. ) => { 
  8.   const serialized = serializeAsJSON(elements, appState); 
  9.   const blob = new Blob([serialized], { 
  10.     type: MIME_TYPES.excalidraw, 
  11.   }); 
  12.   
  13.   const fileHandle = await fileSave( 
  14.     blob, 
  15.     { 
  16.       fileName: `${appState.name}.excalidraw`, 
  17.       description: "Excalidraw file"
  18.       extensions: [".excalidraw"], 
  19.     }, 
  20.     isImageFileHandle(appState.fileHandle) ? null : appState.fileHandle, 
  21.   ); 
  22.   return { fileHandle }; 
  23. }; 

由以上代碼可知,在 saveAsJSON 函數(shù)內(nèi)部是通過(guò)調(diào)用 fileSave 函數(shù)來(lái)保存文件。fileSave 函數(shù)是從 browser-fs-access 這個(gè)第三庫(kù)導(dǎo)入的。該庫(kù)封裝了 File_System_Access_API,該 API 為開(kāi)發(fā)者提供了 讀、寫(xiě)文件和文件管理 的能力。而 保存文件到指定目錄 的功能,就是通過(guò) showSaveFilePicker 方法來(lái)實(shí)現(xiàn)的。在 showSaveFilePicker 方法出現(xiàn)之前,在客戶端實(shí)現(xiàn)保存文件的功能,比較常見(jiàn)的方案是使用 a 標(biāo)簽 或 FileSaver.js 這個(gè)庫(kù)。

  1. const saveFile = async (blob, filename) => { 
  2.   const a = document.createElement('a'); 
  3.   a.download = filename; 
  4.   a.href = URL.createObjectURL(blob); 
  5.   a.addEventListener('click', (e) => { 
  6.     setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000); 
  7.   }); 
  8.   a.click(); 
  9. }; 

提示:如果你想了解其他的文件下載方式,可以閱讀 文件下載,搞懂這9種場(chǎng)景就夠了 這篇文章。

對(duì)于前面介紹的客戶端文件保存的方案來(lái)說(shuō),它最大的問(wèn)題就是沒(méi)有辦法實(shí)現(xiàn) 打開(kāi)->編輯->保存 這種常見(jiàn)的文件操作流程。因?yàn)槲覀儧](méi)有辦法覆蓋原始的文件,只能創(chuàng)建一個(gè)新的文件。而使用新的 File_System_Access_API 就可以解決上述的問(wèn)題,比如我們可以使用 window.showOpenFilePicker 方法來(lái)打開(kāi)文件,在文件編輯完成之后,再使用 window.showSaveFilePicker 來(lái)保存文件。

圖片

(文本編輯器地址:https://googlechromelabs.github.io/text-editor/)

下面我們來(lái)介紹一下 showSaveFilePicker API,它是 Window 接口中定義的方法,調(diào)用該方法后會(huì)顯示允許用戶選擇保存路徑的文件選擇器。該方法的簽名如下所示:

  1. let FileSystemFileHandle = Window.showSaveFilePicker(options); 

showSaveFilePicker 方法支持一個(gè)對(duì)象類型的可選參數(shù),可包含以下屬性:

excludeAcceptAllOption:布爾類型,默認(rèn)值為 false。默認(rèn)情況下,選擇器應(yīng)包含一個(gè)不應(yīng)用任何文件類型過(guò)濾器的選項(xiàng)(由下面的 types 選項(xiàng)啟用)。將此選項(xiàng)設(shè)置為 true 意味著 types 選項(xiàng)不可用。

types:數(shù)組類型,表示允許保存的文件類型列表。數(shù)組中的每一項(xiàng)是包含以下屬性的配置對(duì)象:

  • description(可選):用于描述允許保存文件類型類別。
  • accept:是一個(gè)對(duì)象,該對(duì)象的 key 是 MIME 類型,值是文件擴(kuò)展名列表。

調(diào)用 showSaveFilePicker 方法之后,會(huì)返回一個(gè) FileSystemFileHandle 對(duì)象。有了該對(duì)象,你就可以調(diào)用該對(duì)象上的方法來(lái)操作文件。比如調(diào)用該對(duì)象上的 createWritable 方法之后,就會(huì)返回 FileSystemWritableFileStream 對(duì)象,就可以把數(shù)據(jù)寫(xiě)入到文件中。具體的使用方式如下所示:

  1. async function saveFile(blob, filename) { 
  2.   try { 
  3.     const handle = await window.showSaveFilePicker({ 
  4.       suggestedName: filename, 
  5.       types: [ 
  6.         { 
  7.           description: "PNG file"
  8.           accept: { 
  9.             "image/png": [".png"], 
  10.           }, 
  11.         }, 
  12.       ], 
  13.      }); 
  14.     const writable = await handle.createWritable(); 
  15.     await writable.write(blob); 
  16.     await writable.close(); 
  17.     return handle; 
  18.   } catch (err) { 
  19.      console.error(err.name, err.message); 
  20.   } 
  21.  
  22. saveFile(imgBlob, "face.png"); 

當(dāng)你使用以上的 saveFile 函數(shù),來(lái)保存圖片時(shí),就會(huì)顯示以下保存文件選擇器:

看到這里是不是覺(jué)得 showSaveFilePicker API 功能挺強(qiáng)大的,不過(guò)可惜的是該 API 目前的兼容性還不是很好,具體如下圖所示:

(圖片來(lái)源:https://caniuse.com/?search=showSaveFilePicker)

showSaveFilePicker 是 File System Access API 中定義的方法,除了 showSaveFilePicker 之外,還有 showOpenFilePicker 和 showDirectoryPicker 等方法。接下來(lái),阿寶哥來(lái)簡(jiǎn)單介紹一下另外這兩個(gè)比較有用的 API。

showOpenFilePicker API,它是 Window 接口中定義的方法,調(diào)用該方法后會(huì)顯示一個(gè)允許用戶選擇一個(gè)或多個(gè)文件的文件選擇器。該方法的簽名如下所示:

  1. let FileSystemHandles = Window.showOpenFilePicker(); 

showOpenFilePicker 方法支持一個(gè)對(duì)象類型的可選參數(shù),可包含以下屬性:

multiple:布爾類型,默認(rèn)值為 false。若設(shè)置為 true,則允許選擇多個(gè)文件。

excludeAcceptAllOption:布爾類型,默認(rèn)值為 false。默認(rèn)情況下,選擇器應(yīng)包含一個(gè)不應(yīng)用任何文件類型過(guò)濾器的選項(xiàng)(由下面的 types 選項(xiàng)啟用)。將此選項(xiàng)設(shè)置為 true 意味著 types 選項(xiàng)不可用。

  • types:數(shù)組類型,表示允許保存的文件類型列表。數(shù)組中的每一項(xiàng)是包含以下屬性的配置對(duì)象:
  • description(可選):用于描述允許保存文件類型類別。

accept:是一個(gè)對(duì)象,該對(duì)象的 key 是 MIME 類型,值是文件擴(kuò)展名列表。

調(diào)用 showOpenFilePicker 方法之后,會(huì)返回 FileSystemHandles 即 FileSystemFileHandle 對(duì)象數(shù)組。有了 FileSystemFileHandle 對(duì)象,你就可以調(diào)用該對(duì)象上的方法來(lái)操作文件。下面我們來(lái)舉一個(gè)簡(jiǎn)單的使用示例:

  1. <div> 
  2.    <textarea id="container" rows="5" cols="30"></textarea> 
  3. </div> 
  4. <button onclick="openFile()">打開(kāi)文件</button> 
  5. <script> 
  6.    const container = document.querySelector("#container"); 
  7.    
  8.    async function openFile() { 
  9.      let [fileHandle] = await window.showOpenFilePicker(); 
  10.      const file = await fileHandle.getFile(); 
  11.      const contents = await file.text(); 
  12.      container.value = contents; 
  13.    } 
  14. </script> 

在以上示例中,當(dāng)用戶點(diǎn)擊 打開(kāi)文件 按鈕時(shí),就會(huì)顯示一個(gè)文件選擇器。在選擇文本文件之后,就會(huì)把文件中的內(nèi)容,顯示在 textarea#container 文本框中。對(duì)于非文本文件,你可以通過(guò)調(diào)用 arrayBuffer 方法來(lái)讀取文件中的二進(jìn)制內(nèi)容。

(圖片來(lái)源:https://caniuse.com/?search=showOpenFilePicker)

由上圖可知,目前 showOpenFilePicker API 的兼容性還比較差。但如果你想在支持 File System Access API 的平臺(tái)中,優(yōu)先使用這些 API 的話,可以考慮使用 GoogleChromeLabs 開(kāi)源的 browser-fs-access 這個(gè)庫(kù),該庫(kù)可以讓你在支持 File System Access API 的平臺(tái)上更方便地使用 File System Access API,而對(duì)于不支持的平臺(tái)會(huì)自動(dòng)降級(jí)使用 <input type="file"> 和 <a download> 的方式。

除了選擇文件之外,我們也可以選擇目錄。針對(duì)這種場(chǎng)景,我們就可以使用 showDirectoryPicker API。它是 Window 接口中定義的方法,調(diào)用該方法后會(huì)顯示一個(gè)允許用戶選擇目錄的選擇器。該方法的簽名如下所示:

  1. var FileSystemDirectoryHandle = Window.showDirectoryPicker(); 

與前面介紹的 showOpenFilePicker 方法不同的是,調(diào)用 showDirectoryPicker 方法后是,返回的是 FileSystemDirectoryHandle 對(duì)象。利用該對(duì)象,我們就可以執(zhí)行一些目錄的相關(guān)操作操作。比如讀取目錄的信息、讀取目錄下的指定文件、刪除目錄下的指定文件或在目錄下新建文件等。同樣,我們也來(lái)舉一些簡(jiǎn)單的示例。

讀取目錄的信息

  1. async function readDirectory() { 
  2.   const dirHandle = await window.showDirectoryPicker(); 
  3.   for await (const entry of dirHandle.values()) { 
  4.     console.log(entry.kind, entry.name); 
  5.   } 

讀取目錄下的指定文件

  1. const container = document.querySelector("#container"); 
  2.  
  3. async function readFile() { 
  4.   const dirHandle = await window.showDirectoryPicker(); 
  5.   const fileHandle = await dirHandle.getFileHandle("hello.txt"); 
  6.   const file = await fileHandle.getFile(); 
  7.   const contents = await file.text(); 
  8.   container.value = contents; 

刪除目錄下的指定文件

  1. async function removeFile() { 
  2.   const dirHandle = await window.showDirectoryPicker(); 
  3.   const result = await dirHandle.removeEntry("hello.copy.txt"); 
  4.   container.value = `刪除hello.copy.txt文件${ 
  5.     typeof result == "undefined" ? "成功" : "失敗" 
  6.   }`; 

需要注意的是,removeEntry 方法除了支持刪除指定文件之外,還可以支持刪除指定目錄。

創(chuàng)建指定文件

  1. async function createFile() { 
  2.   const dirHandle = await window.showDirectoryPicker(); 
  3.   const fileHandle = await dirHandle.getFileHandle("hello.new.txt", { 
  4.     createtrue
  5.   }); 
  6.   container.value = "hello.new.txt文件創(chuàng)建成功!"
  7.   const writable = await fileHandle.createWritable(); 
  8.   await writable.write(new Blob(["大家好,我是阿寶哥!"])); 
  9.   await writable.close(); 

在以上代碼中,我們通過(guò)調(diào)用 getFileHandle 方法來(lái)獲取指定文件,對(duì)應(yīng)的 FileSystemFileHandle 對(duì)象。create: true 表示如果在當(dāng)前目錄下未找到指定文件,則創(chuàng)建新的文件。了解完以上的示例,是不是覺(jué)得瀏覽器的文件處理能力越來(lái)越強(qiáng)大了。同樣,我們也來(lái)看一下 showDirectoryPicker API 的兼容性:

(圖片來(lái)源:https://caniuse.com/?search=showDirectoryPicker)

二、拖拽打開(kāi) *.excalidraw 文件并保存至當(dāng)前文件

以上 Gif 動(dòng)圖演示了拖拽打開(kāi) *.excalidraw 文件并保存至當(dāng)前文件的過(guò)程,可以發(fā)現(xiàn)在編輯完文件之后,我們只需確認(rèn)是否保存文件,而無(wú)需選擇文件的保存路徑,在大大提高了用戶的使用體驗(yàn)。

  1. class App extends React.Component<AppProps, AppState> { 
  2.    // 省略大部分代碼 
  3.     const file = event.dataTransfer?.files[0]; 
  4.     if ( 
  5.       file?.type === MIME_TYPES.excalidrawlib || 
  6.       file?.name?.endsWith(".excalidrawlib"
  7.     ) { 
  8.       // 處理導(dǎo)入的控件庫(kù)的邏輯 
  9.     } else { 
  10.       this.setState({ isLoading: true }); 
  11.       if (fsSupported) { // 判斷是否支持File System Access API 
  12.         try { 
  13.           const item = event.dataTransfer.items[0]; 
  14.           // 關(guān)鍵點(diǎn):獲取FileSystemHandle對(duì)象 
  15.           (file as any).handle = await (item as any).getAsFileSystemHandle(); 
  16.         } catch (error) { 
  17.           console.warn(error.name, error.message); 
  18.         } 
  19.       } 
  20.       // 加載.excalidraw文件到Canvas 
  21.       await this.loadFileToCanvas(file); 
  22.     } 
  23.   }; 

以上代碼的關(guān)鍵點(diǎn)是,調(diào)用 DataTransferItem.getAsFileSystemHandle() 方法來(lái)獲取 FileSystemFileHandle 對(duì)象。擁有該對(duì)象之后,我們就可以對(duì)文件進(jìn)行讀、寫(xiě)操作。具體的使用方式如下所示:

讀文件示例

  1. async function getTheFile() { 
  2.   // open file picker 
  3.   [fileHandle] = await window.showOpenFilePicker(pickerOpts); 
  4.  
  5.   // get file contents 
  6.   const fileData = await fileHandle.getFile(); 

寫(xiě)文件示例

  1. async function writeFile(fileHandle, contents) { 
  2.   // Create a FileSystemWritableFileStream to write to
  3.   const writable = await fileHandle.createWritable(); 
  4.  
  5.   // Write the contents of the file to the stream. 
  6.   await writable.write(contents); 
  7.  
  8.   // Close the file and write the contents to disk. 
  9.   await writable.close(); 

三、復(fù)制圖片到剪貼板

  1. // https://github.com/excalidraw/excalidraw/blob/master/src/clipboard.ts 
  2. export const copyBlobToClipboardAsPng = async (blob: Blob) => { 
  3.   await navigator.clipboard.write([ 
  4.     new window.ClipboardItem({ "image/png": blob }), 
  5.   ]); 
  6. }; 

在以上代碼中,copyBlobToClipboardAsPng 函數(shù)支持一個(gè) blob 參數(shù),在該函數(shù)內(nèi)部會(huì)調(diào)用 navigator.clipboard.write 方法,來(lái)實(shí)現(xiàn)把圖片復(fù)制到剪貼板。而對(duì)于普通文本來(lái)說(shuō),你可以通過(guò) navigator.clipboard.writeText 方法,把它們寫(xiě)入到系統(tǒng)的剪貼板。

其實(shí) navigator.clipboard.write 和 navigator.clipboard.writeText 方法是 Clipboard 接口定義的方法,該接口實(shí)現(xiàn)了 Clipboard API,如果用戶授予了相應(yīng)的權(quán)限,就能提供系統(tǒng)剪貼板的讀寫(xiě)訪問(wèn)。在 Web 應(yīng)用程序中,Clipboard API 可用于實(shí)現(xiàn)剪切、復(fù)制和粘貼功能。該 API 用于取代通過(guò) document.execCommand API 來(lái)實(shí)現(xiàn)剪貼板的操作。

在實(shí)際工作中,我們不需要手動(dòng)創(chuàng)建 Clipboard 對(duì)象,而是通過(guò) navigator.clipboard 來(lái)獲取 Clipboard 對(duì)象:

在獲取 Clipboard 對(duì)象之后,我們就可以利用該對(duì)象提供的 API 來(lái)訪問(wèn)剪貼板。比如,通過(guò) navigator.clipboard.readText 方法來(lái)讀取剪貼板的內(nèi)容:

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

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

異步剪貼板 API 是一個(gè)相對(duì)較新的 API,瀏覽器仍在逐漸實(shí)現(xiàn)它。由于潛在的安全問(wèn)題和技術(shù)復(fù)雜性,大多數(shù)瀏覽器正在逐步集成這個(gè) API。目前 Navigator API: clipboard 的兼容性如下圖所示:

(圖片來(lái)源:https://caniuse.com/mdn-api_navigator_clipboard)

對(duì)于瀏覽器擴(kuò)展來(lái)說(shuō),你可以請(qǐng)求 clipboardRead 和 clipboardWrite 權(quán)限以使用 clipboard.readText() 和 clipboard.writeText()。如果你對(duì) Clipboard 其他 API 感興趣的話,可以閱讀 想要復(fù)制圖像?Clipboard API 了解一下 這篇文章。

其實(shí)除了上面介紹的技術(shù), Excalidraw 還使用了其他 Web API 來(lái)實(shí)現(xiàn)特定的功能。比如利用 window.crypto API 來(lái)實(shí)現(xiàn)導(dǎo)出只讀鏈接時(shí),對(duì)畫(huà)布數(shù)據(jù)進(jìn)行加密保護(hù)。利用 WebSocket API 來(lái)實(shí)現(xiàn)協(xié)同編輯和利用 Share API 實(shí)現(xiàn)文件共享的功能,感興趣的小伙伴可以閱讀一下 Excalidraw 的相關(guān)源碼。

四、總結(jié)

本文阿寶哥分析了 Excalidraw 這款在線繪圖工具,所提供的一些不錯(cuò)功能背后使用的技術(shù)。希望閱讀完本文后,你對(duì) File_System_Access_API 中定義的 window.showOpenFilePicker、window.showSaveFilePicker、window.showDirectoryPicker 和 DataTransferItem.getAsFileSystemHandle 這些方法都有一定的了解。

由于目前 File_System_Access_API 的兼容性還不是很好,如果你想在項(xiàng)目中使用它的話。建議你使用 GoogleChromeLabs 開(kāi)源的 browser-fs-access 這個(gè)庫(kù),該庫(kù)不僅為我們提供了更簡(jiǎn)潔的 API,而且還提供了自動(dòng)降級(jí)的方案。在今后的項(xiàng)目中,有機(jī)會(huì)的話,小伙伴們可以嘗試一下。

五、參考資源

  • web.dev — excalidraw-and-fugu
  • web.dev — browser-fs-access
  • web.dev — file-system-access
  • MDN — File_System_Access_API
  • MDN — FileSystemFileHandle

 

責(zé)任編輯:姜華 來(lái)源: 全棧修仙之路
相關(guān)推薦

2020-09-25 06:32:25

前端

2020-02-22 15:01:51

后端前端開(kāi)發(fā)

2021-03-09 09:55:02

Vuejs前端代碼

2020-07-07 08:52:16

機(jī)器學(xué)習(xí)機(jī)器學(xué)習(xí)工具人工智能

2022-03-27 09:06:04

React類型定義前端

2020-12-31 10:47:03

開(kāi)發(fā)Vuejs技術(shù)

2016-01-18 10:06:05

編程

2021-04-15 08:15:27

Vue.js源碼方法

2020-02-22 14:49:30

畢業(yè)入職半年感受

2020-11-04 07:13:57

數(shù)據(jù)工程代碼編程

2024-04-12 08:54:13

從庫(kù)數(shù)據(jù)庫(kù)應(yīng)用

2021-01-02 09:48:13

函數(shù)運(yùn)算js

2021-07-28 07:01:09

薅羊毛架構(gòu)Vue+SSR

2020-10-30 12:40:04

Reac性能優(yōu)化

2019-08-27 10:49:30

跳槽那些事兒技術(shù)Linux

2013-06-27 10:31:39

2019-08-16 17:14:28

跳槽那些事兒技術(shù)Linux

2015-06-29 13:47:19

創(chuàng)業(yè)創(chuàng)業(yè)智慧

2011-10-18 11:43:25

UNIXC語(yǔ)言丹尼斯·里奇

2023-06-06 08:14:18

核心Docker應(yīng)用程序
點(diǎn)贊
收藏

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