Carga y manipulación de imágenes con React | Programar Plus

La siguiente es una publicación de invitado de Damon Bauer, quien aborda un trabajo de desarrollador web bastante común: ofrecer cargas de imágenes de usuario. Dudaría en llamarlo fácil, pero con la ayuda de algunas herramientas poderosas que hacen gran parte del trabajo pesado, este trabajo se ha vuelto mucho más fácil de lo que solía ser. ¡Damon incluso lo hace por completo en el navegador!

Una cosa común que deben hacer los desarrolladores web es brindar a los usuarios la capacidad de cargar imágenes. Al principio puede parecer trivial, pero hay cosas en las que pensar al crear un componente de carga de imágenes. Estas son solo algunas de las consideraciones:

  • ¿Qué tipos de imágenes permitirás?
  • ¿Qué tamaño deben tener las imágenes? ¿Cómo afectará eso al rendimiento?
  • ¿Qué relación de aspecto deben tener las imágenes?
  • ¿Cómo se moderarán las imágenes? ¿Se captarán imágenes inapropiadas?
  • ¿Dónde se alojarán las imágenes? ¿Cómo se administrará eso?

Las herramientas del lado del servidor, como Paperclip e ImageProcessor, brindan una solución para la mayoría de estos problemas. Desafortunadamente, no existe una herramienta estándar para usar en una aplicación de una sola página (que he encontrado). Te mostraré cómo resolví esto dentro de una aplicación React que no usa un lenguaje del lado del servidor en absoluto.

Aquí hay una pequeña demostración de lo que estaremos construyendo:

Caja de herramientas

Las tres herramientas que utilicé incluyen:

  • react-dropzone para aceptar una imagen de un usuario
  • superagent para transferir la imagen cargada
  • Cloudinary para almacenar y manipular las imágenes

Configuración de Cloudinary

Cloudinary es un servicio basado en la nube donde puede almacenar, manipular, administrar y entregar imágenes. Elegí usar Cloudinary porque tiene un nivel gratuito que incluye todas las funciones que necesito. Necesitará al menos una cuenta gratuita para comenzar.

Supongamos que desea recortar, cambiar el tamaño y agregar un filtro a las imágenes cargadas. Cloudinary tiene el concepto de transformaciones, que se encadenan para modificar las imágenes como sea necesario. Una vez cargada, ocurren las transformaciones, modificando y almacenando la nueva imagen.

En el panel de Cloudinary, vaya a Configuración> Cargar y seleccione “Agregar ajuste preestablecido de carga” en Cargar ajustes preestablecidos.

En la siguiente pantalla, cambie “Modo” a “Sin firmar”. Esto es necesario para que pueda cargar directamente a Cloudinary sin negociar una clave privada utilizando un idioma del lado del servidor.

Agregue cualquier transformación seleccionando “Editar” en la sección “Transformaciones entrantes”. Aquí es donde puede recortar, cambiar el tamaño, cambiar la calidad, rotar, filtrar, etc. Guarde el ajuste preestablecido, ¡y listo! Ahora tiene un lugar para cargar, manipular, almacenar y publicar imágenes para su aplicación. Tome nota del nombre predeterminado, ya que lo usaremos más adelante. Pasemos al código.

Aceptar la entrada del usuario

Para manejar la carga de la imagen, utilicé react-dropzone. Incluye funciones como arrastrar y soltar, restricción de tipo de archivo y carga de varios archivos.

Para comenzar, instale las dependencias. En su línea de comando, ejecute:

npm install react react-dropzone superagent --save

Entonces importa React, react-dropzone, y superagent en su componente. Estoy usando el ES6 import sintaxis:

import React from 'react';
import Dropzone from 'react-dropzone';
import request from 'superagent';

Usaremos superagent mas tarde. Por ahora, en el método de renderizado de su componente, incluya un react-dropzone ejemplo:

export default class ContactForm extends React.Component {

  render() {
    <Dropzone
      multiple={false}
      accept="image/*"
      onDrop={this.onImageDrop.bind(this)}>
      <p>Drop an image or click to select a file to upload.</p>
    </Dropzone>
  }

Readyer Lucas Recoaro escribió diciendo que el siguiente fragmento de Dropzone funciona mejor para él. Parece probable que la sintaxis haya cambiado en una versión más reciente de lib.

<Dropzone
  onDrop={this.onImageDrop.bind(this)}
  accept="image/*"
  multiple={false}>
    {({getRootProps, getInputProps}) => {
      return (
        <div
          {...getRootProps()}
        >
          <input {...getInputProps()} />
          {
          <p>Try dropping some files here, or click to select files to upload.</p>
          }
        </div>
      )
  }}
</Dropzone>

Aquí hay un resumen de lo que está haciendo este componente:

  • multiple={false} permite que solo se cargue una imagen a la vez.
  • accept="image/*" permite cualquier tipo de imagen. Puede ser más explícito para limitar solo ciertos tipos de archivos, p. Ej. accept="image/jpg,image/png".
  • onDrop es un método que se activa cuando se carga una imagen.

Cuando se usa la sintaxis de la clase React ES5 (React.createClass), todos los métodos se “enlazan automáticamente” a la instancia de la clase. El código de esta publicación usa la sintaxis de la clase ES6 (extends React.Component), que no proporciona enlace automático. Por eso usamos .bind(this) en el onDrop apuntalar. (Si no está familiarizado con .bind, Usted puede leer sobre ello aquí.

Manejo de la caída de la imagen

Ahora, configuremos el método para hacer algo cuando se carga una imagen.

Primero, configure un const para dos piezas de información de carga importante:

  1. El ID de ajuste preestablecido de carga (creado automáticamente para usted cuando creó su ajuste preestablecido de carga)
  2. Tu URL de carga de Cloudinary
// import statements

const CLOUDINARY_UPLOAD_PRESET = 'your_upload_preset_id';
const CLOUDINARY_UPLOAD_URL = 'https://api.cloudinary.com/v1_1/your_cloudinary_app_name/upload';

export default class ContactForm extends React.Component {
// render()

A continuación, agregue una entrada al estado inicial del componente (usando this.setState); He llamado a esto uploadedFileCloudinaryUrl. Eventualmente, esto contendrá una URL de imagen cargada creada por Cloudinary. Usaremos este fragmento de estado un poco más tarde.

export default class ContactForm extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      uploadedFileCloudinaryUrl: ''
    };
  }

El react-dropzone La documentación indica que siempre devolverá una matriz de los archivos cargados, por lo que pasaremos esa matriz a la files parámetro de la onImageDrop método. Como solo permitimos una imagen a la vez, sabemos que la imagen siempre estará en la primera posición de la matriz.

Llamar handleImageUpload, pasando la imagenfiles[0]) a este método. Rompí esto en un método separado, siguiendo el principio de responsabilidad única. Esencialmente, este principio le enseña a mantener los métodos compactos y solo hacer una cosa.

export default class ContactForm extends React.Component {

  constructor(props) { ... }

  onImageDrop(files) {
    this.setState({
      uploadedFile: files[0]
    });

    this.handleImageUpload(files[0]);
  }

  render() { ... }

}

Manejo de la carga y transferencia de imágenes

Primer uso superagent para PUBLICAR en Cloudinary usando los dos const que configuramos antes. Utilizando el .field El método nos da la capacidad de adjuntar datos a la solicitud POST. Estos datos contienen toda la información que Cloudinary necesita para manejar la imagen cargada. Llamando .end, se realiza la solicitud y se proporciona una devolución de llamada.

export default class ContactForm extends React.Component {

  constructor(props) { ... }

  onImageDrop(files) { ... }

  handleImageUpload(file) {
    let upload = request.post(CLOUDINARY_UPLOAD_URL)
                        .field('upload_preset', CLOUDINARY_UPLOAD_PRESET)
                        .field('file', file);

    upload.end((err, response) => {
      if (err) {
        console.error(err);
      }

      if (response.body.secure_url !== '') {
        this.setState({
          uploadedFileCloudinaryUrl: response.body.secure_url
        });
      }
    });
  }

  render() { ... }

}

Dentro de la .end devolución de llamada, estoy registrando cualquier error que se devuelva. Probablemente sea mejor decirle al usuario que también se produjo un error.

A continuación, verificamos si la respuesta que recibimos contiene una URL que no sea una cadena vacía. Esto significa que la imagen se cargó y manipuló y Cloudinary generó una URL. Por ejemplo, si un usuario estaba editando su perfil y subió una imagen, podría almacenar la nueva URL de la imagen de Cloudinary en su base de datos.

Con el código que hemos escrito hasta ahora, un usuario puede soltar una imagen y el componente la enviará a Cloudinary y recibe una URL de imagen transformada para que la usemos.

Render, continuó

La última parte del componente es un div que contiene una vista previa de la imagen cargada.

export default class ContactForm extends React.Component {

  constructor(props) { ... }

  onImageDrop(files) { ... }

  handleImageUpload(file) { ... }

  render() {
    <div>
      <div className="FileUpload">
        ...
      </div>

      <div>
        {this.state.uploadedFileCloudinaryUrl === '' ? null :
        <div>
          <p>{this.state.uploadedFile.name}</p>
          <img src={this.state.uploadedFileCloudinaryUrl} />
        </div>}
      </div>
    </div>
  }

Las salidas del operador ternario null (nada) si el uploadedFileCloudinaryUrl el estado es una cadena vacía. Recuerde que, de forma predeterminada, configuramos el componente uploadedFileCloudinaryUrl estado a una cadena vacía; esto significa que cuando se renderiza el componente, este div estará vacío.

Sin embargo, cuando Cloudinary responde con una URL, el estado ya no es una cadena vacía porque actualizamos el estado en handleImageUpload. En este punto, el componente se volverá a renderizar, mostrando el nombre del archivo cargado y una vista previa de la imagen transformada.

Envolver

Esta es solo la base para un componente de carga de imágenes. Hay muchas características adicionales que podría agregar, como:

  • Permitir subir varias imágenes
  • Eliminación de imágenes cargadas
  • Visualización de errores si la carga falla por cualquier motivo
  • Usar la cámara de un dispositivo móvil como fuente de carga

Hasta ahora, esta configuración ha funcionado bien para mis necesidades. Tener que codificar el ajuste preestablecido de carga no es perfecto, pero aún no he experimentado ningún problema con él.

Es de esperar que haya comprendido cómo puede cargar, almacenar y manipular imágenes usando React sin un lenguaje del lado del servidor. Si tiene alguna pregunta o comentario, ¡me encantaría escucharlos! Creé un repositorio donde puedes ver este código en acción.