Использование FileReader для чтения файлов в JavaScript

Использование FileReader для чтения файлов в JavaScript
Содержание:
  1. Что такое FileReader?
  2. Чтение файлов с помощью FileReader
  3. Чтение файлов полученных посредством Drag'n'Drop
  4. Комментарии

В этой статье изучим как использовать FileReader для чтения содержимого файлов в браузере. На практике рассмотрим несколько примеров, как с использованием элемента input, так и посредством Drag'n'Drop.

Что такое FileReader?

FileReader – это объект, который мы можем использовать для чтения содержимого файлов в браузере. При этом делает он это действие асинхронно.

При этом в браузере мы не можем напрямую работать с файловой системой операционной системы (как например в Node.js). В основном это всё то, что пользователь выбрал с помощью элемента <input type="file"> или посредством перетаскивания, то есть через механизм Drag'n'Drop.

Например, для того чтобы организовать выбор одного файла с локального устройства нам достаточно всего лишь добавить в HTML-документ следующий код:

HTML
<input type="file">
HTML-страница, на которой находится элемент input, с помощью которого пользователь может выбрать файл

После выбора файла мы можем в коде JavaScript обратиться к нему. Осуществляется это через свойство files этого DOM-элемента. Но перед обращением к свойству мы должны сначала получить сам этот элемент в DOM, например, с помощью метода querySelector:

JavaScript
// получим элемент, используя селектор [type="file"]
const elemInput = document.querySelector('[type="file"]');
// сохраним в переменную files значение свойства files
const files = elemInput.files;

Свойство files – это массивоподобный объект FileList. Узнать количество выбранных файлов в files можно с помощью свойства length.

JavaScript
// сохраним количество элементов в files в переменную countFiles
const countFiles = files.length;

Если мы не выбрали файл, то значение countFiles будет равно 0. Поэтому перед тем, как обратиться к выбранному файлу нужно проверить, а выбрал ли пользователь файл:

JavaScript
if (countFiles) {
  // действия над выбранным файлом
}

Когда мы выбираем один файл, то он всегда находится в files в позиции 0. Таким образом чтобы получить его мы должны использовать индекс 0:

JavaScript
if (countFiles) {
  // присваиваем переменной file ссылку на выбранный файл
  const file = files[0];
}

Таким образом, обращение к файлам в коллекции FileList осуществляется также как к элементам обычного массива. При этом каждый элемент в этой коллекции является файлом и представлен в JavaScript через экземпляр класса File.

Получения файла, выбранного пользователем с помощью элемента input

В этом примере элемент <input type="file"> мы записали без атрибута multiple. В этом случае, мы можем выбрать только один файл.

Заключим весь приведённый код в обработчик события change для <input type="file">:

JavaScript
// при изменении <input type="file">
document.querySelector('[type="file"]').addEventListener('change', (e) => {
  // сохраним в переменную files значение свойства files
  const files = e.target.files;
  // сохраним количество элементов в files в переменную countFiles
  const countFiles = files.length;
  // если количество выбранных файлов больше 0
  if (countFiles) {
    // присваиваем переменной selectedFile ссылку на выбранный файл
    const selectedFile = files[0];
  }
});

Теперь наш код будет выполняться всякий раз при изменении значения <input type="file"> (в данном случае, когда выбраны файлы).

Чтение файлов с помощью FileReader

После того, как мы получили файл он нам доступен для чтения с помощью FileReader.

Чтобы эго прочитать нам необходимо:

1. Создать новый экземпляр класса FileReader:

JavaScript
const reader = new FileReader();

2. Вызвать метод для чтения его содержимого. Это может быть readAsText, readAsDataURL, readAsBinaryString или readAsArrayBuffer. Кстати все эти методы выполняют чтение файла асинхронно.

Например, прочитаем файл посредством метода readAsDataURL:

JavaScript
reader.readAsDataURL(file);

Этот метод возвращает содержимое файла как Data URL, закодированное в формат base64. Это значит, что мы можем вывести его прямо на страницу. Так как мы будем выбирать изображение, то можем указать эту строку в качестве значения атрибута src элементу <img>. Но об этом ниже, сейчас же нам необходимо получить содержимое файла как Data URL.

3. Так как чтение файла reader выполняет асинхронно, то нам нужно дождаться окончание этого процесса. То есть мы не можем получить содержимое файла сразу, нам нужно дождаться завершение его чтения. Для этого можно воспользоваться событием load. Оно возникнет на объекте reader сразу как только он прочитает файл. Результат его чтения доступен через e.target.result:

JavaScript
reader.addEventListener('load', (e) => {
  // e.target.result – содержимое файла
});

4. Теперь наконец-то выведем содержимое файла как Data URL в качестве значения атрибута src тега <img>. Но будем это делать только для изображений. Если пользователь выбрал что-то другое, то будем сообщать ему об этом. В результате у нас получился следующий код:

HTML
<input type="file">
<img src="" alt="">

<script>
  // при изменении <input type="file">
  document.querySelector('[type="file"]').addEventListener('change', (e) => {
    // сохраним в переменную files значение свойства files
    const files = e.target.files;
    // сохраним количество элементов в files в переменную countFiles
    const countFiles = files.length;
    // если количество выбранных файлов больше 0
    if (!countFiles) {
      alert('Не выбран файл!');
      return;
    }
    // присваиваем переменной selectedFile ссылку на выбранный файл
    const selectedFile = files[0];
    if (!/^image/.test(selectedFile.type)) {
      alert('Выбранный файл не является изображением!');
      return;
    }
    const reader = new FileReader();
    reader.readAsDataURL(selectedFile);
    reader.addEventListener('load', (e) => {
      const img = document.querySelector('img');
      img.src = e.target.result;
      img.alt = selectedFile.name;
    });
  });
</script>

Для обработки ошибок подпишемся на событие error. Например, выведем с помощью alert предупреждение пользователю, если такое произойдет:

JavaScript
reader.addEventListener('error', () => {
  console.error(`Произошла ошибка при чтении файла: ${selectedFile.name}`);
});

Ну и последнее что сделаем – это выведем для выбранной картинки, её имя и размер. А также немного изменим код, чтобы это осуществлялось в контейнер .selected-file:

HTML
<input type="file" accept="image/*">
<div class="selected-file">
  <img class="selected-file-img" src="placeholder.svg" alt="placeholder">
  <p><b>Имя файла:</b> <span class="selected-file-name">-</span></p>
  <p><b>Размер:</b> <span class="selected-file-size">-</span></p>
</div>

<script>
  // при изменении <input type="file">
  document.querySelector('[type="file"]').addEventListener('change', (e) => {
    const elFileImg = document.querySelector('.selected-file-img');
    const elFileName = document.querySelector('.selected-file-name');
    const elFileSize = document.querySelector('.selected-file-size');
    elFileImg.src = 'placeholder.svg';
    elFileImg.alt = 'placeholder';
    elFileName.textContent = '-';
    elFileSize.textContent = '-';
    // сохраним в переменную files значение свойства files
    const files = e.target.files;
    // сохраним количество элементов в files в переменную countFiles
    const countFiles = files.length;
    // если количество выбранных файлов больше 0
    if (!countFiles) {
      alert('Не выбран файл!');
      return;
    }
    // присваиваем переменной selectedFile ссылку на выбранный файл
    const selectedFile = files[0];
    if (!/^image/.test(selectedFile.type)) {
      alert('Выбранный файл не является изображением!');
      return;
    }
    const reader = new FileReader();
    reader.readAsDataURL(selectedFile);
    reader.addEventListener('error', () => {
      console.error(`Произошла ошибка при чтении файла: ${selectedFile.name}`);
    });
    reader.addEventListener('load', (e) => {
      elFileImg.src = e.target.result;
      elFileImg.alt = selectedFile.name;
      elFileName.textContent = selectedFile.name;
      elFileSize.textContent = `${(selectedFile.size / 1024).toFixed(1)} Кбайт`;
    });
  });
</script>

До выбора файла:

До выбора файла

После выбора файла и его чтения с помощью FileReader:

До выбора файла

Чтение файлов полученных посредством Drag'n'Drop

В HTML мы можем получить файлы ещё посредством их перетаскивания, используя HTML5 Drag and Drop API. Для получения файлов мы разместим на странице, например, следующий контейнер:

HTML
<div class="drop-area"></div>

После этого добавим следующие стили для его оформления:

CSS
.drop-area {
  display: flex;
  min-height: 200px;
  max-width: 480px;
  margin: 100px auto;
  padding: 2rem 1.5rem 1.5rem;
  border-radius: 0.25rem;
  border: 2px dashed #ccc;
  justify-content: center;
  flex-wrap: wrap;
  gap: 2rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23e0e0e0' class='bi bi-card-image' viewBox='0 0 16 16'%3E%3Cpath d='M6.002 5.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z'/%3E%3Cpath d='M1.5 2A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13zm13 1a.5.5 0 0 1 .5.5v6l-3.775-1.947a.5.5 0 0 0-.577.093l-3.71 3.71-2.66-1.772a.5.5 0 0 0-.63.062L1.002 12v.54A.505.505 0 0 1 1 12.5v-9a.5.5 0 0 1 .5-.5h13z'/%3E%3C/svg%3E");
  background-position: center center;
  background-repeat: no-repeat;
  background-size: 50% 50%;
}
Элемент, с помощью которого мы будем принимать перетаскиваемые файлы

Для отслеживания процесса перетаскивания существуют различные события. Нас в данном случае будут интересовать только 4 из них:

JavaScript
// получаем элемент .drop-area
const dropArea = document.querySelector('.drop-area');
// возникает при входе перетаскиваемого файла в область элемента dropArea
dropArea.addEventListener('dragenter', (e) => {
  e.preventDefault();
  // ...
});
// возникает при выходе перетаскиваемого файла из области элемента dropArea
dropArea.addEventListener('dragleave', (e) => {
  e.preventDefault();
  // ...
});
// возникает, когда перетаскиваемый файл находится в области элемента dropArea
dropArea.addEventListener('dragover', (e) => {
  e.preventDefault();
});
// возникает при отпускании перетаскиваемого файла в dropArea
dropArea.addEventListener('drop', (e) => {
  e.preventDefault();
  // ...
});

Для dragenter, dragleave, dragover и drop мы отменим стандартное поведение браузера при их возникновении на dropArea.

С помощью dragenter и dragleave мы будем просто переключать класс drop-area-over HTML-элемента .drop-area и тем самым выделять область .drop-area, когда перетаскиваемый файл будет находиться над ним.

JavaScript
dropArea.addEventListener('dragenter', (e) => {
  e.preventDefault();
  dropArea.classList.add('drop-area-over');
});
dropArea.addEventListener('dragleave', (e) => {
  e.preventDefault();
  dropArea.classList.remove('drop-area-over');
});
Выделение границ элемента при входе перетаскиваемого файла в его область

Самое интересное из этих событий – это же конечно drop. Оно возникнет когда пользователь отпустит файл в данном случае над элементом .drop-area.

В обработчике этого события мы будем делать следующее:

JavaScript
dropArea.addEventListener('drop', (e) => {
  e.preventDefault();
  dropArea.classList.remove('drop-area-over');
  const transferredFiles = e.dataTransfer.files;
  [...transferredFiles].forEach(transferredFile => {
    if (!/^image/.test(transferredFile.type)) {
      console.log('Выбранный файл не является изображением!');
      return;
    }
    const reader = new FileReader();
    reader.readAsDataURL(transferredFile);
    reader.addEventListener('error', () => {
      console.error(`Произошла ошибка при чтении файла: ${transferredFile.name}`);
      return;
    });
    reader.addEventListener('load', (e) => {
      dropArea.insertAdjacentHTML('beforeend', `<div class="drop-area-preview"><img class="drop-area-image" src="${e.target.result}" alt="${transferredFile.name}"><div class="drop-area-name">${transferredFile.name}</div><div class="drop-area-remove"><svg viewBox="0 0 24 24" height="24" width="24"><path d="M0 0h24v24H0z" fill="none"></path><path fill='#f5f5f5' d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path></svg></div></div>`);
    });
  });
});

А именно:

  1. удалять класс drop-area-over и тем самым убирать цветовое выделение границ элемента;
  2. получать перетаскиваемые файлы с помощью объекта DataTransfer, который нам доступен через e.dataTransfer;
  3. перебирать полученные файлы transferredFiles с помощью метода forEach;
  4. в forEach выполнять чтение файлов, которые являются изображениями, с помощью FileReader, а затем выводить их с помощью метода insertAdjacentHTML на страницу.

Добавим в код ещё некоторые улучшения. В результате:

JavaScript
<style>
  body {
    font-family: consolas;
    line-height: 1.5;
  }

  .drop-area {
    display: flex;
    min-height: 100px;
    max-width: 240px;
    margin: 100px auto;
    padding: 2rem 1.5rem 1.5rem;
    border-radius: 0.25rem;
    border: 2px dashed #ccc;
    justify-content: center;
    flex-wrap: wrap;
    gap: 2rem;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23e0e0e0' class='bi bi-card-image' viewBox='0 0 16 16'%3E%3Cpath d='M6.002 5.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z'/%3E%3Cpath d='M1.5 2A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13zm13 1a.5.5 0 0 1 .5.5v6l-3.775-1.947a.5.5 0 0 0-.577.093l-3.71 3.71-2.66-1.772a.5.5 0 0 0-.63.062L1.002 12v.54A.505.505 0 0 1 1 12.5v-9a.5.5 0 0 1 .5-.5h13z'/%3E%3C/svg%3E");
    background-position: center center;
    background-repeat: no-repeat;
    background-size: 50% 50%;
  }

  .drop-area-over {
    border: 2px dashed #2196f3;
  }

  .drop-area-preview {
    flex: 0 0 45%;
    position: relative;
  }

  img {
    display: block;
    max-width: 100%;
    height: auto;
    border-radius: 0.25rem;
    margin-bottom: 0.5rem;
  }

  .drop-area-name {
    text-align: center;
  }

  .drop-area-remove {
    position: absolute;
    top: 0;
    right: 0;
    transform: translate(50%, -50%);
    display: flex;
    background-color: #424242;
    border-radius: 0.25rem;
    cursor: pointer;
  }

  .drop-area-remove:hover {
    background-color: #616161;
  }
</style>

<div class="drop-area"></div>

<script>
  const dropArea = document.querySelector('.drop-area');
  dropArea.addEventListener('dragenter', (e) => {
    e.preventDefault();
    dropArea.classList.add('drop-area-over');
  });
  dropArea.addEventListener('dragleave', (e) => {
    e.preventDefault();
    dropArea.classList.remove('drop-area-over');
  });
  dropArea.addEventListener('dragover', (e) => {
    e.preventDefault();
  });
  // возникает при отпускании перетаскиваемого файла в dropArea
  dropArea.addEventListener('drop', (e) => {
    e.preventDefault();
    dropArea.classList.remove('drop-area-over');
    const transferredFiles = e.dataTransfer.files;
    [...transferredFiles].forEach(transferredFile => {
      if (!/^image/.test(transferredFile.type)) {
        console.log('Выбранный файл не является изображением!');
        return;
      }
      const reader = new FileReader();
      reader.readAsDataURL(transferredFile);
      reader.addEventListener('error', () => {
        console.error(`Произошла ошибка при чтении файла: ${transferredFile.name}`);
        return;
      });
      reader.addEventListener('load', (e) => {
        dropArea.insertAdjacentHTML('beforeend', `<div class="drop-area-preview"><img class="drop-area-image" src="${e.target.result}" alt="${transferredFile.name}"><div class="drop-area-name">${transferredFile.name}</div><div class="drop-area-remove"><svg viewBox="0 0 24 24" height="24" width="24"><path d="M0 0h24v24H0z" fill="none"></path><path fill='#f5f5f5' d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path></svg></div></div>`);
      });
    });
  });
  document.addEventListener('click', (e) => {
    if (e.target.closest('.drop-area-remove')) {
      e.target.closest('.drop-area-preview').remove();
    }
  });
</script>
Чтение файлов в JavaScript полученных посредством Drag'n'Drop

Комментарии: 4

kosta232
kosta232

Отличная статья! Отдал своим студентам на изучение.

Александр Мальцев
Александр Мальцев

Спасибо за отзыв!

erdj
erdj

Очень хорошие и подробные статьи, как я не читал Вас раньше, большое спасибо!

Александр Мальцев
Александр Мальцев

Спасибо за отзыв! Рад, что вам нравятся статьи.