อ่านไฟล์ใน JavaScript

การเลือกและโต้ตอบกับไฟล์ในอุปกรณ์ของผู้ใช้เป็นหนึ่งในฟีเจอร์ที่ใช้กันมากที่สุดของเว็บ ซึ่งช่วยให้ผู้ใช้เลือกไฟล์และอัปโหลดไปยังเซิร์ฟเวอร์ได้ เช่น เมื่อแชร์รูปภาพหรือส่งเอกสารภาษี นอกจากนี้ยังช่วยให้เว็บไซต์อ่านและดัดแปลงข้อมูลได้โดยไม่ต้องโอนข้อมูลผ่านเครือข่าย หน้านี้จะอธิบายวิธีใช้ JavaScript เพื่อโต้ตอบกับไฟล์

File System Access API มีวิธีอ่านและเขียนไฟล์และไดเรกทอรีในระบบในเครื่องของผู้ใช้ ซึ่งพร้อมใช้งานในเบราว์เซอร์ที่พัฒนาบน Chromium ส่วนใหญ่ เช่น Chrome และ Edge ดูข้อมูลเพิ่มเติมเกี่ยวกับ API นี้ได้ที่ 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 เว็บแอปสำหรับการบีบอัดรูปภาพ
Squoosh จะทำให้ทั้งหน้าต่างเป็นจุดวาง

แอปการบีบอัดรูปภาพ 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 Demo

อ่านเนื้อหาของไฟล์

ใช้ 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