浏览器文件操作API
一、 文件基础API
(一) input
获取文件只读对象
- 选择文件
<body>
<input type="file" />
<script>
document.querySelector('input').addEventListener('change', function (e) {
console.log(e.target.files)
})
</script>
</body>
- 选择文件夹
<body>
<input type="file" webkitdirectory multiple />
<script>
document.querySelector('input').addEventListener('change', function (e) {
console.log(e.target.files)
})
</script>
</body>
(二) 拖拽API
Drag and Drop API 允许用户通过拖放操作来上传文件。
dragenter
事件: 当被拖动的元素进入目标区域时触发。dragover
事件: 当被拖动的元素在目标区域内移动时触发。drop
事件: 当被拖动的元素在目标区域内释放时触发。DataTransfer
对象: 包含拖放操作的数据。
<body>
<style>
#dropArea {
width: 300px;
height: 200px;
border: 2px dashed #ccc;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
}
</style>
<div id="dropArea">拖放文件到这里</div>
<script>
let dropArea = document.querySelector('#dropArea')
dropArea.addEventListener('dragenter', (event) => {
event.preventDefault();
dropArea.style.border = '2px dashed blue';
});
dropArea.addEventListener('dragover', (event) => {
event.preventDefault(); // 需要屏蔽一些默认行为才能获取到文件
});
dropArea.addEventListener('dragleave', (event) => {
dropArea.style.border = '2px dashed #ccc';
});
document.querySelector('#dropArea').addEventListener('drop', function (e) {
e.preventDefault(); // 需要屏蔽一些默认行为才能获取到文件
console.log(e.dataTransfer.files)
})
</script>
</body>
(三) 读取文件
1. File对象
File对象继承Blob对象,可以直接使用一些读取方法:
file.arrayBuffer()
返回一个 Promise,其会兑现一个包含 blob 二进制数据内容的 ArrayBuffer。file.stream()
返回一个 ReadableStream 对象,只读流。file.text()
返回一个 Promise,其会兑现一个包含 blob 内容的 UTF-8 格式的字符串。
2. FileReader对象
FileReader 是一个用于读取文件内容的 Web API。它允许你异步读取存储在用户计算机上的文件内容,并将这些内容提供给 JavaScript 代码进行处理。
FileReader
提供了以下几种方法来读取文件:
readAsText(file, [encoding])
: 以文本形式读取文件,可指定编码类型。readAsArrayBuffer(file)
: 以二进制数组形式读取文件。readAsDataURL(file)
: 以数据 URL 形式读取文件(常用于图像预览)。
<body>
<input type="file" />
<pre id="fileContent"></pre>
<script>
document.querySelector('input').addEventListener('change', function (e) {
let file = e.target.files[0];
const reader = new FileReader();
reader.onload = (e) => {
const content = e.target.result;
document.getElementById('fileContent').textContent = content;
};
reader.onerror = (e) => {
console.error('Error reading file:', e);
};
reader.readAsText(file);
})
</script>
</body>
二、 File System Access API
强大的文件操作能力
File System Access API 是一个现代的 Web API,它允许网页应用程序以更细粒度的方式访问用户的文件系统。与传统的 <input type="file">
元素不同,File System Access API 提供了更多的功能,包括读写文件、创建和删除目录等。这个 API 使得 Web 应用程序可以像桌面应用程序一样处理文件和目录。
- 实验性: 这是一项实验性技术,使用时注意浏览器兼容性。
- 安全上下文: 此项功能仅在一些支持的浏览器的安全上下文(HTTPS)中可用。
- 判断是否可用API
function canUseFileAPI() {
// window.isSecureContext 是否安全上下文
// 'showOpenFilePicker' in self 是否存在API方法
return window.isSecureContext && 'showOpenFilePicker' in self;
}
(一) 获取文件
window.showOpenFilePicker()
方法用于显示一个文件选择器,以允许用户选择一个或多个文件并返回这些文件的句柄
<script>
let fileBtn = document.querySelector('#fileBtn');
fileBtn.addEventListener('click', async () => {
let fileHandleList = await window.showOpenFilePicker();
console.log(fileHandleList[0]);
})
</script>
(二) 读取文件内容
handle.getFile()
会返回一个 File 对象,其中包含一个 blob。如需从 blob 中获取数据,请调用 其方法之一(slice()、stream()、text() 或 arrayBuffer())。
<script>
let fileBtn = document.querySelector('#fileBtn');
fileBtn.addEventListener('click', async () => {
let fileHandleList = await window.showOpenFilePicker();
let fileHandle = fileHandleList[0]; // 句柄对象或索引对象
console.log('fileHandle',fileHandle);
let file = await fileHandle.getFile(); // 文件对象
console.log('file',file);
let content = await file.text(); // 文件内容
console.log('content',content);
})
</script>
- 只要磁盘上的底层文件未更改,
FileSystemFileHandle.getFile()
返回的File
对象便可读取。如果磁盘上的文件被修改,File
对象将变为不可读,您需要再次调用getFile()
以获取新的File
对象来读取已更改的数据。
(三) 修改文件内容
handle.createWritable()
方法用于创建一个 FileSystemWritableFileStream 对象,可用于写入文件。此方法返回一个可兑现这些写入流的 Promise 对象。
任何通过写入流造成的更改在写入流被关闭前都不会反映到文件句柄所代表的文件上。这通常是将数据写入到一个临时文件来实现的,然后只有在写入文件流被关闭后才会用临时文件替换掉文件句柄所代表的文件。
- 写入时,不指定写入位置,默认从头开始写入,即会覆盖原数据
<script>
let fileBtn = document.querySelector('#fileBtn');
fileBtn.addEventListener('click', async () => {
let fileHandleList = await window.showOpenFilePicker();
let fileHandle = fileHandleList[0];
console.log('fileHandle',fileHandle);
console.log(await readFileText(fileHandle));
let file = await fileHandle.getFile();
// keepExistingData 如果文件存在,则现将现有文件的内容复制到临时文件,否则临时文件初始时内容为空
let writableStream = await fileHandle.createWritable({keepExistingData: true});
await writableStream.write('1234567890');
await writableStream.close();
console.log(await readFileText(fileHandle));
})
async function readFileText(fileHandle) {
let file = await fileHandle.getFile();
let text = await file.text();
return text;
}
</script>
- 写入时,指定写入位置(从末尾开始写入)
fileBtn.addEventListener('click', async () => {
let fileHandleList = await window.showOpenFilePicker();
let fileHandle = fileHandleList[0];
console.log('fileHandle',fileHandle);
console.log(await readFileText(fileHandle));
let file = await fileHandle.getFile();
let writableStream = await fileHandle.createWritable({keepExistingData: true});
await writableStream.write({type:'write',position:file.size,data:'1234567890'});
await writableStream.close();
console.log(await readFileText(fileHandle));
})
- 其他写入方式
// 只传递数据(没有选项)
writableStream.write(data);
// 向流中指定位置写入数据
writableStream.write({ type: "write", position, data });
// 将文件当前的指针更新到指定的偏移位置
writableStream.write({ type: "seek", position });
// 调整文件至指定字节长度
writableStream.write({ type: "truncate", size });
(四) 创建文件
window.showSaveFilePicker()
方法用于显示一个文件选择器,以允许用户保存一个文件。可以选择一个已有文件覆盖保存,也可以输入名字新建一个文件。
- 创建空文件并写入数据
<script>
let saveBtn = document.querySelector('#saveBtn');
saveBtn.addEventListener('click', async () => {
// 创建空文件
let fileHandle = await window.showSaveFilePicker({
suggestedName: "showSaveFilePicker.txt",
});
// 写入数据
let writableStream = await fileHandle.createWritable();
await writableStream.write('1234567890');
await writableStream.close();
console.log(fileHandle)
})
</script>
(五) 删除文件或文件夹
handle.remove()
方法用于删除句柄对象对应的文件或文件夹。
// 删除文件
await fileHandle.remove();
// 删除文件夹
await directoryHandle.remove();
(六) 获取文件夹
window.showDirectoryPicker()
方法用于显示一个目录选择器,以允许用户选择一个目录。
<script>
let fileBtn = document.querySelector('#fileBtn');
fileBtn.addEventListener('click', async () => {
let dirHandle = await window.showDirectoryPicker();
console.log(dirHandle)
})
</script>
(七) 读取文件夹内容
dirHandle.entries()
方法返回一个异步迭代器,用于迭代调用此方法的FileSystemDirectoryHandle
中的条目的键值对。键值对是一个[key, value]
形式的数组。
// 获取目录下文件或文件夹
async function getDirEntries(dirHandle) {
let list = [];
for await (const item of dirHandle.entries()) {
list.push(item);
}
return list;
}
dirHandle.keys()
方法返回一个异步迭代器,用于迭代调用此方法的FileSystemDirectoryHandle
中的条目的键。
async function getDirKeys(dirHandle) {
let list = [];
for await (const item of dirHandle.keys()) {
list.push(item);
}
return list;
}
dirHandle.values()
方法返回一个异步迭代器,用于迭代调用此方法的FileSystemDirectoryHandle
中的条目的值。
async function getDirValues(dirHandle) {
let list = [];
for await (const item of dirHandle.values()) {
list.push(item);
}
return list;
}
获取目录下所有数据(返回树形结构)
async function getDirAllValues(dirHandle) {
let list = [];
for await (const item of dirHandle.values()) {
let newItem = { name: item.name, kind: item.kind, handle: item };
if (item.kind === 'directory') newItem.children = await getDirAllValues(item);
list.push(newItem);
}
return list;
}
(八) 获取目录下子条目的相对路径
dirHandle.resolve()
方法返回一个 Promise 对象,会兑现一个包含从父目录前往指定子条目中间的目录的名称的数组。数组的最后一项是子条目的名称。
async function getPathWithDir(dirHandle,handle) {
// resolve 返回的数组不包含目录dirHandle
let list = await dirHandle.resolve(handle);
list.unshift(dirHandle.name);
return list.join('/');
}
(九) 在目录中创建文件和文件夹
dirHandle.getFileHandle()
或 dirHandle.getDirectoryHandle()
方法创建或访问文件和文件夹。通过传入一个可选的 options 对象(键为 create,布尔值为 true 或 false),您可以确定是否应在文件或文件夹不存在时创建新文件或文件夹。
- 只能创建或访问子一级的文件和文件夹
- 若文件和文件夹已存在,会返回存在的文件和文件夹
// dirHandle文件夹下没有'test1.txt'时,会创建新文件返回
await dirHandle.getFileHandle('test1.txt', {create: true});
// dirHandle文件夹下没有'文件夹'时,会创建新文件夹返回
await dirHandle.getDirectoryHandle('文件夹', {create: true});
(十) 删除目录中的文件和文件夹
dirHandle.removeEntry(name)
方法用于尝试将目录句柄内指定名称的文件或目录移除。
dirHandle.removeEntry(name,{recursive:true})
recursive
默认为 false
。当设为 true
时,条目将会被递归移除(即便文件夹中有数据也会被删除)。
- 只能删除子一级的文件和文件夹
// 删除文件
await directoryHandle.removeEntry('test1.txt');
// 删除文件夹
await directoryHandle.removeEntry('文件夹', { recursive: true });
(十一) 重命名和移动文件
fileHandle.move()
方法用于重命名文件或将其移至新位置。
// 重命名文件
await fileHandle.move('new_name.txt');
// 移动文件到目录dirHandle
await fileHandle.move(dirHandle);
// 移动文件到目录dirHandle,并重命名文件
await fileHandle.move(dirHandle, 'newer_name.txt');