前端如何實現對本地文件的IO操作詳解
前言
在網頁中,前端已經可以讀取本地文件系統(tǒng),對本地的文件進行IO讀寫,甚至可以制作一個簡單的VScode編輯器。這篇文章以漸進式方式實現此功能,文末附上所有代碼。
首先看整體功能演示

功能概述
我們將實現一個簡單的 Web 應用,具備以下功能:
- 選擇本地目錄:用戶可以選擇本地目錄并顯示其結構。
- 文件瀏覽:用戶可以瀏覽目錄中的文件和子目錄。
- 文件編輯:用戶可以選擇文件并在網頁上進行編輯。
- 文件保存:用戶可以將編輯后的文件保存到本地。
核心實現步驟
我們將功能拆分為以下幾個核心步驟:
- 選擇本地目錄
- 構建文件樹
- 讀取和編輯文件
- 保存編輯后的文件
1. 選擇本地目錄
選擇本地目錄是實現這個功能的第一步。我們使用 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. 構建文件樹
選擇目錄后,我們需要遞歸地構建文件樹,并在頁面上顯示文件和子目錄。
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. 讀取和編輯文件
當用戶點擊文件時,我們讀取文件內容,并在文本區(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. 保存編輯后的文件
編輯完成后,用戶可以點擊保存按鈕將修改后的文件內容保存回本地文件。
document.getElementById('saveButton').addEventListener('click', async function() {
if (currentFileHandle) {
const editArea = document.getElementById('editArea');
const updatedContent = editArea.value;
// 創(chuàng)建一個 writable 流并寫入編輯后的文件內容
const writable = await currentFileHandle.createWritable();
await writable.write(updatedContent);
await writable.close();
// 更新顯示區(qū)域的內容
document.getElementById('fileContent').textContent = updatedContent;
}
});
核心 API 介紹
window.showDirectoryPicker()
該方法打開目錄選擇對話框,并返回一個 FileSystemDirectoryHandle 對象,代表用戶選擇的目錄。
const directoryHandle = await window.showDirectoryPicker();
FileSystemDirectoryHandle.entries()
該方法返回一個異步迭代器,用于遍歷目錄中的所有文件和子目錄。
for await (const [name, entryHandle] of directoryHandle.entries()) {
// 處理每個文件或目錄
}
FileSystemFileHandle.getFile()
該方法返回一個 File 對象,表示文件的內容。
const file = await fileHandle.getFile();
FileSystemFileHandle.createWritable()
該方法創(chuàng)建一個可寫流,用于寫入文件內容。
const writable = await fileHandle.createWritable(); await writable.write(content); await writable.close();
總結
通過以上步驟,我們能夠選擇本地目錄、瀏覽文件和子目錄、讀取和編輯文件內容,并將編輯后的文件保存回本地。同時,我們使用 Highlight.js 實現了代碼高亮顯示。
源碼
<!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">保存編輯后的文件內容</button>
</div>
<div id="preview">
<h1>本地文件修改后的實時預覽</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'); // 默認隱藏子目錄
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';
}
}
// 保存編輯后的文件內容
document.getElementById('saveButton').addEventListener('click', async function() {
if (currentFileHandle) {
const editArea = document.getElementById('editArea');
const updatedContent = editArea.value;
// 創(chuàng)建一個 writable 流并寫入編輯后的文件內容
const writable = await currentFileHandle.createWritable();
await writable.write(updatedContent);
await writable.close();
// 更新高亮顯示區(qū)域的內容
const codeElement = document.getElementById('fileContent');
codeElement.textContent = updatedContent;
hljs.highlightElement(codeElement);
}
});
// 初始化 highlight.js
hljs.initHighlightingOnLoad();
</script>
</body>
</html>到此這篇關于前端如何實現對本地文件的IO操作的文章就介紹到這了,更多相關前端本地文件的IO操作內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
BootstrapTable refresh 方法使用實例簡單介紹
本文就bootstrapTable refresh 方法如何傳遞參數做簡單舉例說明,非常不錯,具有參考借鑒價值,需要的朋友參考下吧2017-02-02

