Vue. Загрузка изображений с предпросмотром - insbor.ru
insbor.ru

insbor.ru

Привет, это я

Читаю, пишу, перечитываю и исправляю.


Что здесь происходит


Предыдущие записи


Vue. Загрузка изображений с предпросмотром

Опубликовано :   |  Кем :   |  Категория :  Vue

Если вы захотите попробовать что-то вроде <inрut type="file" model="image"/> в Vue.js, то получите следующую ошибку: Uncaught InvalidStateError: Failed to set the ‘value’ property on ‘HTMLInputElement’: This input element accepts a filename, which may only be programmatically set to the empty string. И как же тогда отправлять выбранные изображения на сервер?

Поскольку, нельзя программно установить значение файлового поля, то и синхронизировать с ним модель Vue, да и любого другого фреймворка тоже не получится. Зато мы можем отследить событие изменения изображения: <inрut type="file" @change="sync"/>. Назначим обработчиком метод экземпляра Vue:

methods: {
    sync (e) {
        e.preventDefault();
        this.image = e.target.files[0];
    }
}

Как видите, список файлов, загруженных в файловое поле мы можем получить из события его изменения. Так как же всё-таки это потом отправить на сервер? Можно использовать плагин для Vue.js vue resource:

data = new FormData()
data.append('image', this.image)

this.$http.post('user', data).then(
    response => {
        console.log('Success! Response: ', response.body);
    },
    response => {
        // error callback
    }
);

После выполнения предыдущего метода this.image содержит специальный объект File. Остаётся только добавить его к новому экземпляру FormData и отправить по адресу.

Уже неплохо, но осталось разобраться с предпросмотром. Закодированное в base64 содержимое файла можно получить с помощью FileReader. Поскольку он асинхронный, добавляем коллбэк загрузки файла:

methods: {
     /**
      * Select specified {File} image object
      *
      * @param {File} file
      */
     selectImage (file) {
         this.file = file;
         let reader = new FileReader();
         reader.onload = this.onImageLoad;
         reader.readAsDataURL(file);
    },

   /**
    * Gets File image object from specified {Event}
    *
    * @param {Event} e
    */
    sync (e) {
        e.preventDefault();
        this.selectImage(e.target.files[0]);
    },

   /**
    * New image load handler. Emits 'input' event
    *
    * @param {Event} e
    */
    onImageLoad (e) {
        this.content = e.target.result;
        let filename = this.file instanceof File ? this.file.name : '';
        // Dispatch new input event with filename
        this.$emit('input', filename);
        // Dispatch new event with image content
        this.$emit('image-changed', this.content);
    }
}

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

// Computed properties
computed: {
    /**
     * Picture src
     * 
     * @return {String}
     */
    src () {
        if (this.content) {
            return this.content;
        }
        return this.isEmpty ? '' : this.srcPrefix + this.value;
    }
}

Прекрасно! Теперь добавим возможность загружать изображение перетаскиванием файла на поле ввода. Для этого при инициализации компонента Vue нужно добавить к его корневому элементу DOM обработчик события 'drop', который будет получать список файлов в событии и передавать их в метод selectImage().

<template>
    <div ref="mainContainer">
        . . .
    </div>
</template>
methods: {
     /**
      * Drag over component
      *
      * @param {Event} e
      */
     onDrop (e) {
         e.stopPropagation();
         e.preventDefault();
         if (!e.dataTransfer.files || !eventOnElement(e, this.$refs.mainContainer)) {
             return;
         }
         if (!e.dataTransfer.files|| !e.dataTransfer.files[0]) {
             return;
         }
         if (!/^image\//.test(e.dataTransfer.files[0].type)) {
             return;
         }
         this.selectImage(e.dataTransfer.files[0]);
     },
},

// Component mounted event handler
mounted () {
    this.$refs.mainContainer.addEventListener('drop', this.onDrop, true);
}

Функция eventOnElement() определяет координаты события и проверяет, произошло ли оно над указанным элементом:

/**
 * Checking event was thrown in object screen coordinates
 *
 * @param event
 * @param object
 * @return boolean
 */
eventOnElement (event, object) {
    let rect = object.getBoundingClientRect();
    return event.pageX >= rect.left
        && event.pageY >= rect.top
        && event.pageX 

Всё. Осталось только как-то подсвечивать или выделять поле ввода при перетаскивании файла, подсказывая пользователю, что от него ожидается. Можете посмотреть в репозитории, или примере, как это сделал я. Если есть время, то советую собрать что-то своё и поэкспериментировать, например, проверять допустимые типы и размеры изображений, или показывать прогресс загрузки больших файлов. Удачи!

P.S. Спасибо авторам этих двух заметок: losstopschade.de, taha-sh.com. Пусть даже они и не знают русского языка :)