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

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

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

הגדרת תחום השחרור

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