קריאת קבצים ב-JavaScript

אחת מהתכונות הכי נפוצות באינטרנט היא האפשרות לבחור קבצים במכשיר המקומי של המשתמש ולבצע פעולות שונות. היא מאפשרת למשתמשים לבחור קבצים ולהעלות אותם לשרת, למשל כשמשתפים תמונות או שולחים מסמכי מס. הם גם מאפשרים לאתרים לקרוא ולשנות אותם בלי להעביר את הנתונים ברשת. בדף הזה מוסבר איך להשתמש ב-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 מדור קודם.

בחירת קבצים

יש שתי דרכים עיקריות לבחירת קבצים: באמצעות רכיב קלט HTML, ובאמצעות אזור גרירה ושחרור.

רכיב קלט HTML

הדרך הכי קלה למשתמשים לבחור קבצים היא באמצעות הרכיב <input type="file">, שנתמך בכל הדפדפנים העיקריים. כשלוחצים עליו, הוא מאפשר למשתמש לבחור קובץ אחד או כמה קבצים אם כלול המאפיין multiple, באמצעות ממשק המשתמש המובנה לבחירת קבצים במערכת ההפעלה. כשהמשתמש מסיים לבחור קובץ או קבצים, מופעל האירוע 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>

בדוגמה הבאה, משתמש יכול לבחור כמה קבצים באמצעות ממשק המשתמש המובנה לבחירת קבצים במערכת ההפעלה שלו, ואז כל קובץ שנבחר נרשם במסוף.

הגבלת סוגי הקבצים שהמשתמשים יכולים לבחור

במקרים מסוימים, כדאי להגביל את סוגי הקבצים שהמשתמשים יכולים לבחור. לדוגמה, אפליקציה לעריכת תמונות צריכה לקבל רק תמונות, ולא קובצי טקסט. כדי להגדיר הגבלות על סוגי קבצים, מוסיפים מאפיין accept לרכיב הקלט כדי לציין אילו סוגי קבצים יתקבלו:

<input type="file" id="file-selector" accept=".jpg, .jpeg, .png">

גרירה ושחרור בהתאמה אישית

בדפדפנים מסוימים, רכיב <input type="file"> הוא גם אזור שאפשר לגרור אליו קבצים, כך שהמשתמשים יכולים לגרור ולשחרר קבצים באפליקציה. עם זאת, האזור הזה קטן וקשה לשימוש. במקום זאת, אחרי שאתם מספקים תכונות ליבה באמצעות רכיב <input type="file">, אתם יכולים לספק משטח גדול בהתאמה אישית לגרירה ולשחרור.

בחירת אזור השחרור

אזור השחרור תלוי בעיצוב של האפליקציה. יכול להיות שתרצו להשתמש רק בחלק מהחלון כמשטח להזזה, אבל אפשר להשתמש בכל החלון.

צילום מסך של Squoosh, אפליקציית אינטרנט לדחיסת תמונות.
ב-Squoosh, כל החלון הוא אזור השמטה.

אפליקציית הדחיסה של תמונות Squoosh מאפשרת למשתמשים לגרור תמונה לכל מקום בחלון וללחוץ על select an image כדי להפעיל את הרכיב <input type="file">. לא משנה מה תבחרו כאזור השחרור, חשוב לוודא שהמשתמש יבין שאפשר לגרור קבצים אל האזור הזה.

הגדרת אזור השחרור

כדי להגדיר אלמנט כאזור גרירה ושחרור, צריך ליצור פונקציות event listener לשני אירועים: dragover ו-drop. האירוע dragover מעדכן את ממשק המשתמש של הדפדפן כדי לציין באופן חזותי שפעולת הגרירה וההדבקה יוצרת עותק של הקובץ. האירוע 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 לקרוא קובץ כמאגר נתונים מסוג ArrayBuffer, ככתובת 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.

מעקב אחרי ההתקדמות של קריאת קובץ

כשקוראים קבצים גדולים, כדאי לספק חוויית משתמש שתציג למשתמש את התקדמות הקריאה. לשם כך, משתמשים באירוע progress שמופיע ב-FileReader. לאירוע progress יש שתי מאפיינים: 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);
}

תמונה ראשית (Hero) של Vincent Botta מ-Unsplash