קריאת קבצים ב-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 החדש בכל מקום שבו הוא זמין, ומשתמשת בגישות מדור קודם כשה-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"> הוא גם יעד להעברה (drop target), שמאפשר למשתמשים לגרור ולשחרר קבצים באפליקציה. עם זאת, יעד ההעברה הזה קטן וקשה להשתמש בו. במקום זאת, אחרי שמספקים את התכונות הבסיסיות באמצעות רכיב <input type="file">, אפשר לספק משטח גדול מותאם אישית לגרירה ושחרור.

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

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

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

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

הגדרת אזור ההעברה

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