קריאת קבצים ב-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);
}