Эти вещи о загрузке фронтенд-изображений

JavaScript

Загрузка изображений, упомянутая в этой статье, в основном предназначена для загрузки аватаров. Как мы все знаем, загрузка аватара обычно делится на следующие 4 шага:

Выберите Изображение -> Изображение для предварительного просмотра -> Обрезать изображение -> Загрузить изображение.

Далее подробно описывается конкретная реализация каждого шага.

Выберите изображение

Какой смысл выбирать картинку? не одинinput[type=file], а затем нажмите на него? Это действительно так, но если мы хотим быть более дружелюбными, например, нам нужно отфильтровать файлы, не являющиеся изображениями, или разрешить делать снимки только с камеры и т. д., нам все равно нужно выполнить некоторые простые настройки. .

Давайте сначала посмотрим на простейшую картинку выбора:

<input type="file" />

В это время нажмите на этот вход, дисплей на телефоне iOS выглядит следующим образом:

Опция «Обзор» в нем может просматривать файлы нетипа изображения, что не является тем результатом, который нам нужен, в конце концов, нам нужен только тип изображения. в состоянии пройтиacceptсвойства следующим образом:

  <input type="file" accept="image/*">

Это отфильтрует типы, не являющиеся изображениями. Но типов картинок может быть слишком много, и некоторые из них могут не поддерживаться сервером, поэтому, если вы хотите быть консервативным, разрешайте толькоjpgа такжеpngТипа можно написать так:

<input type="file" accept="image/jpg, image/jpeg, image/png">

или:

<input type="file" accept=".jpg, .jpeg, .png">

ОК, необходимость фильтрации не-изображений выполнена. Но иногда продукт также требует, чтобы фотографии можно было собирать только с камеры, например, необходимо загрузить фотографии на документы, чтобы предотвратить загрузку чужих документов из Интернета.captureСвойства могут пригодиться:

  <input type="file" accept="image/*" capture>

В настоящее время фотографии не могут быть выбраны из файловой системы, а могут быть собраны только с камеры. На данный момент это может показаться идеальным, но проблема все еще существует.Возможно, некоторые нестандартные продукты требуют, чтобы передняя камера была включена по умолчанию для съемки изображений, например, они хотят ваши селфи-фотографии.captureПо умолчанию используется вызов задней камеры. Фронтальная камера включена по умолчанию и может быть настроенаcapture="user",следующим образом:

<input type="file" accept="image/*" capture="user">

Ладно, это все, что касается выбора картинок.Одна вещь, которую следует отметить, это то, что некоторые конфигурации могут иметь некоторые проблемы с совместимостью, поэтому вам нужно протестировать их на разных моделях, чтобы увидеть эффект.

Поговорим о реализации превью изображения.

предварительный просмотр изображения

В древние времена в интерфейсе не было возможности предварительного просмотра изображений. На практике в это время после того, как пользователь выбирает изображение, изображение немедленно загружается на сервер, а затем сервер возвращает URL-адрес удаленного изображения на интерфейсный дисплей. Этот метод немного громоздкий и тратит впустую трафик пользователя, потому что пользователь может быть не уверен, что загрузит, а вы уже загрузили. К счастью, древние времена прошли, и в современных браузерах реализована функция предпросмотра изображений на фронтенде. Существует два широко используемых метода, а именноURL.createObjectURL()а такжеFileReader. Хотя они в настоящее время находятся в спецификации w3cWorking Draftэтапе, но большинство современных браузеров уже хорошо его поддерживают. Ниже описано, как использовать эти два метода.

1. Предварительный просмотр с помощью URL.createObjectURL

URL.createObjectURL()Статический метод создаетDOMString, который содержит представление объекта, заданного в аргументеURL. этоURLжизненный цикл и окно, в котором он был созданdocumentсвязывать. Этот новый объект URL представляет указанныйFileобъект илиBlobобъект. Применение:

objectURL = URL.createObjectURL(object);

Среди них параметр объекта относится к объекту File, объекту Blob или объекту MediaSource, используемому для создания URL-адреса.

за нашихinput[type=file]С точки зрения,input.files[0]Вы можете получить объект File текущего выбранного файла. Пример кода выглядит следующим образом:

  <input id="inputFile" type="file" accept="image/*">
  <img src="" id="previewImage" alt="图片预览">
  <script>
    const $ = document.getElementById.bind(document);
    const $inputFile = $('inputFile');
    const $previewImage = $('previewImage');
    $inputFile.addEventListener('change', function() {
      const file = this.files[0];
      $previewImage.src = file ? URL.createObjectURL(file) : '';
    }, this);
  </script>

Для конкретного использования, пожалуйста, обратитесь к MDNURL.createObjectURL(),

2. Предварительный просмотр с помощью FileReader

Объект FileReader позволяет веб-приложениям асинхронно считывать содержимое файла (или буфера необработанных данных), хранящегося на компьютере пользователя, используя объект File или Blob для указания файла или данных для чтения. Точно так же мы можем такжеinput.files[0]Получить текущее выбранное изображениеFileобъект.

Обратите внимание, что FileReader и FileReader асинхронно читают файлы или данные!

Вот пример предварительного просмотра изображения с помощью FileReader:

<input id="inputFile" type="file" accept="image/*">
  <img src="" id="previewImage" alt="图片预览">
  <script>
    const $ = document.getElementById.bind(document);
    const $inputFile = $('inputFile');
    const $previewImage = $('previewImage');
    $inputFile.addEventListener('change', function() {
      const file = this.files[0];
      const reader = new FileReader();
      reader.addEventListener('load', function() {
        $previewImage.src = reader.result;
      }, false);
      
      if(file) {
        reader.readAsDataURL(file);
      }
    }, false)
  </script>

Вы найдете, что Filereader будет относительно сложным.

больше оFileReaderДля использования, пожалуйста, обратитесь к документации MDNFileReader

Сравнение двух методов

лично я предпочитаю использоватьURL.createObjectURL(). В основном его оригинальный API является кратким, синхронным чтением, и он возвращаетURL,СравниватьFileReaerВозвращаемый base64 более компактен. Совместимость, оба похожи, оба вWDсцена. Для сравнения производительности на хроме было выбрано изображение 2М,URL.createObjectURL()прошедшее время равно 0 иFileReaderЭто занимает около 20 мс. 0 не кажется разумным, хотя этот метод немедленно вернет URL-адрес, но я предполагаю, что контент, указанный этим URL-адресом, еще не был сгенерирован, его следует сгенерировать асинхронно, а затем отобразить. Так что нет хорошего способа сравнить их производительность.

Если вы хотите узнать больше о предварительном просмотре изображений, вы можете прочитать следующие две статьи:

Обрезать изображение

Что касается кадрирования изображений, естественно думать об использованииcanvas, действительно пройтиcanvas, Но если мы будем делать все это сами, может потребоваться много работы, поэтому в целях экономии сил мы можем встать на плечи гигантов. Лучшая библиотека обрезки изображенийcropperjs, библиотека может масштабировать, перемещать и вращать изображение.

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

<input id="inputFile" type="file" accept="image/*">
  <img class="preview-image" id="previewImage" src="" alt="">
  <!-- cropper裁剪框 -->
  <div class="cropper" id="cropper">
    <div class="inner">
      <div class="face-container">
        <img class="cropper-image" id="cropperImage">
      </div>
      <div class="tips">请将面部区域置于人脸框架内</div>
      <div class="toolbar">
        <div class="btn" id="confirm">确认</div>
      </div>
    </div>
  </div>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.js"></script>
 
 <style>
  .preview-image,
  .cropper-image {
    max-width: 100%;
  }

  .cropper {
    display: none;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background: #ccc;
    font-size: 0.27rem;
    text-align: center;
  }

  .inner {
      height: 100%;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }

    .face-container {
      position: relative;
      width: 320px;
      height: 320px;
      margin: 50px  auto;
    }

    .cropper-modal {
      background: url('https://ok.166.net/gameyw-misc/opd/squash/20191028/152551-m37snfsyu1.png') center no-repeat;
      background-size: 100% 100%;
      opacity: 1;
    }

    .cropper-bg {
      background: none;
    }

    .cropper-view-box {
      opacity: 0;
    }

    .tips {
      font-size: 16px;
    }

    .toolbar {
      display: flex;
      justify-content: center;
      margin: 50px 0;
    }

    .btn {
      width: 150px;
      line-height: 40px;
      font-size: 20px;
      text-align: center;
      color: #fff;
      background: #007fff;
    }
  </style>

  <script>
  const $ = document.getElementById.bind(document);
  const $cropper = $('cropper');
  const $inputFile = $('inputFile');
  const $previewImage = $('previewImage');
  const $cropperImage = $('cropperImage');
  const $confirmBtn = $('confirm')
  let cropperInstance = null;

  // 选择图片后,显示图片裁剪框
  $inputFile.addEventListener('change', function() {
    const file = this.files[0];
    if(!file) return;
    $cropperImage.src = URL.createObjectURL(file);
    showCropper();
  }, false);

  // 点击确认按钮,将裁剪好的图片放到 img 标签显示。
  $confirmBtn.addEventListener('click', function() {
    const url = cropperInstance.getCroppedCanvas().toDataURL("image/jpeg", 1.0);
    $cropper.style.display = 'none';
    $previewImage.src = url;
  }, false);


  function showCropper() {
    $cropper.style.display = 'block';
    cropperInstance && cropperInstance.destroy();
    cropperInstance = new Cropper($cropperImage, {
      viewMode: 1,
      aspectRatio: 1,
      autoCropArea: 1,
      dragMode: 'move',
      guides: false,
      highlight: false,
      cropBoxMovable: false,
      cropBoxResizable: false
    });
  }
  </script>

Схема эффекта выглядит следующим образом:

загрузить

Предыдущие операции завершили подготовку к загрузке изображений, включая выбор изображений, предварительный просмотр изображений, редактирование изображений и т. д., после чего вы можете загружать изображения. В приведенном выше примере с использованиемcropperInstance.getCroppedCanvas()метод получения соответствующегоcanvasобъект . имеютcanvasС объектами легче обращаться, потому чтоcanvas.toBlob()метод получения соответствующегоBlobобъект, то мы можем поместить этоBlobобъект добавлен вFromDataВыполняется коммит без обновления. Примерный код выглядит следующим образом:

function uploadFile() {
    cropperInstance.getCroppedCanvas().toBlob(function(blob) {
      const formData = new FormData();
      formData.append('avatar', blob);
      fetch('xxxx', {
        method: 'POST',
        body: formData
      });
    });
  }

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

постскриптум

Что касается введения загрузки изображений, то оно почти закончено. Но раньше я сталкивался со странной проблемой на мобильных телефонах iPhone и Xiaomi: фотографии, сделанные фронтальной камерой, после выбора будут поворачиваться на 90 градусов против часовой стрелки, например, как показано ниже:

Когда фотка была сделана, она явно была сделана прямо, почему превью переворачивается боком? Когда я впервые столкнулся с этой проблемой, я подумал, что это странно. Я проверил позже и узнал, что это связано с тем, что камера будет записывать информацию об ракурсе фотографии при съемке фотографии.Возможно, информация об ракурсе, записанная передней камерой iPhone, немного отличается от других, и собственный фотоальбом iPhone автоматически исправляет при просмотре фотографий угол, а браузер его не исправляет, поэтому происходит такое вращение.

Для решения этой задачи необходимо использоватьEXIFЭта библиотека справляется с этим.

Я только что попробовал и обнаружил, что на моем iPhone сейчас нет этой проблемы. Около полугода назад, когда я делал запрос, селфи-картинка вращалась вот так. Возможно, это было исправлено после обновления системы iOS. это проблема. А мобильного телефона Xiaomi сейчас нет, поэтому воспроизвести его непросто. К счастью, в то время я сохранил картинку, которая будет автоматически поворачиваться. Вы можете скачать это здесь:

OK.166.net/game English-Секретариат…

После того, как изображение загружено, его можно открыть с помощью программы просмотра изображений на компьютере.Однако в браузере после выбора этого изображения используйтеURL.createObjectURL()илиFileReaderДля предварительного просмотра он будет вращаться. Даже прямой импорт тега img повернется на 90 градусов против часовой стрелки, например:

<img src="https://ok.166.net/gameyw-misc/opd/squash/20191028/170829-f5t38i0d9k.png">

Эффект следующий:

Давайте возьмем это изображение в качестве примера, чтобы представить, как использовать EXIF ​​для определения угла изображения. Для подробного использования EXIF ​​вы можете перейти на домашнюю страницу github, чтобы просмотретьGitHub.com/EXIF-just/порочный…

  <img id="exifImage" src="https://ok.166.net/gameyw-misc/opd/squash/20191028/170829-f5t38i0d9k.png" alt="">

  <script src="https://cdnjs.cloudflare.com/ajax/libs/exif-js/2.3.0/exif.js"></script>
  <script>
      const $exifImage = document.getElementById('exifImage');
      $exifImage.onload = function() {
        EXIF.getData($exifImage, function() {
          let allMetaData = EXIF.getAllTags(this);
          console.log(allMetaData.Orientation); // 6
        });
      };
  </script>

Вывод приведенного выше кодаallMetaData.OrientationРезультат 6 , что означает 6? Вы можете обратиться к этой статьеExif Orientation Tag В нем есть форма:

Если вы не понимаете эту форму, обратитесь к этой статье еще раз.JPEG Orientation, есть картинка:

Видно, что информация о камере повернута на 90 градусов против часовой стрелки. Как это исправить? CSS можно использоватьtransfrom: rotate(-90deg)Поверните на 90 градусов по часовой стрелке, чтобы отменить этот угол.

Фактически,CropperJSОн также обнаружит информацию EXIF ​​​​изображения и автоматически исправит угол.Подробнее см.GitHub.com/Фэн Юань Че…Он также упоминается здесь, но он поддерживает только чтение EXIF-информации изображений jpg, а наше изображение — PNG, поэтому оно не поддерживается.

Существует свойство CSS, называемоеimage-orientation, значение которого называетсяfrom-image, заключается в использовании данных EXIF ​​изображения для поворота. К сожалению, в настоящее время это свойство не поддерживается в Chrome. Кому интересно, могут узнать.

Хорошо, давайте сначала напишем это сюда. Если у вас есть какие-либо вопросы, добро пожаловать в общение в области комментариев~

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