前端如何實(shí)現(xiàn)對(duì)本地文件的IO操作詳解
前言
在網(wǎng)頁中,前端已經(jīng)可以讀取本地文件系統(tǒng),對(duì)本地的文件進(jìn)行IO讀寫,甚至可以制作一個(gè)簡單的VScode編輯器。這篇文章以漸進(jìn)式方式實(shí)現(xiàn)此功能,文末附上所有代碼。
首先看整體功能演示
功能概述
我們將實(shí)現(xiàn)一個(gè)簡單的 Web 應(yīng)用,具備以下功能:
- 選擇本地目錄:用戶可以選擇本地目錄并顯示其結(jié)構(gòu)。
- 文件瀏覽:用戶可以瀏覽目錄中的文件和子目錄。
- 文件編輯:用戶可以選擇文件并在網(wǎng)頁上進(jìn)行編輯。
- 文件保存:用戶可以將編輯后的文件保存到本地。
核心實(shí)現(xiàn)步驟
我們將功能拆分為以下幾個(gè)核心步驟:
- 選擇本地目錄
- 構(gòu)建文件樹
- 讀取和編輯文件
- 保存編輯后的文件
1. 選擇本地目錄
選擇本地目錄是實(shí)現(xiàn)這個(gè)功能的第一步。我們使用 File System Access API 的 showDirectoryPicker
方法來選擇目錄。
document.getElementById('selectDirectoryButton').addEventListener('click', async function() { try { const directoryHandle = await window.showDirectoryPicker(); console.log(directoryHandle); // 打印目錄句柄 } catch (error) { console.error('Error: ', error); } });
2. 構(gòu)建文件樹
選擇目錄后,我們需要遞歸地構(gòu)建文件樹,并在頁面上顯示文件和子目錄。
async function buildFileTree(directoryHandle, parentElement) { for await (const [name, entryHandle] of directoryHandle.entries()) { const li = document.createElement('li'); li.textContent = name; if (entryHandle.kind === 'file') { li.classList.add('file'); li.addEventListener('click', async function() { currentFileHandle = entryHandle; const file = await entryHandle.getFile(); const fileContent = await file.text(); document.getElementById('fileContent').textContent = fileContent; document.getElementById('editArea').value = fileContent; document.getElementById('editArea').style.display = 'block'; document.getElementById('saveButton').style.display = 'block'; }); } else if (entryHandle.kind === 'directory') { li.classList.add('folder'); const ul = document.createElement('ul'); ul.style.display = 'none'; li.appendChild(ul); li.addEventListener('click', function() { ul.style.display = ul.style.display === 'none' ? 'block' : 'none'; }); await buildFileTree(entryHandle, ul); } parentElement.appendChild(li); } }
3. 讀取和編輯文件
當(dāng)用戶點(diǎn)擊文件時(shí),我們讀取文件內(nèi)容,并在文本區(qū)域中顯示以便編輯。
li.addEventListener('click', async function() { currentFileHandle = entryHandle; const file = await entryHandle.getFile(); const fileContent = await file.text(); document.getElementById('fileContent').textContent = fileContent; document.getElementById('editArea').value = fileContent; document.getElementById('editArea').style.display = 'block'; document.getElementById('saveButton').style.display = 'block'; });
4. 保存編輯后的文件
編輯完成后,用戶可以點(diǎn)擊保存按鈕將修改后的文件內(nèi)容保存回本地文件。
document.getElementById('saveButton').addEventListener('click', async function() { if (currentFileHandle) { const editArea = document.getElementById('editArea'); const updatedContent = editArea.value; // 創(chuàng)建一個(gè) writable 流并寫入編輯后的文件內(nèi)容 const writable = await currentFileHandle.createWritable(); await writable.write(updatedContent); await writable.close(); // 更新顯示區(qū)域的內(nèi)容 document.getElementById('fileContent').textContent = updatedContent; } });
核心 API 介紹
window.showDirectoryPicker()
該方法打開目錄選擇對(duì)話框,并返回一個(gè) FileSystemDirectoryHandle
對(duì)象,代表用戶選擇的目錄。
const directoryHandle = await window.showDirectoryPicker();
FileSystemDirectoryHandle.entries()
該方法返回一個(gè)異步迭代器,用于遍歷目錄中的所有文件和子目錄。
for await (const [name, entryHandle] of directoryHandle.entries()) { // 處理每個(gè)文件或目錄 }
FileSystemFileHandle.getFile()
該方法返回一個(gè) File
對(duì)象,表示文件的內(nèi)容。
const file = await fileHandle.getFile();
FileSystemFileHandle.createWritable()
該方法創(chuàng)建一個(gè)可寫流,用于寫入文件內(nèi)容。
const writable = await fileHandle.createWritable(); await writable.write(content); await writable.close();
總結(jié)
通過以上步驟,我們能夠選擇本地目錄、瀏覽文件和子目錄、讀取和編輯文件內(nèi)容,并將編輯后的文件保存回本地。同時(shí),我們使用 Highlight.js 實(shí)現(xiàn)了代碼高亮顯示。
源碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Local File Browser with Edit and Save</title> <link rel="stylesheet" rel="external nofollow" > <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/highlight.min.js"></script> <style> body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 0; display: flex; height: 100vh; overflow: hidden; background-color: #2e2e2e; color: #f1f1f1; } #sidebar { width: 20%; background-color: #333; border-right: 1px solid #444; padding: 20px; box-sizing: border-box; overflow-y: auto; } #content { width: 40%; padding: 20px; box-sizing: border-box; overflow-y: auto; } #preview { width: 40%; padding: 20px; box-sizing: border-box; overflow-y: auto; background-color: #1e1e1e; border-left: 1px solid #444; } #fileTree { list-style-type: none; padding: 0; } #fileTree li { margin-bottom: 5px; cursor: pointer; user-select: none; /* 禁止文本選中 */ } #fileTree .folder::before { content: "??"; margin-right: 5px; } #fileTree .file::before { content: "??"; margin-right: 5px; } #fileContent { white-space: pre-wrap; /* Preserve whitespace */ background-color: #1e1e1e; padding: 10px; border: 1px solid #444; min-height: 200px; color: #f1f1f1; } #editArea { width: 100%; height: calc(100% - 40px); background-color: #1e1e1e; color: #f1f1f1; border: 1px solid #444; padding: 10px; box-sizing: border-box; } #saveButton { margin-top: 10px; background-color: #4caf50; color: white; border: none; padding: 10px 15px; cursor: pointer; border-radius: 5px; } #saveButton:hover { background-color: #45a049; } h1 { font-size: 1.2em; margin-bottom: 10px; } ::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-track { background: #333; } ::-webkit-scrollbar-thumb { background-color: #555; border-radius: 10px; border: 2px solid #333; } .hidden { display: none; } </style> </head> <body> <div id="sidebar"> <h1>選擇目錄</h1> <button id="selectDirectoryButton">選擇目錄</button> <ul id="fileTree"></ul> </div> <div id="content"> <h1>編輯文件</h1> <textarea id="editArea"></textarea> <button id="saveButton">保存編輯后的文件內(nèi)容</button> </div> <div id="preview"> <h1>本地文件修改后的實(shí)時(shí)預(yù)覽</h1> <pre><code id="fileContent" class="plaintext"></code></pre> </div> <script> let currentFileHandle = null; document.getElementById('selectDirectoryButton').addEventListener('click', async function() { try { const directoryHandle = await window.showDirectoryPicker(); const fileTree = document.getElementById('fileTree'); fileTree.innerHTML = ''; // 清空文件樹 async function buildFileTree(directoryHandle, parentElement) { for await (const [name, entryHandle] of directoryHandle.entries()) { const li = document.createElement('li'); li.textContent = name; if (entryHandle.kind === 'file') { li.classList.add('file'); li.addEventListener('click', async function() { currentFileHandle = entryHandle; const file = await entryHandle.getFile(); const fileContent = await file.text(); const fileExtension = name.split('.').pop(); const codeElement = document.getElementById('fileContent'); const editArea = document.getElementById('editArea'); codeElement.textContent = fileContent; editArea.value = fileContent; codeElement.className = ''; // 清除之前的語言類 codeElement.classList.add(getHighlightLanguage(fileExtension)); hljs.highlightElement(codeElement); // 顯示編輯區(qū)域和保存按鈕 editArea.style.display = 'block'; document.getElementById('saveButton').style.display = 'block'; }); } else if (entryHandle.kind === 'directory') { li.classList.add('folder'); const ul = document.createElement('ul'); ul.classList.add('hidden'); // 默認(rèn)隱藏子目錄 li.appendChild(ul); li.addEventListener('click', function(event) { event.stopPropagation(); // 阻止事件冒泡 ul.classList.toggle('hidden'); }); await buildFileTree(entryHandle, ul); } parentElement.appendChild(li); } } await buildFileTree(directoryHandle, fileTree); } catch (error) { console.log('Error: ', error); } }); // 獲取代碼高亮語言類型 function getHighlightLanguage(extension) { switch (extension) { case 'js': return 'javascript'; case 'html': return 'html'; case 'css': return 'css'; case 'json': return 'json'; case 'xml': return 'xml'; case 'py': return 'python'; case 'java': return 'java'; default: return 'plaintext'; } } // 保存編輯后的文件內(nèi)容 document.getElementById('saveButton').addEventListener('click', async function() { if (currentFileHandle) { const editArea = document.getElementById('editArea'); const updatedContent = editArea.value; // 創(chuàng)建一個(gè) writable 流并寫入編輯后的文件內(nèi)容 const writable = await currentFileHandle.createWritable(); await writable.write(updatedContent); await writable.close(); // 更新高亮顯示區(qū)域的內(nèi)容 const codeElement = document.getElementById('fileContent'); codeElement.textContent = updatedContent; hljs.highlightElement(codeElement); } }); // 初始化 highlight.js hljs.initHighlightingOnLoad(); </script> </body> </html>
到此這篇關(guān)于前端如何實(shí)現(xiàn)對(duì)本地文件的IO操作的文章就介紹到這了,更多相關(guān)前端本地文件的IO操作內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
BootstrapTable refresh 方法使用實(shí)例簡單介紹
本文就bootstrapTable refresh 方法如何傳遞參數(shù)做簡單舉例說明,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-02-02Javascript attachEvent傳遞參數(shù)的辦法
找了半天找到的解決辦法,看介紹說是javascript的閉包問題,導(dǎo)致得不能直接讀取外部的那個(gè)函數(shù),不然就所有傳遞的參數(shù)都變?yōu)樽詈笠粋€(gè)了。2009-12-12最精簡的JavaScript實(shí)現(xiàn)鼠標(biāo)拖動(dòng)效果的方法
這篇文章主要介紹了最精簡的JavaScript實(shí)現(xiàn)鼠標(biāo)拖動(dòng)效果的方法,可實(shí)現(xiàn)javascript控制鼠標(biāo)拖動(dòng)div層效果的方法,需要的朋友可以參考下2015-05-05js導(dǎo)出table到excel同時(shí)兼容FF和IE示例
js導(dǎo)出table到excel,在百度可以搜索很多的方法,但是其兼容性是相當(dāng)差的,本文制定了一個(gè)可以同時(shí)兼容FF和IE的方法,感興趣的朋友可以參考下2013-09-09JS實(shí)現(xiàn)可編輯的后臺(tái)管理菜單功能【附demo源碼下載】
這篇文章主要介紹了JS實(shí)現(xiàn)可編輯的后臺(tái)管理菜單功能,涉及javascript針對(duì)頁面元素的遍歷及動(dòng)態(tài)修改相關(guān)操作技巧,并附帶demo源碼供讀者下載參考,需要的朋友可以參考下2016-09-09詳解js中構(gòu)造流程圖的核心技術(shù)JsPlumb
這篇文章主要介紹了js中構(gòu)造流程圖的核心技術(shù)JsPlumb,jsPlumb是一個(gè)強(qiáng)大的JavaScript連線庫,它可以將html中的元素用箭頭、曲線、直線等連接起來,適用于開發(fā)Web上的圖表、建模工具等,需要的朋友可以參考下2015-12-12深入理解ES6之?dāng)?shù)據(jù)解構(gòu)的用法
本文介紹了深入理解ES6之?dāng)?shù)據(jù)解構(gòu)的用法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-01-01