DevGang
Авторизоваться

Создание компонента React для загрузки изображений

Загрузка и сохранение изображений, созданных пользователями, является очень распространенным вариантом использования в веб-приложениях. Но когда я искал решение, подходящее для моих нужд, я не смог его найти. Все варианты попали в одну из двух категорий: библиотеки с привязкой к поставщику, предоставляемые компаниями по хранению изображений, которые не давали свободы, необходимой мне для моего проекта, или чрезвычайно упрощенные и уродливые варианты, которые не обеспечивали гладкого UX. Я искал.

Теперь вполне возможно, что я просто плохо гуглю, и там есть отличные варианты, однако, будучи предприимчивым, я решил сделать свой собственный с нуля! Итак, вот история о том, как я сделал это простым, но полнофункциональным способом.

Процесс проектирования

Но сначала давайте посмотрим на требования к этому загрузчику изображений:

  • Элегантный дизайн, который можно использовать в любых будущих проектах, которые я создаю (возможность повторного использования - ключ к успеху!).
  • Настраиваемость для адаптации к различным вариантам использования — круглые изображения профиля и изображения с обычным соотношением сторон являются двумя ключевыми вариантами использования.
  • Возможность для пользователя обрезать или редактировать изображения простым способом при загрузке.
  • Результатом должна быть строка base64, которую я могу загрузить в базу данных по своему выбору в виде большого двоичного объекта.
  • Элементы управления на стороне разработчика для предотвращения злонамеренного использования (ограничение размера/типа файла, ограничение объема загрузки и т.д.)

Итак, с учетом этих 5 ключевых требований я решил приступить к строительству!

Начало проекта

Для начала я использовал свой любимый инструмент командной строки для создания нового проекта React, предоставленного Vite:

npm create vite@latest

Это позволяет вам выбирать из нескольких вариантов, но я выбрал вариант react-ts, поскольку он автоматически настраивает поддержку typescript. Я также выбрал их экспериментальный компилятор rust, поскольку он обеспечивает гораздо лучшую производительность, чем традиционный Webpack, особенно при горячей перезагрузке во время разработки.

После этого я загружаю свои единственные другие 2 зависимости:

  1. react-cropper, потрясающий проект FOSS, предоставляющий все, что мне нужно для обрезки изображений, и
  2. storybook, который позволит мне и другим легко протестировать внешний вид моего компонента в браузере на ходу. Хотя технически это не является зависимостью для создания этого загрузчика изображений, он все же необходим для того, чего я хочу достичь.

И все, время кодировать!

Код

Для моего компонента мне нужна функция перетаскивания, а также возможность загрузки по щелчку. Для этого я использовал div с обработчиком onDrop, а внутри него добавил традиционный ввод файла:

<div id="drop-zone" onDrop={() => dropHandler(event)} onDragOver={() => dragOverHandler(event)}>
   <p id="drop-label">Click or drag a file to <i>upload</i>.</p>
   <input id="image-input" type="file" accept=".png,.jpg,.jpeg,.gif" onInput={(e) => {handleFile(e)}} />
   {fileInput && <p id="file-name">{fileName}</p>}
</div>

Как вы можете заметить, input позволяет мне ограничить допустимые типы файлов наиболее распространенными форматами изображений. Но это работает только при клике для загрузки. Для функции dropHandler я делаю все немного по-другому:

const dropHandler = (ev: any) => {
   ev.preventDefault();

   if (ev.dataTransfer.items) {
      [...ev.dataTransfer.items].forEach((item, i) => {
      if (item.kind === "file" && (item.type === "image/png" || item.type === "image/gif" || item.type === "image/jpg" || item.type === "image/jpeg")) {
         const file = item.getAsFile();
         if(props.sizeLimit && file.size > props.sizeLimit)
         {
            setStatusMessage("File is too large.");
         }
         else
         {
            setFileName(file.name);
            getBase64(file);
         }

      }
      else
      {
         setStatusMessage("Invalid file type.");
      }
      });
   }
}

Хотя это и не самый чистый код, он позволяет мне легко настроить, какие типы файлов я буду принимать. Я также проверяю размер файла по необязательному параметру fileSize, который может быть передан родительским элементом.

Последняя важная вещь, которую следует отметить здесь, - это моя функция getBase64, которая преобразует входной файл в строку base64:

const  getBase64 = (file: any) => {
   const reader = new FileReader();
   reader.readAsDataURL(file);
   reader.onload = function () {
      setFileInput(reader.result);
   };
   reader.onerror = function (error) {
      console.log('Error: ', error);
   };

   return reader.result;
}

Здесь я использую FileReader для преобразования файла в строку и сохранения результата в моем состоянии ввода файла.

Для функции обрезки изображения я уже упоминал, что использовал react-cropper, так как он уже содержит все, что мне нужно. Я могу просто поместить его в элемент dialog и использовать showModal(), как только пользователь загрузит изображение:

<dialog ref={dialogRef} id="editor">
   <div id={props.round ? "round" : "rect"}>
      <Cropper
         src={fileInput}
         style={{height: 500, width: 500}}
         initialAspectRatio={props.aspect}
         aspectRatio={props.aspect}
         guides={false}
         ref={cropperRef}
      />
   </div>
   <div id="editor-button-row">
      <button id="crop-button" onClick={onCrop}>Crop</button>
   </div>
</dialog>

Это дает мне следующий вид, в данном случае с реквизитом "round", переданным для круглой фотографии:

Пользователь может выбрать обрезанную часть изображения, которую он хочет, и react-cropper выполнит задачу вывода строки base64 результирующего изображения. Затем я сохраняю его в свой объект состояния croppedImage и закрываю модальное диалоговое окно:

const onCrop = () => {
   const cropper = cropperRef.current?.cropper;
   setCroppedImage(cropper.getCroppedCanvas().toDataURL());
   dialogRef.current?.close();
};

Последний компонент предназначен для отображения обрезанного изображения пользователю. Это позволяет им проверить внешний вид изображения перед его загрузкой. Если нет, они могут очистить изображение и повторить попытку или отредактировать его для повторной обрезки:

<div id="img-display">
   <div id="clear-button" onClick={() => {clearFileInput()}}>𐌢</div>
   <img id={props.round ? "round" : ""} src={croppedImage} />
   <div id="options-row">
      <button id="edit-button" onClick={showEditor}>Edit</button>
      <button id="save-button" onClick={() => saveImage()}>Save</button>
   </div>
</div>

Что дает следующее (опять же, с возможностью выбора круглого изображения):

Единственное, что еще нужно добавить, - это несколько простых вариантов оформления, например, разрешить пользователю выбрать основной цвет, чтобы он мог сочетаться с остальной частью своего приложения. Для этого вы можете ознакомиться с полным репозиторием кода здесь:

https://github.com/Sammortinger/React-ImageUpload

В нашем блоге можно изучить возможность загрузки, обработки и конвертация изображения в React.

Источник:

#React
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться