การเลือกและโต้ตอบกับไฟล์ในอุปกรณ์ของผู้ใช้นั้นเป็นฟีเจอร์หนึ่งที่มีการใช้กันมากที่สุดในเว็บ เครื่องมือนี้ช่วยให้ผู้ใช้เลือกไฟล์และ อัปโหลดไปยังเซิร์ฟเวอร์ได้ เช่น เมื่อแชร์รูปภาพหรือส่งเอกสารภาษี และยังอนุญาตให้เว็บไซต์อ่านและจัดการได้โดยที่เราไม่ต้องโอนข้อมูลผ่านเครือข่าย หน้านี้จะแนะนำวิธีใช้ JavaScript ในการโต้ตอบกับไฟล์
File System Access API ที่ทันสมัย
File System Access API เป็นวิธีอ่านและเขียนไปยังไฟล์และไดเรกทอรีในระบบภายในของผู้ใช้ พร้อมใช้งานในเบราว์เซอร์ส่วนใหญ่ที่ใช้ Chromium เช่น Chrome และ Edge ดูข้อมูลเพิ่มเติมได้ที่ File System Access API
เนื่องจาก File System Access API เข้ากันไม่ได้กับบางเบราว์เซอร์ เราขอแนะนำให้ใช้ browser-fs-access ซึ่งเป็นไลบรารีตัวช่วยที่ใช้ API ใหม่ในทุกที่ที่มีและกลับไปใช้แนวทางเดิมเมื่อไม่ได้ใช้งาน
ทำงานกับไฟล์ในแบบคลาสสิก
คู่มือนี้แสดงวิธีโต้ตอบกับไฟล์โดยใช้เมธอด JavaScript เดิม
เลือกไฟล์
วิธีการหลักในการเลือกไฟล์มี 2 วิธี ได้แก่ การใช้องค์ประกอบอินพุต HTML และการใช้โซนการลากและวาง
องค์ประกอบอินพุต HTML
วิธีที่ง่ายที่สุดในการให้ผู้ใช้เลือกไฟล์คือการใช้องค์ประกอบ <input type="file">
ซึ่งเบราว์เซอร์หลักทั้งหมดรองรับ เมื่อผู้ใช้คลิก ผู้ใช้จะเลือกไฟล์เดียวหรือหลายไฟล์หากมีการรวมแอตทริบิวต์ multiple
ไว้โดยใช้ UI การเลือกไฟล์ในตัวของระบบปฏิบัติการ เมื่อผู้ใช้เลือกไฟล์เสร็จแล้ว เหตุการณ์ change
ขององค์ประกอบจะเริ่มทำงาน คุณสามารถเข้าถึงรายการไฟล์จาก event.target.files
ซึ่งเป็นออบเจ็กต์ FileList
แต่ละรายการใน FileList
คือออบเจ็กต์ File
<!-- The `multiple` attribute lets users select multiple files. -->
<input type="file" id="file-selector" multiple>
<script>
const fileSelector = document.getElementById('file-selector');
fileSelector.addEventListener('change', (event) => {
const fileList = event.target.files;
console.log(fileList);
});
</script>
ตัวอย่างต่อไปนี้ช่วยให้ผู้ใช้เลือกไฟล์หลายไฟล์โดยใช้ UI การเลือกไฟล์ในตัวของระบบปฏิบัติการ จากนั้นจึงบันทึกไฟล์ที่เลือกแต่ละไฟล์ไปยังคอนโซล
จำกัดประเภทไฟล์ที่ผู้ใช้เลือกได้
ในบางกรณี คุณอาจต้องการจำกัดประเภทไฟล์ที่ผู้ใช้สามารถเลือกได้ ตัวอย่างเช่น แอปตัดต่อรูปภาพควรยอมรับเฉพาะรูปภาพ ไม่ใช่ไฟล์ข้อความ หากต้องการกำหนดข้อจำกัดประเภทไฟล์ ให้เพิ่มแอตทริบิวต์ accept
ลงในองค์ประกอบอินพุตเพื่อระบุประเภทไฟล์ที่ยอมรับ ดังนี้
<input type="file" id="file-selector" accept=".jpg, .jpeg, .png">
ลากและวางที่กำหนดเอง
ในเบราว์เซอร์บางประเภท องค์ประกอบ <input type="file">
ยังเป็นเป้าหมายแบบเลื่อนลงด้วย ที่ช่วยให้ผู้ใช้ลากและวางไฟล์ลงในแอปได้ แต่เป้าหมายการลดลงนี้มีขนาดเล็กและอาจใช้งานยาก หลังจากระบุฟีเจอร์หลักโดยใช้องค์ประกอบ <input type="file">
แล้ว คุณจะใส่พื้นผิวแบบลากและวางที่กำหนดเองขนาดใหญ่ได้
เลือกพื้นที่ที่ต้องการ
พื้นที่ตกกระทบของคุณขึ้นอยู่กับการออกแบบแอปพลิเคชัน คุณอาจต้องการให้เพียงส่วนหนึ่งของหน้าต่างเป็นพื้นผิวตก แต่สามารถใช้ทั้งหน้าต่างได้
แอปบีบอัดรูปภาพ Squoosh ช่วยให้ผู้ใช้ลากรูปภาพไปที่ใดก็ได้ในหน้าต่าง และคลิกเลือกรูปภาพเพื่อเรียกใช้องค์ประกอบ <input type="file">
ไม่ว่าคุณจะเลือกส่วนใดเป็นโซนวางพื้นที่ อย่าลืมตรวจสอบว่าผู้ใช้สามารถลากไฟล์มาไว้บนพื้นผิวนั้นได้
กำหนดโซนการเข้าชม
หากต้องการเปิดใช้องค์ประกอบเป็นโซนแบบลากและวาง ให้สร้าง Listener สำหรับ 2 เหตุการณ์ ได้แก่ dragover
และ drop
เหตุการณ์ dragover
จะอัปเดต UI ของเบราว์เซอร์เพื่อให้เห็นภาพว่าการดำเนินการลากและวางกำลังสร้างสำเนาของไฟล์ เหตุการณ์ drop
จะเริ่มทำงานหลังจากที่ผู้ใช้วางไฟล์ลงบนแพลตฟอร์ม คุณจะเข้าถึงรายการไฟล์จาก event.dataTransfer.files
ซึ่งเป็นออบเจ็กต์ FileList
ได้ เช่นเดียวกับองค์ประกอบอินพุต แต่ละรายการใน FileList
จะเป็นออบเจ็กต์ File
const dropArea = document.getElementById('drop-area');
dropArea.addEventListener('dragover', (event) => {
event.stopPropagation();
event.preventDefault();
// Style the drag-and-drop as a "copy file" operation.
event.dataTransfer.dropEffect = 'copy';
});
dropArea.addEventListener('drop', (event) => {
event.stopPropagation();
event.preventDefault();
const fileList = event.dataTransfer.files;
console.log(fileList);
});
event.stopPropagation()
และ event.preventDefault()
หยุดการทำงานเริ่มต้นของเบราว์เซอร์แล้วปล่อยให้โค้ดทำงานแทน หากไม่มี เบราว์เซอร์จะออกจากหน้าเว็บของคุณและเปิดไฟล์ที่ผู้ใช้วางไว้ในหน้าต่างเบราว์เซอร์
สำหรับการสาธิตสด โปรดดูการลากและวางที่กำหนดเอง
แล้วไดเรกทอรีล่ะ
ขออภัย การเข้าถึงไดเรกทอรีโดยใช้ JavaScript ไม่มีวิธีที่ดี
แอตทริบิวต์ webkitdirectory
ในองค์ประกอบ <input type="file">
ช่วยให้ผู้ใช้เลือกไดเรกทอรีหรือไดเรกทอรีได้ ซึ่งรองรับในเบราว์เซอร์หลักๆ ส่วนใหญ่
ยกเว้น Firefox สำหรับ Android และ Safari ใน iOS
หากเปิดใช้การลากและวาง ผู้ใช้อาจพยายามลากไดเรกทอรีไปยังโซนการวาง เมื่อเหตุการณ์การทิ้งเริ่มทำงาน จะมีออบเจ็กต์ File
สำหรับไดเรกทอรี แต่จะไม่ให้สิทธิ์เข้าถึงไฟล์ใดๆ ในไดเรกทอรี
อ่านข้อมูลเมตาของไฟล์
ออบเจ็กต์ File
มีข้อมูลเมตาเกี่ยวกับไฟล์ เบราว์เซอร์ส่วนใหญ่จะมีชื่อไฟล์ ขนาดไฟล์ และประเภท MIME อย่างไรก็ตาม เบราว์เซอร์ต่างๆ อาจให้ข้อมูลเพิ่มเติมหรือแตกต่างกัน ทั้งนี้ขึ้นอยู่กับแพลตฟอร์มนั้นๆ
function getMetadataForFileList(fileList) {
for (const file of fileList) {
// Not supported in Safari for iOS.
const name = file.name ? file.name : 'NOT SUPPORTED';
// Not supported in Firefox for Android or Opera for Android.
const type = file.type ? file.type : 'NOT SUPPORTED';
// Unknown cross-browser support.
const size = file.size ? file.size : 'NOT SUPPORTED';
console.log({file, name, type, size});
}
}
คุณดูการทำงานจริงได้ในการสาธิต input-type-file
อ่านเนื้อหาไฟล์
ใช้ FileReader
เพื่ออ่านเนื้อหาของออบเจ็กต์ File
ในหน่วยความจำ คุณบอกให้ FileReader
อ่านไฟล์เป็นบัฟเฟอร์อาร์เรย์, URL ข้อมูล หรือข้อความได้โดยทำดังนี้
function readImage(file) {
// Check if the file is an image.
if (file.type && !file.type.startsWith('image/')) {
console.log('File is not an image.', file.type, file);
return;
}
const reader = new FileReader();
reader.addEventListener('load', (event) => {
img.src = event.target.result;
});
reader.readAsDataURL(file);
}
ตัวอย่างนี้อ่าน File
ที่ผู้ใช้ให้ไว้ จากนั้นแปลงเป็น URL ข้อมูล และใช้ URL ของข้อมูลดังกล่าวเพื่อแสดงรูปภาพในองค์ประกอบ img
หากต้องการดูวิธียืนยันว่าผู้ใช้ได้เลือกไฟล์ภาพแล้ว ให้ดูการสาธิตจาก read-image-file
ตรวจสอบความคืบหน้าของการอ่านไฟล์
เมื่ออ่านไฟล์ขนาดใหญ่ จะเป็นประโยชน์ในการระบุ UX ที่บอกให้ผู้ใช้ทราบว่าการอ่านมีความคืบหน้าไปไกลเพียงใด ซึ่งทำได้โดยใช้เหตุการณ์ progress
จาก FileReader
เหตุการณ์ progress
มี 2 พร็อพเพอร์ตี้ ได้แก่ loaded
(จำนวนเงินที่อ่าน) และ total
(จำนวนเงินที่อ่าน)
function readFile(file) {
const reader = new FileReader();
reader.addEventListener('load', (event) => {
const result = event.target.result;
// Do something with result
});
reader.addEventListener('progress', (event) => {
if (event.loaded && event.total) {
const percent = (event.loaded / event.total) * 100;
console.log(`Progress: ${Math.round(percent)}`);
}
});
reader.readAsDataURL(file);
}
รูปภาพหลักของ Vincent Botta จาก Unsplash