File System Access API: ลดความซับซ้อนในการเข้าถึงไฟล์ในเครื่อง

File System Access API ช่วยให้เว็บแอปอ่านหรือบันทึกการเปลี่ยนแปลงลงในไฟล์และโฟลเดอร์ในอุปกรณ์ของผู้ใช้โดยตรงได้

File System Access API คืออะไร

File System Access API ช่วยให้นักพัฒนาแอปสามารถสร้างเว็บแอปที่มีประสิทธิภาพซึ่งโต้ตอบกับไฟล์ในอุปกรณ์เครื่องของผู้ใช้ เช่น IDE, โปรแกรมแก้ไขรูปภาพและวิดีโอ, เครื่องมือแก้ไขข้อความ และอื่นๆ หลังจากผู้ใช้ให้สิทธิ์เข้าถึงเว็บแอปแล้ว API นี้จะอนุญาตให้เว็บแอปอ่านหรือบันทึกการเปลี่ยนแปลงลงในไฟล์และโฟลเดอร์ในอุปกรณ์ของผู้ใช้โดยตรง นอกจากการอ่านและเขียนไฟล์แล้ว File System Access API ยังมีความสามารถในการเปิดไดเรกทอรีและแสดงรายการเนื้อหา

หากคุณเคยอ่านและเขียนไฟล์มาก่อน ข้อมูลส่วนใหญ่ที่เราจะแชร์จะดูคุ้นเคย เราขอแนะนำให้คุณอ่านบทความดังกล่าวอยู่ดี เนื่องจากระบบบางระบบอาจไม่เหมือนกัน

เบราว์เซอร์ Chromium ส่วนใหญ่ใน Windows, macOS, ChromeOS และ Linux รองรับ File System Access API ข้อยกเว้นที่น่าสังเกตคือ Brave ซึ่งขณะนี้ใช้ได้เฉพาะหลัง Flag เรากําลังดำเนินการรองรับ Android ในบริบทของ crbug.com/1011535

การใช้ File System Access API

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

การสนับสนุนเบราว์เซอร์

การรองรับเบราว์เซอร์

  • Chrome: 86
  • Edge: 86
  • Firefox: ไม่รองรับ
  • Safari: ไม่รองรับ

แหล่งที่มา

การตรวจหาองค์ประกอบ

หากต้องการทราบว่าระบบรองรับ File System Access API หรือไม่ ให้ตรวจสอบว่ามีเมธอดเครื่องมือเลือกที่คุณสนใจหรือไม่

if ('showOpenFilePicker' in self) {
  // The `showOpenFilePicker()` method of the File System Access API is supported.
}

ลองใช้

ดู File System Access API ใช้งานได้จริงในตัวอย่างเครื่องมือแก้ไขข้อความ

อ่านไฟล์จากระบบไฟล์ในเครื่อง

กรณีการใช้งานแรกที่ฉันต้องการจัดการคือการขอให้ผู้ใช้เลือกไฟล์ จากนั้นเปิดและอ่านไฟล์นั้นจากดิสก์

ขอให้ผู้ใช้เลือกไฟล์ที่จะอ่าน

จุดแรกเข้าของ File System Access API คือ window.showOpenFilePicker() เมื่อเรียกใช้ ฟีเจอร์นี้จะแสดงกล่องโต้ตอบเครื่องมือเลือกไฟล์ และแจ้งให้ผู้ใช้เลือกไฟล์ หลังจากผู้ใช้เลือกไฟล์แล้ว API จะแสดงผลอาร์เรย์ของตัวแฮนเดิลไฟล์ พารามิเตอร์ options (ไม่บังคับ) ช่วยให้คุณควบคุมลักษณะการทํางานของเครื่องมือเลือกไฟล์ได้ เช่น อนุญาตให้ผู้ใช้เลือกไฟล์ ไดเรกทอรี หรือไฟล์ประเภทต่างๆ ได้หลายรายการ หากไม่ได้ระบุตัวเลือกใดๆ เครื่องมือเลือกไฟล์จะอนุญาตให้ผู้ใช้เลือกไฟล์ได้เพียงไฟล์เดียว ซึ่งเหมาะที่จะใช้เครื่องมือแก้ไขข้อความ

เช่นเดียวกับ API ที่มีประสิทธิภาพอื่นๆ อีกมากมาย การเรียก showOpenFilePicker() จะต้องดำเนินการในบริบทที่ปลอดภัย และต้องเรียกจากภายในท่าทางสัมผัสของผู้ใช้

let fileHandle;
butOpenFile.addEventListener('click', async () => {
  // Destructure the one-element array.
  [fileHandle] = await window.showOpenFilePicker();
  // Do something with the file handle.
});

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

คุณควรเก็บการอ้างอิงแฮนเดิลไฟล์ไว้เพื่อใช้ในภายหลัง คุณต้องใช้สิทธิ์นี้เพื่อบันทึกการเปลี่ยนแปลงในไฟล์หรือดำเนินการอื่นๆ กับไฟล์

อ่านไฟล์จากระบบไฟล์

เมื่อคุณมีแฮนเดิลของไฟล์แล้ว คุณจะได้รับพร็อพเพอร์ตี้ของไฟล์หรือเข้าถึงไฟล์นั้นได้ ในระหว่างนี้ เราจะอ่านเนื้อหาของคำขอ การเรียก handle.getFile() จะแสดงผลออบเจ็กต์ File ซึ่งมี Blob หากต้องการรับข้อมูลจาก Blob ให้เรียกเมธอดใดเมธอดหนึ่ง (slice(), stream(), text() หรือ arrayBuffer())

const file = await fileHandle.getFile();
const contents = await file.text();

ออบเจ็กต์ File ที่ FileSystemFileHandle.getFile() แสดงผลจะอ่านได้ก็ต่อเมื่อไฟล์พื้นฐานบนดิสก์ไม่มีการเปลี่ยนแปลง หากไฟล์ในดิสก์ได้รับการแก้ไข ออบเจ็กต์ File จะอ่านไม่ได้ และคุณจะต้องเรียกใช้ getFile() อีกครั้งเพื่อรับออบเจ็กต์ File ใหม่เพื่ออ่านข้อมูลที่เปลี่ยนแปลง

สรุปข้อมูลทั้งหมด

เมื่อผู้ใช้คลิกปุ่มเปิดเบราว์เซอร์จะแสดงเครื่องมือเลือกไฟล์ เมื่อเลือกไฟล์แล้ว แอปจะอ่านเนื้อหาและใส่ลงใน <textarea>

let fileHandle;
butOpenFile.addEventListener('click', async () => {
  [fileHandle] = await window.showOpenFilePicker();
  const file = await fileHandle.getFile();
  const contents = await file.text();
  textArea.value = contents;
});

เขียนไฟล์ลงในระบบไฟล์ในเครื่อง

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

สร้างไฟล์ใหม่

หากต้องการบันทึกไฟล์ ให้เรียกใช้ showSaveFilePicker() ซึ่งจะแสดงเครื่องมือเลือกไฟล์ในโหมด "บันทึก" ซึ่งช่วยให้ผู้ใช้เลือกไฟล์ใหม่ที่ต้องการใช้บันทึกได้ สำหรับเครื่องมือแก้ไขข้อความ ฉันต้องการให้เพิ่มส่วนขยาย .txt โดยอัตโนมัติด้วย จึงระบุพารามิเตอร์เพิ่มเติม

async function getNewFileHandle() {
  const options = {
    types: [
      {
        description: 'Text Files',
        accept: {
          'text/plain': ['.txt'],
        },
      },
    ],
  };
  const handle = await window.showSaveFilePicker(options);
  return handle;
}

บันทึกการเปลี่ยนแปลงลงในดิสก์

คุณดูโค้ดทั้งหมดสําหรับการบันทึกการเปลี่ยนแปลงในไฟล์ได้ในตัวอย่างเครื่องมือแก้ไขข้อความของฉันใน GitHub การโต้ตอบหลักของระบบไฟล์อยู่ใน fs-helpers.js กระบวนการที่ง่ายที่สุดจะมีลักษณะเป็นโค้ดต่อไปนี้ เราจะอธิบายแต่ละขั้นตอนให้ฟัง

// fileHandle is an instance of FileSystemFileHandle..
async function writeFile(fileHandle, contents) {
  // Create a FileSystemWritableFileStream to write to.
  const writable = await fileHandle.createWritable();
  // Write the contents of the file to the stream.
  await writable.write(contents);
  // Close the file and write the contents to disk.
  await writable.close();
}

การเขียนข้อมูลลงในดิสก์ใช้ออบเจ็กต์ FileSystemWritableFileStream ซึ่งเป็นคลาสย่อยของ WritableStream สร้างสตรีมโดยเรียกใช้ createWritable() ในออบเจ็กต์ตัวแฮนเดิลไฟล์ เมื่อเรียก createWritable() เบราว์เซอร์จะตรวจสอบก่อนว่าผู้ใช้ได้ให้สิทธิ์เขียนไฟล์หรือไม่ หากยังไม่ได้ให้สิทธิ์การเขียน เบราว์เซอร์จะแจ้งให้ผู้ใช้ขอสิทธิ์ หากไม่ได้รับสิทธิ์ createWritable() จะแสดงข้อผิดพลาด DOMException และแอปจะเขียนลงในไฟล์ไม่ได้ ในเครื่องมือแก้ไขข้อความ ระบบจะจัดการออบเจ็กต์ DOMException ในเมธอด saveFile()

เมธอด write() ใช้สตริง ซึ่งเป็นสิ่งที่จำเป็นสำหรับเครื่องมือแก้ไขข้อความ แต่ก็สามารถรับ BufferSource หรือ Blob ได้ด้วย เช่น คุณสามารถส่งผ่านสตรีมไปยัง stdin ได้โดยตรงโดยทำดังนี้

async function writeURLToFile(fileHandle, url) {
  // Create a FileSystemWritableFileStream to write to.
  const writable = await fileHandle.createWritable();
  // Make an HTTP request for the contents.
  const response = await fetch(url);
  // Stream the response into the file.
  await response.body.pipeTo(writable);
  // pipeTo() closes the destination pipe by default, no need to close it.
}

นอกจากนี้ คุณยังกด seek() หรือ truncate() ภายในสตรีมเพื่ออัปเดตไฟล์ที่ตำแหน่งที่ต้องการ หรือปรับขนาดไฟล์ได้ด้วย

การระบุชื่อไฟล์และไดเรกทอรีเริ่มต้นที่แนะนำ

ในหลายกรณี คุณอาจต้องการให้แอปแนะนำชื่อไฟล์หรือตำแหน่งเริ่มต้น เช่น เครื่องมือแก้ไขข้อความอาจต้องการแนะนำชื่อไฟล์เริ่มต้นเป็น Untitled Text.txt แทน Untitled ซึ่งทำได้โดยการส่งพร็อพเพอร์ตี้ suggestedName เป็นส่วนหนึ่งของตัวเลือก showSaveFilePicker

const fileHandle = await self.showSaveFilePicker({
  suggestedName: 'Untitled Text.txt',
  types: [{
    description: 'Text documents',
    accept: {
      'text/plain': ['.txt'],
    },
  }],
});

เช่นเดียวกับไดเรกทอรีเริ่มต้น หากกำลังสร้างเครื่องมือแก้ไขข้อความ คุณอาจต้องเริ่มกล่องโต้ตอบบันทึกไฟล์หรือเปิดไฟล์ในโฟลเดอร์ documents เริ่มต้น ส่วนเครื่องมือแก้ไขรูปภาพอาจต้องเริ่มในโฟลเดอร์ pictures เริ่มต้น คุณสามารถแนะนําไดเรกทอรีเริ่มต้นได้โดยส่งพร็อพเพอร์ตี้ startIn ไปยังเมธอด showSaveFilePicker, showDirectoryPicker() หรือ showOpenFilePicker ดังนี้

const fileHandle = await self.showOpenFilePicker({
  startIn: 'pictures'
});

รายการไดเรกทอรีระบบที่รู้จักมีดังนี้

  • desktop: ไดเรกทอรีเดสก์ท็อปของผู้ใช้ หากมี
  • documents: ไดเรกทอรีที่ระบบมักจะจัดเก็บเอกสารที่ผู้ใช้สร้างขึ้น
  • downloads: ไดเรกทอรีที่มักใช้จัดเก็บไฟล์ที่ดาวน์โหลด
  • music: ไดเรกทอรีที่มักใช้จัดเก็บไฟล์เสียง
  • pictures: ไดเรกทอรีที่มักใช้เก็บรูปภาพและภาพนิ่งอื่นๆ
  • videos: ไดเรกทอรีที่มักใช้จัดเก็บวิดีโอหรือภาพยนตร์

นอกจากไดเรกทอรีของระบบที่รู้จักกันดีแล้ว คุณยังส่งตัวแฮนเดิลไฟล์หรือไดเรกทอรีที่มีอยู่เป็นค่าสำหรับ startIn ได้ด้วย จากนั้นกล่องโต้ตอบจะเปิดขึ้นในไดเรกทอรีเดียวกัน

// Assume `directoryHandle` is a handle to a previously opened directory.
const fileHandle = await self.showOpenFilePicker({
  startIn: directoryHandle
});

การระบุวัตถุประสงค์ของเครื่องมือเลือกไฟล์ต่างๆ

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

const fileHandle1 = await self.showSaveFilePicker({
  id: 'openText',
});

const fileHandle2 = await self.showSaveFilePicker({
  id: 'importImage',
});

การจัดเก็บตัวแฮนเดิลไฟล์หรือตัวแฮนเดิลไดเรกทอรีใน IndexedDB

แฮนเดิลไฟล์และแฮนเดิลไดเรกทอรีเป็นข้อมูลที่จัดเรียงได้ ซึ่งหมายความว่าคุณสามารถบันทึกแฮนเดิลไฟล์หรือไดเรกทอรีลงใน IndexedDB หรือเรียกใช้ postMessage() เพื่อส่งแฮนเดิลเหล่านั้นระหว่างต้นทางระดับบนสุดเดียวกัน

การจัดเก็บแฮนเดิลไฟล์หรือไดเรกทอรีไว้ใน IndexedDB หมายความว่าคุณสามารถจัดเก็บสถานะหรือจดจำไฟล์หรือไดเรกทอรีที่ผู้ใช้กำลังทำงานอยู่ ซึ่งช่วยให้คุณเก็บรายการไฟล์ที่เพิ่งเปิดหรือแก้ไขไว้ได้ เสนอให้เปิดไฟล์ล่าสุดอีกครั้งเมื่อเปิดแอป กู้คืนไดเรกทอรีที่ทำงานอยู่ก่อนหน้านี้ และอื่นๆ ในเครื่องมือแก้ไขข้อความ เราจะจัดเก็บรายการไฟล์ล่าสุด 5 รายการที่ผู้ใช้เปิดไว้ เพื่อให้เข้าถึงไฟล์เหล่านั้นได้อีกครั้ง

ตัวอย่างโค้ดต่อไปนี้แสดงการจัดเก็บและการเรียกข้อมูลแฮนเดิลไฟล์และแฮนเดิลไดเรกทอรี คุณสามารถดูการทํางานจริงได้ใน Glitch (เราใช้ไลบรารี idb-keyval เพื่อความกระชับ)

import { get, set } from 'https://unpkg.com/idb-keyval@5.0.2/dist/esm/index.js';

const pre1 = document.querySelector('pre.file');
const pre2 = document.querySelector('pre.directory');
const button1 = document.querySelector('button.file');
const button2 = document.querySelector('button.directory');

// File handle
button1.addEventListener('click', async () => {
  try {
    const fileHandleOrUndefined = await get('file');
    if (fileHandleOrUndefined) {
      pre1.textContent = `Retrieved file handle "${fileHandleOrUndefined.name}" from IndexedDB.`;
      return;
    }
    const [fileHandle] = await window.showOpenFilePicker();
    await set('file', fileHandle);
    pre1.textContent = `Stored file handle for "${fileHandle.name}" in IndexedDB.`;
  } catch (error) {
    alert(error.name, error.message);
  }
});

// Directory handle
button2.addEventListener('click', async () => {
  try {
    const directoryHandleOrUndefined = await get('directory');
    if (directoryHandleOrUndefined) {
      pre2.textContent = `Retrieved directroy handle "${directoryHandleOrUndefined.name}" from IndexedDB.`;
      return;
    }
    const directoryHandle = await window.showDirectoryPicker();
    await set('directory', directoryHandle);
    pre2.textContent = `Stored directory handle for "${directoryHandle.name}" in IndexedDB.`;
  } catch (error) {
    alert(error.name, error.message);
  }
});

แฮนเดิลและสิทธิ์ของไฟล์หรือไดเรกทอรีที่เก็บไว้

เนื่องจากสิทธิ์จะไม่คงอยู่ระหว่างเซสชันเสมอไป คุณจึงควรตรวจสอบว่าผู้ใช้ได้ให้สิทธิ์แก่ไฟล์หรือไดเรกทอรีโดยใช้ queryPermission() หรือไม่ หากยังไม่ได้ดำเนินการ ให้โทรไปที่ requestPermission() เพื่อขอ (อีกครั้ง) ซึ่งจะใช้ได้กับแฮนเดิลไฟล์และไดเรกทอรีด้วย คุณต้องเรียกใช้ fileOrDirectoryHandle.requestPermission(descriptor) หรือ fileOrDirectoryHandle.queryPermission(descriptor) ตามลำดับ

ในเครื่องมือแก้ไขข้อความ เราได้สร้างเมธอด verifyPermission() ที่ตรวจสอบว่าผู้ใช้ได้ให้สิทธิ์แล้วหรือยัง และส่งคำขอหากจำเป็น

async function verifyPermission(fileHandle, readWrite) {
  const options = {};
  if (readWrite) {
    options.mode = 'readwrite';
  }
  // Check if permission was already granted. If so, return true.
  if ((await fileHandle.queryPermission(options)) === 'granted') {
    return true;
  }
  // Request permission. If the user grants permission, return true.
  if ((await fileHandle.requestPermission(options)) === 'granted') {
    return true;
  }
  // The user didn't grant permission, so return false.
  return false;
}

การขอสิทธิ์เขียนพร้อมกับคำขออ่านทำให้ฉันลดจำนวนข้อความแจ้งสิทธิ์ได้ ผู้ใช้จะเห็นข้อความแจ้งเพียงรายการเดียวเมื่อเปิดไฟล์ และจะให้สิทธิ์ทั้งอ่านและเขียนไฟล์

การเปิดไดเรกทอรีและการแจกแจงเนื้อหา

หากต้องการแจกแจงไฟล์ทั้งหมดในไดเรกทอรี ให้เรียกใช้ showDirectoryPicker() ผู้ใช้เลือกไดเรกทอรีในเครื่องมือเลือก จากนั้นระบบจะแสดง FileSystemDirectoryHandle ซึ่งช่วยให้คุณแจกแจงและเข้าถึงไฟล์ของไดเรกทอรีได้ โดยค่าเริ่มต้น คุณจะมีสิทธิ์อ่านไฟล์ในไดเรกทอรี แต่หากต้องการสิทธิ์เขียน ให้ส่ง { mode: 'readwrite' } ไปยังเมธอด

butDir.addEventListener('click', async () => {
  const dirHandle = await window.showDirectoryPicker();
  for await (const entry of dirHandle.values()) {
    console.log(entry.kind, entry.name);
  }
});

หากต้องการเข้าถึงไฟล์แต่ละไฟล์โดยใช้ getFile() เพิ่มเติม เช่น เพื่อดูขนาดไฟล์แต่ละไฟล์ อย่าใช้ await กับผลลัพธ์แต่ละรายการตามลำดับ แต่ให้ประมวลผลไฟล์ทั้งหมดพร้อมกัน เช่น โดยใช้ Promise.all()

butDir.addEventListener('click', async () => {
  const dirHandle = await window.showDirectoryPicker();
  const promises = [];
  for await (const entry of dirHandle.values()) {
    if (entry.kind !== 'file') {
      continue;
    }
    promises.push(entry.getFile().then((file) => `${file.name} (${file.size})`));
  }
  console.log(await Promise.all(promises));
});

การสร้างหรือเข้าถึงไฟล์และโฟลเดอร์ในไดเรกทอรี

จากไดเรกทอรี คุณสามารถสร้างหรือเข้าถึงไฟล์และโฟลเดอร์ได้โดยใช้เมธอด getFileHandle() หรือ getDirectoryHandle() ตามลำดับ การส่งออบเจ็กต์ options (ไม่บังคับ) ที่มีคีย์เป็น create และค่าบูลีนเป็น true หรือ false จะช่วยให้คุณระบุได้ว่าควรสร้างไฟล์หรือโฟลเดอร์ใหม่หรือไม่หากไม่มีอยู่

// In an existing directory, create a new directory named "My Documents".
const newDirectoryHandle = await existingDirectoryHandle.getDirectoryHandle('My Documents', {
  create: true,
});
// In this new directory, create a file named "My Notes.txt".
const newFileHandle = await newDirectoryHandle.getFileHandle('My Notes.txt', { create: true });

การแก้ไขเส้นทางของรายการในไดเรกทอรี

เมื่อทำงานกับไฟล์หรือโฟลเดอร์ในไดเรกทอรี การแก้ไขเส้นทางของรายการที่เป็นปัญหาอาจมีประโยชน์ ซึ่งทำได้ด้วยเมธอด resolve() ที่เหมาะเจาะ สำหรับการแก้ไข รายการอาจเป็นรายการย่อยโดยตรงหรือโดยอ้อมของไดเรกทอรี

// Resolve the path of the previously created file called "My Notes.txt".
const path = await newDirectoryHandle.resolve(newFileHandle);
// `path` is now ["My Documents", "My Notes.txt"]

การลบไฟล์และโฟลเดอร์ในไดเรกทอรี

หากได้รับสิทธิ์เข้าถึงไดเรกทอรี คุณจะลบไฟล์และโฟลเดอร์ที่อยู่ในไดเรกทอรีได้ด้วยวิธี removeEntry() สําหรับโฟลเดอร์ คุณสามารถลบแบบทําซ้ำและรวมโฟลเดอร์ย่อยและไฟล์ทั้งหมดที่อยู่ในโฟลเดอร์นั้นๆ ได้ด้วย

// Delete a file.
await directoryHandle.removeEntry('Abandoned Projects.txt');
// Recursively delete a folder.
await directoryHandle.removeEntry('Old Stuff', { recursive: true });

การลบไฟล์หรือโฟลเดอร์โดยตรง

หากมีสิทธิ์เข้าถึงแฮนเดิลไฟล์หรือไดเรกทอรี ให้เรียกใช้ remove() ใน FileSystemFileHandle หรือ FileSystemDirectoryHandle เพื่อนำออก

// Delete a file.
await fileHandle.remove();
// Delete a directory.
await directoryHandle.remove();

การเปลี่ยนชื่อและย้ายไฟล์และโฟลเดอร์

คุณเปลี่ยนชื่อหรือย้ายไฟล์และโฟลเดอร์ไปยังตำแหน่งใหม่ได้โดยเรียกใช้ move() ในอินเทอร์เฟซ FileSystemHandle FileSystemHandle มีอินเทอร์เฟซย่อย FileSystemFileHandle และ FileSystemDirectoryHandle เมธอด move() จะใช้พารามิเตอร์ 1 หรือ 2 รายการ รายการแรกอาจเป็นสตริงที่มีชื่อใหม่หรือ FileSystemDirectoryHandle ไปยังโฟลเดอร์ปลายทางก็ได้ ในกรณีหลัง พารามิเตอร์ที่ 2 (ไม่บังคับ) คือสตริงที่มีชื่อใหม่เพื่อให้ย้ายและเปลี่ยนชื่อได้ในขั้นตอนเดียว

// Rename the file.
await file.move('new_name');
// Move the file to a new directory.
await file.move(directory);
// Move the file to a new directory and rename it.
await file.move(directory, 'newer_name');

การผสานรวมแบบลากและวาง

อินเทอร์เฟซการลากและวาง HTML ช่วยให้เว็บแอปพลิเคชันยอมรับไฟล์ที่ลากและวางในหน้าเว็บได้ ในระหว่างการลากและวาง ระบบจะเชื่อมโยงรายการไฟล์และไดเรกทอรีที่ลากมากับรายการไฟล์และรายการไดเรกทอรีตามลำดับ DataTransferItem.getAsFileSystemHandle() เมธอดจะแสดงผลพรอมิสที่มีออบเจ็กต์ FileSystemFileHandle หากรายการที่ลากเป็นไฟล์ และพรอมิสที่มีออบเจ็กต์ FileSystemDirectoryHandle หากรายการที่ลากเป็นไดเรกทอรี ข้อมูลต่อไปนี้แสดงการใช้งาน โปรดทราบว่า DataTransferItem.kind ของอินเทอร์เฟซการลากและวางคือ "file" สำหรับทั้งไฟล์และไดเรกทอรี ส่วน FileSystemHandle.kind ของ File System Access API คือ "file" สำหรับไฟล์และ "directory" สำหรับไดเรกทอรี

elem.addEventListener('dragover', (e) => {
  // Prevent navigation.
  e.preventDefault();
});

elem.addEventListener('drop', async (e) => {
  e.preventDefault();

  const fileHandlesPromises = [...e.dataTransfer.items]
    .filter((item) => item.kind === 'file')
    .map((item) => item.getAsFileSystemHandle());

  for await (const handle of fileHandlesPromises) {
    if (handle.kind === 'directory') {
      console.log(`Directory: ${handle.name}`);
    } else {
      console.log(`File: ${handle.name}`);
    }
  }
});

การเข้าถึงระบบไฟล์ส่วนตัวของต้นทาง

ระบบไฟล์ส่วนตัวของต้นทางคือปลายทางพื้นที่เก็บข้อมูลที่เป็นส่วนตัวสำหรับต้นทางของหน้าเว็บ ดังที่ชื่อบอกไว้ แม้ว่าโดยทั่วไปเบราว์เซอร์จะใช้วิธีนี้โดยเก็บเนื้อหาของระบบไฟล์ส่วนตัวต้นทางนี้ไว้ในดิสก์ แต่ก็ไม่ได้ตั้งใจให้ผู้ใช้เข้าถึงเนื้อหาได้ ในทำนองเดียวกัน ไม่มีความคาดหวังว่าจะมีไฟล์หรือไดเรกทอรีที่มีชื่อตรงกับชื่อของไฟล์ย่อยในระบบไฟล์ส่วนตัวต้นทาง แม้ว่าเบราว์เซอร์อาจทำให้ดูเหมือนว่ามีไฟล์ แต่ภายในนั้นเบราว์เซอร์อาจจัดเก็บ "ไฟล์" เหล่านี้ไว้ในฐานข้อมูลหรือโครงสร้างข้อมูลอื่นๆ เนื่องจากเป็นระบบไฟล์ส่วนตัวต้นทาง สรุปคือ หากคุณใช้ API นี้ อย่าคาดหวังว่าจะพบไฟล์ที่สร้างขึ้นซึ่งจับคู่กันแบบ 1:1 บนฮาร์ดดิสก์ คุณจะดำเนินการได้ตามปกติในระบบไฟล์ส่วนตัวต้นทางเมื่อเข้าถึงรูท FileSystemDirectoryHandle ได้

const root = await navigator.storage.getDirectory();
// Create a new file handle.
const fileHandle = await root.getFileHandle('Untitled.txt', { create: true });
// Create a new directory handle.
const dirHandle = await root.getDirectoryHandle('New Folder', { create: true });
// Recursively remove a directory.
await root.removeEntry('Old Stuff', { recursive: true });

การรองรับเบราว์เซอร์

  • Chrome: 86
  • Edge: 86
  • Firefox: 111
  • Safari: 15.2

แหล่งที่มา

การเข้าถึงไฟล์ที่เพิ่มประสิทธิภาพแล้วจากระบบไฟล์ส่วนตัวต้นทาง

ระบบไฟล์ส่วนตัวของต้นทางให้สิทธิ์เข้าถึงไฟล์ประเภทพิเศษซึ่งเพิ่มประสิทธิภาพให้สูง เช่น โดยการเสนอสิทธิ์เขียนเนื้อหาของไฟล์ในตำแหน่งเดิมและสิทธิ์เขียนเฉพาะ ใน Chromium 102 ขึ้นไป จะมีเมธอดเพิ่มเติมในระบบไฟล์ส่วนตัวของต้นทางเพื่อลดความซับซ้อนในการเข้าถึงไฟล์ นั่นคือ createSyncAccessHandle() (สําหรับการดําเนินการอ่านและเขียนแบบซิงค์) รายการนี้จะแสดงใน FileSystemFileHandle แต่แสดงเฉพาะใน Web Worker

// (Read and write operations are synchronous,
// but obtaining the handle is asynchronous.)
// Synchronous access exclusively in Worker contexts.
const accessHandle = await fileHandle.createSyncAccessHandle();
const writtenBytes = accessHandle.write(buffer);
const readBytes = accessHandle.read(buffer, { at: 1 });

การใช้ Polyfill

คุณไม่สามารถโพลีไฟล์เมธอด File System Access API ได้ทั้งหมด

  • เมธอด showOpenFilePicker() สามารถประมาณได้ด้วยองค์ประกอบ <input type="file">
  • คุณสามารถจําลองเมธอด showSaveFilePicker() ด้วยองค์ประกอบ <a download="file_name"> ได้ แม้ว่าการดำเนินการนี้จะทริกเกอร์การดาวน์โหลดแบบเป็นโปรแกรมและไม่อนุญาตให้เขียนทับไฟล์ที่มีอยู่
  • วิธีการ showDirectoryPicker() สามารถจําลองได้โดยใช้องค์ประกอบ <input type="file" webkitdirectory> ที่ไม่เป็นไปตามมาตรฐาน

เราได้พัฒนาไลบรารีชื่อ browser-fs-access ที่ใช้ File System Access API ทุกครั้งที่เป็นไปได้ และจะใช้ตัวเลือกถัดไปที่ดีที่สุดในกรณีอื่นๆ ทั้งหมด

ความปลอดภัยและสิทธิ์

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

การเปิดไฟล์หรือบันทึกไฟล์ใหม่

เครื่องมือเลือกไฟล์เพื่อเปิดไฟล์สำหรับการอ่าน
เครื่องมือเลือกไฟล์ที่ใช้เปิดไฟล์ที่มีอยู่เพื่ออ่าน

เมื่อเปิดไฟล์ ผู้ใช้จะให้สิทธิ์อ่านไฟล์หรือไดเรกทอรีโดยใช้เครื่องมือเลือกไฟล์ เครื่องมือเลือกไฟล์ที่เปิดอยู่จะแสดงได้โดยใช้ท่าทางสัมผัสของผู้ใช้เมื่อแสดงจากบริบทที่ปลอดภัยเท่านั้น หากผู้ใช้เปลี่ยนใจ ก็สามารถยกเลิกการเลือกในเครื่องมือเลือกไฟล์ได้ และเว็บไซต์จะไม่ได้รับสิทธิ์เข้าถึงข้อมูลใดๆ ซึ่งมีลักษณะการทำงานเหมือนกับองค์ประกอบ <input type="file">

เครื่องมือเลือกไฟล์เพื่อบันทึกไฟล์ลงในดิสก์
เครื่องมือเลือกไฟล์ที่ใช้บันทึกไฟล์ลงในดิสก์

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

โฟลเดอร์ที่ถูกจำกัด

เบราว์เซอร์อาจจำกัดความสามารถในการบันทึกของผู้ใช้ไปยังโฟลเดอร์บางโฟลเดอร์ เช่น โฟลเดอร์หลักของระบบปฏิบัติการ เช่น Windows, โฟลเดอร์ไลบรารีของ macOS เพื่อช่วยปกป้องผู้ใช้และข้อมูลของผู้ใช้ เมื่อเกิดกรณีนี้ขึ้น เบราว์เซอร์จะแสดงข้อความแจ้งและขอให้ผู้ใช้เลือกโฟลเดอร์อื่น

การแก้ไขไฟล์หรือไดเรกทอรีที่มีอยู่

เว็บแอปจะแก้ไขไฟล์ในดิสก์ไม่ได้หากไม่ได้รับสิทธิ์อย่างชัดเจนจากผู้ใช้

ข้อความแจ้งสิทธิ์

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

ข้อความแจ้งสิทธิ์ที่แสดงก่อนบันทึกไฟล์
ข้อความแจ้งที่แสดงต่อผู้ใช้ก่อนที่เบราว์เซอร์จะได้รับสิทธิ์เขียนในไฟล์ที่มีอยู่

หรือเว็บแอปที่แก้ไขไฟล์หลายไฟล์ เช่น IDE ก็ขอสิทธิ์บันทึกการเปลี่ยนแปลงได้เช่นกันเมื่อเปิดไฟล์

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

ความโปร่งใส

ไอคอนแถบอเนกประสงค์
ไอคอนแถบที่อยู่ซึ่งระบุว่าผู้ใช้ได้ให้สิทธิ์เว็บไซต์บันทึกลงในไฟล์ในเครื่องแล้ว

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

การเก็บรักษาสิทธิ์

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

ความคิดเห็น

เราอยากทราบความคิดเห็นของคุณเกี่ยวกับ File System Access API

บอกเราเกี่ยวกับการออกแบบ API

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

พบปัญหาในการติดตั้งใช้งานใช่ไหม

หากพบข้อบกพร่องในการใช้งาน Chrome หรือการติดตั้งใช้งานแตกต่างจากข้อมูลจำเพาะหรือไม่

  • รายงานข้อบกพร่องที่ https://new.crbug.com โปรดระบุรายละเอียดให้มากที่สุดเท่าที่จะเป็นไปได้ รวมถึงวิธีการจำลองข้อบกพร่อง และตั้งค่าคอมโพเนนต์เป็น Blink>Storage>FileSystem Glitch เหมาะอย่างยิ่งสำหรับการแชร์การจำลองอย่างรวดเร็ว

หากมีแผนจะใช้ API

หากวางแผนที่จะใช้ File System Access API ในเว็บไซต์ การสนับสนุนแบบสาธารณะของคุณช่วยให้เราจัดลำดับความสำคัญของฟีเจอร์ต่างๆ ได้ และแสดงให้เห็นว่าการสนับสนุนฟีเจอร์เหล่านี้สำคัญกับผู้ให้บริการเบราว์เซอร์รายอื่นๆ เพียงใด

ลิงก์ที่มีประโยชน์

ขอขอบคุณ

Marijn Kruisselbrink เป็นผู้เขียนข้อกำหนดของ File System Access API