成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

從 23.9K 的前端開源項目我學到了啥?

開發 開發工具
在 7年技術寫作,分享6點心得體會 這篇文章中,阿寶哥介紹了自己經常使用的一款不錯的在線繪圖工具 — Excalidraw。

[[430695]]

在 7年技術寫作,分享6點心得體會 這篇文章中,阿寶哥介紹了自己經常使用的一款不錯的在線繪圖工具 — Excalidraw。使用它你可以輕松地繪制各種漂亮的手繪示意圖,目前在 Github 上 Excalidraw 的 Star 數已達 23.9 K,因此它也是一個很不錯的開源項目。

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

提示:上圖演示了拖拽打開 *.excalidraw 文件并保存至當前文件的功能

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

了解并掌握了這些相關技術之后,在今后的工作中也許就會有用武之地,特別是對于一些在線 Web 編輯器的場景,利用這些技術將會大大提高產品的用戶體驗。比如在支持相關 Web 技術的平臺上,你們開發的在線編輯器就能完美支持 打開->編輯->保存 這個常見的文件處理流程。

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

一、保存文件到指定目錄

圖片

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

以上 Gif 動圖演示了保存文件到指定目錄的過程,因為 Excalidraw 這個在線工具是開源的,所以通過分析它的源碼,我們找到了實現 保存文件到指定目錄 功能的實現函數:

  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 函數內部是通過調用 fileSave 函數來保存文件。fileSave 函數是從 browser-fs-access 這個第三庫導入的。該庫封裝了 File_System_Access_API,該 API 為開發者提供了 讀、寫文件和文件管理 的能力。而 保存文件到指定目錄 的功能,就是通過 showSaveFilePicker 方法來實現的。在 showSaveFilePicker 方法出現之前,在客戶端實現保存文件的功能,比較常見的方案是使用 a 標簽 或 FileSaver.js 這個庫。

  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種場景就夠了 這篇文章。

對于前面介紹的客戶端文件保存的方案來說,它最大的問題就是沒有辦法實現 打開->編輯->保存 這種常見的文件操作流程。因為我們沒有辦法覆蓋原始的文件,只能創建一個新的文件。而使用新的 File_System_Access_API 就可以解決上述的問題,比如我們可以使用 window.showOpenFilePicker 方法來打開文件,在文件編輯完成之后,再使用 window.showSaveFilePicker 來保存文件。

圖片

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

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

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

showSaveFilePicker 方法支持一個對象類型的可選參數,可包含以下屬性:

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

types:數組類型,表示允許保存的文件類型列表。數組中的每一項是包含以下屬性的配置對象:

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

調用 showSaveFilePicker 方法之后,會返回一個 FileSystemFileHandle 對象。有了該對象,你就可以調用該對象上的方法來操作文件。比如調用該對象上的 createWritable 方法之后,就會返回 FileSystemWritableFileStream 對象,就可以把數據寫入到文件中。具體的使用方式如下所示:

  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"); 

當你使用以上的 saveFile 函數,來保存圖片時,就會顯示以下保存文件選擇器:

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

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

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

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

  1. let FileSystemHandles = Window.showOpenFilePicker(); 

showOpenFilePicker 方法支持一個對象類型的可選參數,可包含以下屬性:

multiple:布爾類型,默認值為 false。若設置為 true,則允許選擇多個文件。

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

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

accept:是一個對象,該對象的 key 是 MIME 類型,值是文件擴展名列表。

調用 showOpenFilePicker 方法之后,會返回 FileSystemHandles 即 FileSystemFileHandle 對象數組。有了 FileSystemFileHandle 對象,你就可以調用該對象上的方法來操作文件。下面我們來舉一個簡單的使用示例:

  1. <div> 
  2.    <textarea id="container" rows="5" cols="30"></textarea> 
  3. </div> 
  4. <button onclick="openFile()">打開文件</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> 

在以上示例中,當用戶點擊 打開文件 按鈕時,就會顯示一個文件選擇器。在選擇文本文件之后,就會把文件中的內容,顯示在 textarea#container 文本框中。對于非文本文件,你可以通過調用 arrayBuffer 方法來讀取文件中的二進制內容。

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

由上圖可知,目前 showOpenFilePicker API 的兼容性還比較差。但如果你想在支持 File System Access API 的平臺中,優先使用這些 API 的話,可以考慮使用 GoogleChromeLabs 開源的 browser-fs-access 這個庫,該庫可以讓你在支持 File System Access API 的平臺上更方便地使用 File System Access API,而對于不支持的平臺會自動降級使用 <input type="file"> 和 <a download> 的方式。

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

  1. var FileSystemDirectoryHandle = Window.showDirectoryPicker(); 

與前面介紹的 showOpenFilePicker 方法不同的是,調用 showDirectoryPicker 方法后是,返回的是 FileSystemDirectoryHandle 對象。利用該對象,我們就可以執行一些目錄的相關操作操作。比如讀取目錄的信息、讀取目錄下的指定文件、刪除目錄下的指定文件或在目錄下新建文件等。同樣,我們也來舉一些簡單的示例。

讀取目錄的信息

  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 方法除了支持刪除指定文件之外,還可以支持刪除指定目錄。

創建指定文件

  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文件創建成功!"
  7.   const writable = await fileHandle.createWritable(); 
  8.   await writable.write(new Blob(["大家好,我是阿寶哥!"])); 
  9.   await writable.close(); 

在以上代碼中,我們通過調用 getFileHandle 方法來獲取指定文件,對應的 FileSystemFileHandle 對象。create: true 表示如果在當前目錄下未找到指定文件,則創建新的文件。了解完以上的示例,是不是覺得瀏覽器的文件處理能力越來越強大了。同樣,我們也來看一下 showDirectoryPicker API 的兼容性:

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

二、拖拽打開 *.excalidraw 文件并保存至當前文件

以上 Gif 動圖演示了拖拽打開 *.excalidraw 文件并保存至當前文件的過程,可以發現在編輯完文件之后,我們只需確認是否保存文件,而無需選擇文件的保存路徑,在大大提高了用戶的使用體驗。

  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.       // 處理導入的控件庫的邏輯 
  9.     } else { 
  10.       this.setState({ isLoading: true }); 
  11.       if (fsSupported) { // 判斷是否支持File System Access API 
  12.         try { 
  13.           const item = event.dataTransfer.items[0]; 
  14.           // 關鍵點:獲取FileSystemHandle對象 
  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.   }; 

以上代碼的關鍵點是,調用 DataTransferItem.getAsFileSystemHandle() 方法來獲取 FileSystemFileHandle 對象。擁有該對象之后,我們就可以對文件進行讀、寫操作。具體的使用方式如下所示:

讀文件示例

  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(); 

寫文件示例

  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(); 

三、復制圖片到剪貼板

  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 函數支持一個 blob 參數,在該函數內部會調用 navigator.clipboard.write 方法,來實現把圖片復制到剪貼板。而對于普通文本來說,你可以通過 navigator.clipboard.writeText 方法,把它們寫入到系統的剪貼板。

其實 navigator.clipboard.write 和 navigator.clipboard.writeText 方法是 Clipboard 接口定義的方法,該接口實現了 Clipboard API,如果用戶授予了相應的權限,就能提供系統剪貼板的讀寫訪問。在 Web 應用程序中,Clipboard API 可用于實現剪切、復制和粘貼功能。該 API 用于取代通過 document.execCommand API 來實現剪貼板的操作。

在實際工作中,我們不需要手動創建 Clipboard 對象,而是通過 navigator.clipboard 來獲取 Clipboard 對象:

在獲取 Clipboard 對象之后,我們就可以利用該對象提供的 API 來訪問剪貼板。比如,通過 navigator.clipboard.readText 方法來讀取剪貼板的內容:

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

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

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

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

對于瀏覽器擴展來說,你可以請求 clipboardRead 和 clipboardWrite 權限以使用 clipboard.readText() 和 clipboard.writeText()。如果你對 Clipboard 其他 API 感興趣的話,可以閱讀 想要復制圖像?Clipboard API 了解一下 這篇文章。

其實除了上面介紹的技術, Excalidraw 還使用了其他 Web API 來實現特定的功能。比如利用 window.crypto API 來實現導出只讀鏈接時,對畫布數據進行加密保護。利用 WebSocket API 來實現協同編輯和利用 Share API 實現文件共享的功能,感興趣的小伙伴可以閱讀一下 Excalidraw 的相關源碼。

四、總結

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

由于目前 File_System_Access_API 的兼容性還不是很好,如果你想在項目中使用它的話。建議你使用 GoogleChromeLabs 開源的 browser-fs-access 這個庫,該庫不僅為我們提供了更簡潔的 API,而且還提供了自動降級的方案。在今后的項目中,有機會的話,小伙伴們可以嘗試一下。

五、參考資源

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

 

責任編輯:姜華 來源: 全棧修仙之路
相關推薦

2020-09-25 06:32:25

前端

2021-03-09 09:55:02

Vuejs前端代碼

2020-02-22 15:01:51

后端前端開發

2020-07-07 08:52:16

機器學習機器學習工具人工智能

2022-03-27 09:06:04

React類型定義前端

2016-01-18 10:06:05

編程

2020-12-31 10:47:03

開發Vuejs技術

2021-04-15 08:15:27

Vue.js源碼方法

2024-04-12 08:54:13

從庫數據庫應用

2020-11-04 07:13:57

數據工程代碼編程

2020-02-22 14:49:30

畢業入職半年感受

2021-07-28 07:01:09

薅羊毛架構Vue+SSR

2021-01-02 09:48:13

函數運算js

2020-10-30 12:40:04

Reac性能優化

2019-08-27 10:49:30

跳槽那些事兒技術Linux

2013-06-27 10:31:39

2019-08-16 17:14:28

跳槽那些事兒技術Linux

2011-10-18 11:43:25

UNIXC語言丹尼斯·里奇

2015-06-29 13:47:19

創業創業智慧

2023-06-06 08:14:18

核心Docker應用程序
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 色综合久| 日韩有码在线播放 | 国产乱码精品1区2区3区 | 中文字幕av亚洲精品一部二部 | 欧美激情亚洲 | 色综合久久久 | 蜜桃一区 | 伊人影院99 | 免费日本视频 | 午夜精品视频在线观看 | 色综合天天天天做夜夜夜夜做 | 国产在线精品区 | 色伊人网 | 国产精品久久久久久久久免费相片 | 91原创视频| 中文字幕av一区 | 超碰97人人人人人蜜桃 | 亚洲国产视频一区 | 日本在线看片 | 国产在线播 | 日日干日日 | 国产精品中文字幕在线 | 中文字幕免费视频 | 中文二区 | 精品美女久久久久久免费 | 在线成人 | 在线免费观看a级片 | 国产精品高 | 自拍偷拍小视频 | 国产亚洲一区二区在线观看 | 国产亚洲精品美女久久久久久久久久 | 亚洲国产精品一区二区久久 | 亚洲人精品午夜 | 国产高潮好爽受不了了夜夜做 | 国产精品综合一区二区 | 男女羞羞视频在线免费观看 | 高清一区二区 | 亚州av在线| 国产精品一区二区三区在线播放 | 视频在线观看一区 | 嫩草视频在线免费观看 |