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

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

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"> הוא גם יעד להעברה (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