<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【總結(jié)】1123- 從 23.9K 的前端開源項(xiàng)目我學(xué)到了啥?

          共 10270字,需瀏覽 21分鐘

           ·

          2021-10-30 11:37

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

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

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

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

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

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

          一、保存文件到指定目錄

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

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

          //?https://github.com/excalidraw/excalidraw/blob/master/src/data/json.ts#L31
          import?{?fileOpen,?fileSave?}?from?"browser-fs-access";

          export?const?saveAsJSON?=?async?(
          ??elements:?readonly?ExcalidrawElement[],
          ??appState:?AppState,
          )?=>?{
          ??const?serialized?=?serializeAsJSON(elements,?appState);
          ??const?blob?=?new?Blob([serialized],?{
          ????type:?MIME_TYPES.excalidraw,
          ??});
          ?
          ??const?fileHandle?=?await?fileSave(
          ????blob,
          ????{
          ??????fileName:?`${appState.name}.excalidraw`,
          ??????description:?"Excalidraw?file",
          ??????extensions:?[".excalidraw"],
          ????},
          ????isImageFileHandle(appState.fileHandle)???null?:?appState.fileHandle,
          ??);
          ??return?{?fileHandle?};
          };

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

          const?saveFile?=?async?(blob,?filename)?=>?{
          ??const?a?=?document.createElement('a');
          ??a.download?=?filename;
          ??a.href?=?URL.createObjectURL(blob);
          ??a.addEventListener('click',?(e)?=>?{
          ????setTimeout(()?=>?URL.revokeObjectURL(a.href),?30?*?1000);
          ??});
          ??a.click();
          };

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

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

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

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

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

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

          • excludeAcceptAllOption:布爾類型,默認(rèn)值為 false。默認(rèn)情況下,選擇器應(yīng)包含一個不應(yīng)用任何文件類型過濾器的選項(xiàng)(由下面的 types 選項(xiàng)啟用)。將此選項(xiàng)設(shè)置為 true 意味著 types 選項(xiàng)不可用。
          • types:數(shù)組類型,表示允許保存的文件類型列表。數(shù)組中的每一項(xiàng)是包含以下屬性的配置對象:
            • description(可選):用于描述允許保存文件類型類別。
            • accept:是一個對象,該對象的 key 是 MIME 類型,值是文件擴(kuò)展名列表。

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

          async?function?saveFile(blob,?filename)?{
          ??try?{
          ????const?handle?=?await?window.showSaveFilePicker({
          ??????suggestedName:?filename,
          ??????types:?[
          ????????{
          ??????????description:?"PNG?file",
          ??????????accept:?{
          ????????????"image/png":?[".png"],
          ??????????},
          ????????},
          ??????],
          ?????});
          ????const?writable?=?await?handle.createWritable();
          ????await?writable.write(blob);
          ????await?writable.close();
          ????return?handle;
          ??}?catch?(err)?{
          ?????console.error(err.name,?err.message);
          ??}
          }

          saveFile(imgBlob,?"face.png");

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

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

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

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

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

          let?FileSystemHandles?=?Window.showOpenFilePicker();

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

          • multiple:布爾類型,默認(rèn)值為 false。若設(shè)置為 true,則允許選擇多個文件。
          • excludeAcceptAllOption:布爾類型,默認(rèn)值為 false。默認(rèn)情況下,選擇器應(yīng)包含一個不應(yīng)用任何文件類型過濾器的選項(xiàng)(由下面的 types 選項(xiàng)啟用)。將此選項(xiàng)設(shè)置為 true 意味著 types 選項(xiàng)不可用。
          • types:數(shù)組類型,表示允許保存的文件類型列表。數(shù)組中的每一項(xiàng)是包含以下屬性的配置對象:
            • description(可選):用于描述允許保存文件類型類別。
            • accept:是一個對象,該對象的 key 是 MIME 類型,值是文件擴(kuò)展名列表。

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

          <div>
          ???<textarea?id="container"?rows="5"?cols="30">textarea>
          div>
          <button?onclick="openFile()">打開文件button>
          <script>
          ???const?container?=?document.querySelector("#container");
          ??
          ???async?function?openFile()?{
          ?????let?[fileHandle]?=?await?window.showOpenFilePicker();
          ?????const?file?=?await?fileHandle.getFile();
          ?????const?contents?=?await?file.text();
          ?????container.value?=?contents;
          ???}
          script>

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

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

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

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

          var?FileSystemDirectoryHandle?=?Window.showDirectoryPicker();

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

          讀取目錄的信息

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

          讀取目錄下的指定文件

          const?container?=?document.querySelector("#container");

          async?function?readFile()?{
          ??const?dirHandle?=?await?window.showDirectoryPicker();
          ??const?fileHandle?=?await?dirHandle.getFileHandle("hello.txt");
          ??const?file?=?await?fileHandle.getFile();
          ??const?contents?=?await?file.text();
          ??container.value?=?contents;
          }

          刪除目錄下的指定文件

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

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

          創(chuàng)建指定文件

          async?function?createFile()?{
          ??const?dirHandle?=?await?window.showDirectoryPicker();
          ??const?fileHandle?=?await?dirHandle.getFileHandle("hello.new.txt",?{
          ????create:?true,
          ??});
          ??container.value?=?"hello.new.txt文件創(chuàng)建成功!";
          ??const?writable?=?await?fileHandle.createWritable();
          ??await?writable.write(new?Blob(["大家好,我是阿寶哥!"]));
          ??await?writable.close();
          }

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

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

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

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

          class?App?extends?React.Component<AppProps,?AppState>?{
          ???//?省略大部分代碼
          ????const?file?=?event.dataTransfer?.files[0];
          ????if?(
          ??????file?.type?===?MIME_TYPES.excalidrawlib?||
          ??????file?.name?.endsWith(".excalidrawlib")
          ????)?{
          ??????//?處理導(dǎo)入的控件庫的邏輯
          ????}?else?{
          ??????this.setState({?isLoading:?true?});
          ??????if?(fsSupported)?{?//?判斷是否支持File?System?Access?API
          ????????try?{
          ??????????const?item?=?event.dataTransfer.items[0];
          ??????????//?關(guān)鍵點(diǎn):獲取FileSystemHandle對象
          ??????????(file?as?any).handle?=?await?(item?as?any).getAsFileSystemHandle();
          ????????}?catch?(error)?{
          ??????????console.warn(error.name,?error.message);
          ????????}
          ??????}
          ??????//?加載.excalidraw文件到Canvas
          ??????await?this.loadFileToCanvas(file);
          ????}
          ??};
          }

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

          讀文件示例

          async?function?getTheFile()?{
          ??//?open?file?picker
          ??[fileHandle]?=?await?window.showOpenFilePicker(pickerOpts);

          ??//?get?file?contents
          ??const?fileData?=?await?fileHandle.getFile();
          }

          寫文件示例

          async?function?writeFile(fileHandle,?contents)?{
          ??//?Create?a?FileSystemWritableFileStream?to?write?to.
          ??const?writable?=?await?fileHandle.createWritable();

          ??//?Write?the?contents?of?the?file?to?the?stream.
          ??await?writable.write(contents);

          ??//?Close?the?file?and?write?the?contents?to?disk.
          ??await?writable.close();
          }

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

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

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

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

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

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

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

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

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

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

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

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

          四、總結(jié)

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

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

          五、參考資源

          • web.dev — excalidraw-and-fugu
          • web.dev — browser-fs-access
          • web.dev — file-system-access
          • MDN — File_System_Access_API
          • MDN — FileSystemFileHandle
          瀏覽 81
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品V无码A片在线看吃奶 | 精品久久久久久久久久久久久久 | 成年人黄色网址 | 激情五月色婷婷 | 色综合大大 |