การจัดการไฟล์เป็นขั้นตอนหนึ่งที่พบบ่อยที่สุดสำหรับแอปบนเว็บ แต่เดิมนั้น ผู้ใช้ต้องอัปโหลดไฟล์ ทำการเปลี่ยนแปลงบางอย่าง แล้วดาวน์โหลดไฟล์อีกครั้ง จนเกิดเป็นสำเนาในโฟลเดอร์ดาวน์โหลด เมื่อใช้ File System Access API ผู้ใช้จะเปิดไฟล์ได้โดยตรง ทำการแก้ไข และบันทึกการเปลี่ยนแปลงในไฟล์ต้นฉบับได้
วิธีสมัยใหม่
การใช้เมธอด showOpenFilePicker()
ของ File System Access API
หากต้องการเปิดไฟล์ ให้ไปที่ showOpenFilePicker()
ซึ่งจะส่งคืนคำมั่นสัญญาพร้อมกับอาร์เรย์ของไฟล์ที่เลือก หากต้องการหลายไฟล์ ให้ส่ง { multiple: true, }
ไปยังเมธอด
วิธีคลาสสิก
การใช้องค์ประกอบ <input type="file">
องค์ประกอบ <input type="file">
ในหน้าเว็บช่วยให้ผู้ใช้คลิกและเปิดไฟล์ได้อย่างน้อย 1 ไฟล์ เคล็ดลับตอนนี้ประกอบด้วยการแทรกองค์ประกอบในหน้าด้วย JavaScript โดยไม่สามารถมองเห็นได้และคลิกเนื้อหานั้นแบบเป็นโปรแกรม
การเพิ่มประสิทธิภาพแบบต่อเนื่อง
วิธีการด้านล่างใช้ File System Access API เมื่อมีการรองรับและเมื่อเปลี่ยนมาใช้วิธีคลาสสิกแล้ว ในทั้ง 2 กรณี ฟังก์ชันจะแสดงผลอาร์เรย์ของไฟล์ แต่ในกรณีที่มีการรองรับ File System Access API ออบเจ็กต์ไฟล์แต่ละรายการจะมี FileSystemFileHandle
จัดเก็บไว้ในพร็อพเพอร์ตี้ handle
ด้วย คุณจึงเลือกทำให้แฮนเดิลเป็นอนุกรมได้
const openFileOrFiles = async (multiple = false) => {
// Feature detection. The API needs to be supported
// and the app not run in an iframe.
const supportsFileSystemAccess =
"showOpenFilePicker" in window &&
(() => {
try {
return window.self === window.top;
} catch {
return false;
}
})();
// If the File System Access API is supported…
if (supportsFileSystemAccess) {
let fileOrFiles = undefined;
try {
// Show the file picker, optionally allowing multiple files.
const handles = await showOpenFilePicker({ multiple });
// Only one file is requested.
if (!multiple) {
// Add the `FileSystemFileHandle` as `.handle`.
fileOrFiles = await handles[0].getFile();
fileOrFiles.handle = handles[0];
} else {
fileOrFiles = await Promise.all(
handles.map(async (handle) => {
const file = await handle.getFile();
// Add the `FileSystemFileHandle` as `.handle`.
file.handle = handle;
return file;
})
);
}
} catch (err) {
// Fail silently if the user has simply canceled the dialog.
if (err.name !== 'AbortError') {
console.error(err.name, err.message);
}
}
return fileOrFiles;
}
// Fallback if the File System Access API is not supported.
return new Promise((resolve) => {
// Append a new `<input type="file" multiple? />` and hide it.
const input = document.createElement('input');
input.style.display = 'none';
input.type = 'file';
document.body.append(input);
if (multiple) {
input.multiple = true;
}
// The `change` event fires when the user interacts with the dialog.
input.addEventListener('change', () => {
// Remove the `<input type="file" multiple? />` again from the DOM.
input.remove();
// If no files were selected, return.
if (!input.files) {
return;
}
// Return all files or just one file.
resolve(multiple ? Array.from(input.files) : input.files[0]);
});
// Show the picker.
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 one or multiple files</title>
</head>
<body>
<h1>How to open one or multiple files</h1>
<button type="button">Open file</button>
<button class="multiple" type="button">Open files</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;
}
button {
margin: 1rem;
}
JS
const button = document.querySelector('button');
const buttonMultiple = document.querySelector('button.multiple');
const pre = document.querySelector('pre');
const openFileOrFiles = async (multiple = false) => {
// Feature detection. The API needs to be supported
// and the app not run in an iframe.
const supportsFileSystemAccess =
"showOpenFilePicker" in window &&
(() => {
try {
return window.self === window.top;
} catch {
return false;
}
})();
// If the File System Access API is supported…
if (supportsFileSystemAccess) {
let fileOrFiles = undefined;
try {
// Show the file picker, optionally allowing multiple files.
fileOrFiles = await showOpenFilePicker({ multiple });
if (!multiple) {
// Only one file is requested.
fileOrFiles = fileOrFiles[0];
}
} catch (err) {
// Fail silently if the user has simply canceled the dialog.
if (err.name !== 'AbortError') {
console.error(err.name, err.message);
}
}
return fileOrFiles;
}
// Fallback if the File System Access API is not supported.
return new Promise((resolve) => {
// Append a new `` and hide it.
const input = document.createElement('input');
input.style.display = 'none';
input.type = 'file';
document.body.append(input);
if (multiple) {
input.multiple = true;
}
// The `change` event fires when the user interacts with the dialog.
input.addEventListener('change', () => {
// Remove the `` again from the DOM.
input.remove();
// If no files were selected, return.
if (!input.files) {
return;
}
// Return all files or just one file.
resolve(multiple ? input.files : input.files[0]);
});
// Show the picker.
if ('showPicker' in HTMLInputElement.prototype) {
input.showPicker();
} else {
input.click();
}
});
};
button.addEventListener('click', async () => {
const file = await openFileOrFiles();
if (!file) {
return;
}
pre.textContent += `${file.name}\n`;
});
buttonMultiple.addEventListener('click', async () => {
const files = await openFileOrFiles(true);
if (!files) {
return;
}
Array.from(files).forEach((file) => (pre.textContent += `${file.name}\n`));
});