การจัดการไดเรกทอรีไม่ใช่สิ่งที่คุณต้องรับมือในแต่ละวัน แต่บางครั้งก็เกิดกรณีใช้งานขึ้น เช่น ต้องการประมวลผลรูปภาพทั้งหมดในไดเรกทอรี เมื่อใช้ File System Access API ผู้ใช้จะเปิดไดเรกทอรีในเบราว์เซอร์และตัดสินใจได้ว่าต้องมีสิทธิ์เข้าถึงการเขียนหรือไม่
วิธีสมัยใหม่
การใช้เมธอด showDirectoryPicker()
ของ File System Access API
หากต้องการเปิดไดเรกทอรี ให้เรียกใช้ showDirectoryPicker()
ซึ่งจะแสดงผลคำสัญญาพร้อมกับไดเรกทอรีที่เลือก หากต้องการสิทธิ์การเขียน คุณสามารถส่ง { mode: 'readwrite' }
ไปยังเมธอดได้
วิธีคลาสสิก
การใช้องค์ประกอบ <input type="file" webkitdirectory>
องค์ประกอบ <input type="file" webkitdirectory>
ในหน้าเว็บช่วยให้ผู้ใช้คลิกและเปิดไดเรกทอรีได้ กลวิธีตอนนี้ประกอบด้วยการแทรกองค์ประกอบอย่างไม่แสดงในหน้าเว็บด้วย JavaScript และคลิกแบบเป็นโปรแกรม
การเพิ่มประสิทธิภาพแบบต่อเนื่อง
วิธีการด้านล่างใช้ File System Access API เมื่อมีการรองรับและเมื่อเปลี่ยนมาใช้วิธีคลาสสิกแล้ว ในทั้ง 2 กรณี ฟังก์ชันจะแสดงผลไดเรกทอรี แต่ในกรณีที่มีการรองรับ File System Access API ออบเจ็กต์ไฟล์แต่ละรายการจะมี FileSystemDirectoryHandle
จัดเก็บไว้ในพร็อพเพอร์ตี้ directoryHandle
และ FileSystemFileHandle
ที่จัดเก็บไว้ในพร็อพเพอร์ตี้ handle
อยู่ เพื่อให้คุณสามารถเลือกอนุกรมของแฮนเดิลลงในดิสก์ได้
const openDirectory = async (mode = "read") => {
// Feature detection. The API needs to be supported
// and the app not run in an iframe.
const supportsFileSystemAccess =
"showDirectoryPicker" in window &&
(() => {
try {
return window.self === window.top;
} catch {
return false;
}
})();
// If the File System Access API is supported…
if (supportsFileSystemAccess) {
let directoryStructure = undefined;
// Recursive function that walks the directory structure.
const getFiles = async (dirHandle, path = dirHandle.name) => {
const dirs = [];
const files = [];
for await (const entry of dirHandle.values()) {
const nestedPath = `${path}/${entry.name}`;
if (entry.kind === "file") {
files.push(
entry.getFile().then((file) => {
file.directoryHandle = dirHandle;
file.handle = entry;
return Object.defineProperty(file, "webkitRelativePath", {
configurable: true,
enumerable: true,
get: () => nestedPath,
});
})
);
} else if (entry.kind === "directory") {
dirs.push(getFiles(entry, nestedPath));
}
}
return [
...(await Promise.all(dirs)).flat(),
...(await Promise.all(files)),
];
};
try {
// Open the directory.
const handle = await showDirectoryPicker({
mode,
});
// Get the directory structure.
directoryStructure = getFiles(handle, undefined);
} catch (err) {
if (err.name !== "AbortError") {
console.error(err.name, err.message);
}
}
return directoryStructure;
}
// Fallback if the File System Access API is not supported.
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
input.webkitdirectory = true;
input.addEventListener('change', () => {
let files = Array.from(input.files);
resolve(files);
});
if ('showPicker' in HTMLInputElement.prototype) {
input.showPicker();
} else {
input.click();
}
});
};
อ่านเพิ่มเติม
เดโม
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📂</text></svg>"
/>
<title>How to open a directory</title>
</head>
<body>
<h1>How to open a directory</h1>
<button type="button">Open directory</button>
<pre></pre>
</body>
</html>
CSS
:root {
color-scheme: dark light;
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 1rem;
font-family: system-ui, sans-serif;
}
JS
const button = document.querySelector('button');
const pre = document.querySelector('pre');
const openDirectory = async (mode = "read") => {
// Feature detection. The API needs to be supported
// and the app not run in an iframe.
const supportsFileSystemAccess =
"showDirectoryPicker" in window &&
(() => {
try {
return window.self === window.top;
} catch {
return false;
}
})();
// If the File System Access API is supported…
if (supportsFileSystemAccess) {
let directoryStructure = undefined;
const getFiles = async (dirHandle, path = dirHandle.name) => {
const dirs = [];
const files = [];
for await (const entry of dirHandle.values()) {
const nestedPath = `${path}/${entry.name}`;
if (entry.kind === "file") {
files.push(
entry.getFile().then((file) => {
file.directoryHandle = dirHandle;
file.handle = entry;
return Object.defineProperty(file, "webkitRelativePath", {
configurable: true,
enumerable: true,
get: () => nestedPath,
});
})
);
} else if (entry.kind === "directory") {
dirs.push(getFiles(entry, nestedPath));
}
}
return [
...(await Promise.all(dirs)).flat(),
...(await Promise.all(files)),
];
};
try {
const handle = await showDirectoryPicker({
mode,
});
directoryStructure = getFiles(handle, undefined);
} catch (err) {
if (err.name !== "AbortError") {
console.error(err.name, err.message);
}
}
return directoryStructure;
}
// Fallback if the File System Access API is not supported.
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
input.webkitdirectory = true;
input.addEventListener('change', () => {
let files = Array.from(input.files);
resolve(files);
});
if ('showPicker' in HTMLInputElement.prototype) {
input.showPicker();
} else {
input.click();
}
});
};
button.addEventListener('click', async () => {
const filesInDirectory = await openDirectory();
if (!filesInDirectory) {
return;
}
Array.from(filesInDirectory).forEach((file) => (pre.textContent += `${file.name}\n`));
});