Cómo crear un cargador de archivos de arrastrar y soltar con Vanilla JavaScript

 

 

 

  • ¡Registro!
  • Clase magistral de diseño para una interfaz de usuario compleja, con Vitaly Friedman

  • Índice
    1. Eventos de arrastrar y soltar
    2. Configurando nuestro formulario
    3. Agregar la funcionalidad de arrastrar y soltar
    4. Características adicionales
    5. Conclusión
      1. Otras lecturas

    Es difícil diseñar las entradas de selección de archivos de la manera que los desarrolladores desean, por lo que muchos simplemente las ocultan y crean un botón que abre el cuadro de diálogo de selección de archivos. Sin embargo, hoy en día existe una forma aún más sofisticada de manejar la selección de archivos: arrastrar y soltar. En este artículo, Joseph Zimmerman utilizará JavaScript ES2015+ “vainilla” (sin marcos ni bibliotecas) para completar este proyecto, y se supone que tiene conocimientos prácticos de JavaScript en el navegador. Este ejemplo debería ser compatible con todos los navegadores actuales más IE 10 y 11.

     

    Es un hecho conocido que las entradas de selección de archivos son difíciles de diseñar de la manera que los desarrolladores desean, por lo que muchos simplemente las ocultan y crean un botón que abre el cuadro de diálogo de selección de archivos. Sin embargo, hoy en día tenemos una forma aún más sofisticada de manejar la selección de archivos: arrastrar y soltar.

    Técnicamente, esto ya era posible porque la mayoría (si no todas ) las implementaciones de la entrada de selección de archivos le permitían arrastrar archivos sobre él para seleccionarlos, pero esto requiere que realmente muestre el fileelemento. Entonces, usemos las API que nos proporciona el navegador para implementar un selector y cargador de archivos de arrastrar y soltar.

    En este artículo, usaremos JavaScript ES2015+ “vainilla” (sin marcos ni bibliotecas) para completar este proyecto, y se supone que tiene conocimientos prácticos de JavaScript en el navegador. Este ejemplo, aparte de la sintaxis de ES2015+, que puede cambiarse fácilmente a la sintaxis de ES5 o transpilarse con Babel, debería ser compatible con todos los navegadores actuales, además de IE 10 y 11.

     

    Aquí hay un vistazo rápido a lo que harás:

    Eventos de arrastrar y soltar

    Lo primero que debemos discutir son los eventos relacionados con arrastrar y soltar porque son la fuerza impulsora detrás de esta característica. En total, hay ocho eventos que el navegador activa relacionados con arrastrar y soltar: drag, dragend, dragenter, dragexit, dragleave, dragover, dragstarty drop. No los repasaremos todos porque drag, dragend, dragexity dragstartse activan en el elemento que se está arrastrando y, en nuestro caso, arrastraremos archivos desde nuestro sistema de archivos en lugar de elementos DOM, por lo que estos eventos nunca aparecerá.

    Si tienes curiosidad acerca de ellos, puedes leer alguna documentación sobre estos eventos en MDN .

    Como es de esperar, puede registrar controladores de eventos para estos eventos de la misma manera que registra controladores de eventos para la mayoría de los eventos del navegador: a través de addEventListener.

    let dropArea = document.getElementById('drop-area') dropArea.addEventListener('dragenter', handlerFunction, false) dropArea.addEventListener('dragleave', handlerFunction, false) dropArea.addEventListener('dragover', handlerFunction, false) dropArea.addEventListener('drop', handlerFunction, false)

    Aquí hay una pequeña tabla que describe lo que hacen estos eventos, utilizando dropAreael ejemplo de código para que el lenguaje sea más claro:

    Evento ¿Cuándo se despide?
    dragenter El elemento arrastrado se arrastra sobre dropArea, lo que lo convierte en el destino del evento de colocación si el usuario lo suelta allí.
    dragleave El elemento arrastrado se arrastra fuera de dropArea y hacia otro elemento, convirtiéndolo en el destino del evento de colocación.
    dragover Cada pocos cientos de milisegundos, mientras el elemento arrastrado se encuentra sobre dropArea y se mueve.
    drop El usuario suelta el botón del mouse y suelta el elemento arrastrado en dropArea.

    Tenga en cuenta que el elemento arrastrado se arrastra sobre un elemento secundario de dropArea, dragleavese activará dropAreay dragenterse activará en ese elemento secundario porque es el nuevo target. El dropevento se propagará hasta dropArea(a menos que un detector de eventos diferente detenga la propagación antes de llegar allí), por lo que aún se activará dropAreaa pesar de que no sea el evento targetpara el evento.

     

    También tenga en cuenta que para crear interacciones personalizadas de arrastrar y soltar, deberá llamar event.preventDefault()a cada uno de los oyentes para estos eventos. Si no lo hace, el navegador terminará abriendo el archivo que soltó en lugar de enviarlo al dropcontrolador de eventos.

    Configurando nuestro formulario

    Antes de comenzar a agregar la funcionalidad de arrastrar y soltar, necesitaremos un formulario básico con una fileentrada estándar. Técnicamente esto no es necesario, pero es una buena idea proporcionarlo como alternativa en caso de que el usuario tenga un navegador sin soporte para la API de arrastrar y soltar.

    div form pUpload multiple files with the file dialog or by dragging and dropping images onto the dashed region/p input type="file" multiple accept="image/*" onchange="handleFiles(this.files)" label for="fileElem"Select some files/label /form/div

    Estructura bastante simple. Es posible que observe un onchangecontrolador en el archivo input. Veremos eso más tarde. También sería una buena idea agregar actionun botón formy submitpara ayudar a aquellas personas que no tienen JavaScript habilitado. Luego puedes usar JavaScript para deshacerte de ellos y obtener una forma más limpia. En cualquier caso, necesitará un script del lado del servidor para aceptar la carga, ya sea algo desarrollado internamente o si está utilizando un servicio como Cloudinary para hacerlo por usted. Aparte de esas notas, no hay nada especial aquí, así que agreguemos algunos estilos:

    #drop-area { border: 2px dashed #ccc; border-radius: 20px; width: 480px; font-family: sans-serif; margin: 100px auto; padding: 20px;}#drop-area.highlight { border-color: purple;}p { margin-top: 0;}.my-form { margin-bottom: 10px;}#gallery { margin-top: 10px;}#gallery img { width: 150px; margin-bottom: 10px; margin-right: 10px; vertical-align: middle;}.button { display: inline-block; padding: 10px; background: #ccc; cursor: pointer; border-radius: 5px; border: 1px solid #ccc;}.button:hover { background: #ddd;}#fileElem { display: none;}

    Muchos de estos estilos aún no entran en juego, pero está bien. Lo más destacado, por ahora, es que la fileentrada está oculta, pero tiene labelun estilo que parece un botón, por lo que la gente se dará cuenta de que pueden hacer clic en él para abrir el cuadro de diálogo de selección de archivos. También seguimos una convención al delinear el área de colocación con líneas discontinuas.

    Agregar la funcionalidad de arrastrar y soltar

    Ahora llegamos al meollo de la situación: arrastrar y soltar. Incluyamos un script al final de la página, o en un archivo separado, como le apetezca hacerlo. Lo primero que necesitamos en el script es una referencia al área de colocación para que podamos adjuntarle algunos eventos:

     

    let dropArea = document.getElementById('drop-area')

    Ahora agreguemos algunos eventos. Comenzaremos agregando controladores a todos los eventos para evitar comportamientos predeterminados y evitar que los eventos aumenten más de lo necesario:

    ;['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName = { dropArea.addEventListener(eventName, preventDefaults, false)})function preventDefaults (e) { e.preventDefault() e.stopPropagation()}

    Ahora agreguemos un indicador para que el usuario sepa que efectivamente ha arrastrado el elemento sobre el área correcta usando CSS para cambiar el color del borde del área de colocación. Los estilos ya deberían estar ahí debajo del #drop-area.highlightselector, así que usemos JS para agregar y eliminar esa highlightclase cuando sea necesario.

    ;['dragenter', 'dragover'].forEach(eventName = { dropArea.addEventListener(eventName, highlight, false)});['dragleave', 'drop'].forEach(eventName = { dropArea.addEventListener(eventName, unhighlight, false)})function highlight(e) { dropArea.classList.add('highlight')}function unhighlight(e) { dropArea.classList.remove('highlight')}

    Tuvimos que usar ambos dragenter y dragover para resaltar por lo que mencioné antes. Si comienza flotando directamente sobre dropAreauno de sus hijos y luego pasa el cursor sobre uno de sus hijos, dragleavese activará y se eliminará el resaltado. El dragoverevento se activa después de los eventos dragentery dragleave, por lo que el resaltado se volverá a agregar dropAreaantes de que veamos que se elimina.

    También eliminamos el resaltado cuando el elemento arrastrado sale del área designada o cuando suelta el elemento.

    Ahora todo lo que tenemos que hacer es descubrir qué hacer cuando se eliminan algunos archivos:

    dropArea.addEventListener('drop', handleDrop, false)function handleDrop(e) { let dt = e.dataTransfer let files = dt.files handleFiles(files)}

    Esto no nos acerca a la finalización, pero hace dos cosas importantes:

    1. Muestra cómo obtener los datos de los archivos que se eliminaron.
    2. Nos lleva al mismo lugar en el que file inputestaba su onchangecontrolador: esperando handleFiles.

    Tenga en cuenta que filesno es una matriz, sino un archivo FileList. Entonces, cuando implementemos handleFiles, necesitaremos convertirlo en una matriz para poder iterarlo más fácilmente:

    function handleFiles(files) { ([...files]).forEach(uploadFile)}

    Eso fue decepcionante. Entremos uploadFileen lo realmente carnoso. Recetas de cocina tradicionales y comodas

    function uploadFile(file) { let url = 'YOUR URL HERE' let formData = new FormData() formData.append('file', file) fetch(url, { method: 'POST', body: formData }) .then(() = { /* Done. Inform the user */ }) .catch(() = { /* Error. Inform the user */ })}

    Aquí utilizamos FormData, una API de navegador integrada para crear datos de formulario y enviarlos al servidor. Luego usamos la fetchAPI para enviar la imagen al servidor. Asegúrese de cambiar la URL para que funcione con su back-end o servicio, y formData.appendcualquier dato de formulario adicional que pueda necesitar para brindarle al servidor toda la información que necesita. Alternativamente, si desea admitir Internet Explorer, es posible que desee utilizar XMLHttpRequest, lo que significa uploadFileque se vería así:

     

    function uploadFile(file) { var url = 'YOUR URL HERE' var xhr = new XMLHttpRequest() var formData = new FormData() xhr.open('POST', url, true) xhr.addEventListener('readystatechange', function(e) { if (xhr.readyState == 4 xhr.status == 200) { // Done. Inform the user } else if (xhr.readyState == 4 xhr.status != 200) { // Error. Inform the user } }) formData.append('file', file) xhr.send(formData)}

    Dependiendo de cómo esté configurado su servidor, es posible que desee verificar diferentes rangos de statusnúmeros en lugar de solo 200, pero para nuestros propósitos, esto funcionará.

    Características adicionales

    Esa es toda la funcionalidad básica, pero a menudo queremos más funcionalidad. Específicamente, en este tutorial, agregaremos un panel de vista previa que muestra todas las imágenes elegidas al usuario, luego agregaremos una barra de progreso que le permitirá al usuario ver el progreso de las cargas. Entonces, comencemos con la vista previa de imágenes.

    Vista previa de imagen

    Hay un par de formas de hacer esto: puede esperar hasta que se haya cargado la imagen y pedirle al servidor que envíe la URL de la imagen, pero eso significa que debe esperar y las imágenes pueden ser bastante grandes a veces. La alternativa, que exploraremos hoy, es utilizar la API FileReader en los datos del archivo que recibimos del evento. Esto es asincrónico y, alternativamente, podría usar FileReaderSync , pero podríamos estar intentando leer varios archivos grandes seguidos, por lo que esto podría bloquear el hilo durante bastante tiempo y realmente arruinar la experiencia. Entonces, creemos una función y veamos cómo funciona:droppreviewFile

    function previewFile(file) { let reader = new FileReader() reader.readAsDataURL(file) reader.onloadend = function() { let img = document.createElement('img') img.src = reader.result document.getElementById('gallery').appendChild(img) }}

    Aquí creamos un new FileReadery readAsDataURLlo invocamos con el Fileobjeto. Como se mencionó, esto es asincrónico, por lo que debemos agregar un onloadendcontrolador de eventos para obtener el resultado de la lectura. Luego usamos la URL de datos base 64 como srcpara un nuevo elemento de imagen y la agregamos al galleryelemento. Sólo hay dos cosas que deben hacerse para que esto funcione ahora: agregar el galleryelemento y asegurarse de previewFileque realmente se llame.

    Primero, agregue el siguiente HTML justo después del final de la formetiqueta:

    div/div

    Nada especial; es solo un div. Los estilos ya están especificados para él y las imágenes que contiene, por lo que no queda nada por hacer allí. Ahora cambiemos la handleFilesfunción a la siguiente:

     

    function handleFiles(files) { files = [...files] files.forEach(uploadFile) files.forEach(previewFile)}

    Hay algunas formas en que podría haber hecho esto, como la composición o una única devolución de llamada forEachque se ejecutó uploadFiley previewFileen él, pero esto también funciona. Y con eso, cuando sueltas o seleccionas algunas imágenes, deberían aparecer casi instantáneamente debajo del formulario. Lo interesante de esto es que, en ciertas aplicaciones, es posible que en realidad no desees cargar imágenes, sino almacenar sus URL de datos localStorageo en algún otro caché del lado del cliente para que la aplicación pueda acceder a ellas más tarde. Personalmente, no puedo pensar en ningún buen caso de uso para esto, pero estoy dispuesto a apostar que hay algunos.

    Seguimiento del progreso

    Si algo puede tardar un poco, una barra de progreso puede ayudar al usuario a darse cuenta de que realmente se está avanzando y dar una indicación de cuánto tiempo llevará completarlo. Agregar un indicador de progreso es bastante fácil gracias a la progressetiqueta HTML5. Comencemos agregando eso al código HTML esta vez.

    progress max=100 value=0/progress

    Puedes colocarlo justo después labelo entre la formgalería y div, lo que prefieras. De hecho, puedes colocarlo donde quieras dentro de las bodyetiquetas. No se agregaron estilos para este ejemplo, por lo que mostrará la implementación predeterminada del navegador, que es útil. Ahora trabajemos para agregar JavaScript. Primero veremos la implementación usando fetchy luego mostraremos una versión para XMLHttpRequest. Para comenzar, necesitaremos un par de variables nuevas en la parte superior del script:

    let filesDone = 0let filesToDo = 0let progressBar = document.getElementById('progress-bar')

    Al usarlo, fetchsolo podemos determinar cuándo finaliza una carga, por lo que la única información que rastreamos es cuántos archivos se seleccionan para cargar (como filesToDo) y la cantidad de archivos que terminaron de cargarse (como filesDone). También mantenemos una referencia al #progress-barelemento para poder actualizarlo rápidamente. Ahora creemos un par de funciones para gestionar el progreso:

    function initializeProgress(numfiles) { progressBar.value = 0 filesDone = 0 filesToDo = numfiles}function progressDone() { filesDone++ progressBar.value = filesDone / filesToDo * 100}

    Cuando comencemos a cargar, initializeProgressse nos llamará para restablecer la barra de progreso. Luego, con cada carga completa, llamaremos progressDonepara incrementar la cantidad de cargas completadas y actualizar la barra de progreso para mostrar el progreso actual. Así que llamemos a estas funciones actualizando un par de funciones antiguas:

    function handleFiles(files) { files = [...files] initializeProgress(files.length) // - Add this line files.forEach(uploadFile) files.forEach(previewFile)}function uploadFile(file) { let url = 'YOUR URL HERE' let formData = new FormData() formData.append('file', file) fetch(url, { method: 'POST', body: formData }) .then(progressDone) // - Add `progressDone` call here .catch(() = { /* Error. Inform the user */ })}

    Y eso es. Ahora echemos un vistazo a la XMLHttpRequestimplementación. Podríamos simplemente hacer una actualización rápida a uploadFile, pero XMLHttpRequesten realidad nos brinda más funcionalidad que fetch, es decir, podemos agregar un detector de eventos para el progreso de la carga en cada solicitud, lo que periódicamente nos brindará información sobre qué parte de la solicitud ha finalizado. Debido a esto, necesitamos realizar un seguimiento del porcentaje de finalización de cada solicitud en lugar de solo cuántas se realizan. Entonces, comencemos reemplazando las declaraciones de filesDoney filesToDopor lo siguiente:

     

    let uploadProgress = []

    Entonces necesitamos actualizar nuestras funciones también. Cambiaremos progressDoneel nombre updateProgressy los cambiaremos para que sean los siguientes:

    function initializeProgress(numFiles) { progressBar.value = 0 uploadProgress = [] for(let i = numFiles; i 0; i--) { uploadProgress.push(0) }}function updateProgress(fileNumber, percent) { uploadProgress[fileNumber] = percent let total = uploadProgress.reduce((tot, curr) = tot + curr, 0) / uploadProgress.length progressBar.value = total}

    Ahora initializeProgressinicializa una matriz con una longitud igual a numFilesla que se llena con ceros, lo que indica que cada archivo está completo al 0%. Descubrimos updateProgressqué imagen tiene su progreso actualizado y cambiamos el valor en ese índice al proporcionado percent. Luego calculamos el porcentaje de progreso total tomando un promedio de todos los porcentajes y actualizamos la barra de progreso para reflejar el total calculado. Seguimos llamando initializeProgressigual handleFilesque en el fetchejemplo, por lo que ahora todo lo que necesitamos actualizar es uploadFilellamar updateProgress.

    function uploadFile(file, i) { // - Add `i` parameter var url = 'YOUR URL HERE' var xhr = new XMLHttpRequest() var formData = new FormData() xhr.open('POST', url, true) // Add following event listener xhr.upload.addEventListener("progress", function(e) { updateProgress(i, (e.loaded * 100.0 / e.total) || 100) }) xhr.addEventListener('readystatechange', function(e) { if (xhr.readyState == 4 xhr.status == 200) { // Done. Inform the user } else if (xhr.readyState == 4 xhr.status != 200) { // Error. Inform the user } }) formData.append('file', file) xhr.send(formData)}

    Lo primero a tener en cuenta es que agregamos un iparámetro. Este es el índice del archivo en la lista de archivos. No necesitamos actualizar handleFilespara pasar este parámetro porque está usando forEach, que ya proporciona el índice del elemento como segundo parámetro para las devoluciones de llamada. También agregamos el progressdetector de eventos xhr.uploadpara que podamos llamar updateProgresscon el progreso. El objeto de evento (al que se hace referencia een el código) tiene dos datos pertinentes: loadedque contiene la cantidad de bytes que se han cargado hasta el momento y totalque contiene la cantidad de bytes que tiene el archivo en total.

    La || 100pieza está ahí porque a veces, si hay un error, e.loadedserá e.totalcero, lo que significa que el cálculo saldrá como NaN, por lo 100que se usa para informar que el archivo está listo. También podrías usar 0. En cualquier caso, el error aparecerá en el readystatechangecontrolador para que pueda informar al usuario sobre él. Esto es simplemente para evitar que se lancen excepciones al intentar hacer cálculos con NaN.

    Conclusión

    Esa es la pieza final. Ahora tiene una página web donde puede cargar imágenes arrastrando y soltando, obtener una vista previa de las imágenes que se cargan inmediatamente y ver el progreso de la carga en una barra de progreso. Puedes ver la versión final (con XMLHttpRequest) en acción en CodePen , pero ten en cuenta que el servicio al que subo los archivos tiene límites, por lo que si mucha gente lo prueba, puede fallar por un tiempo.

    Otras lecturas

    • SolidStart: una clase diferente de metamarco
    • El desarrollo web se está volviendo demasiado complejo y puede ser culpa nuestra
    • Sube de nivel tus habilidades CSS con el selector :has()
    • Consejos y trucos útiles de DevTools

    (rb, ra, il, mrn)Explora más en

    • Mecanografiado
    • javascript
    • Navegadores





    Tal vez te puede interesar:

    1. ¿Deberían abrirse los enlaces en ventanas nuevas?
    2. 24 excelentes tutoriales de AJAX
    3. 70 técnicas nuevas y útiles de AJAX y JavaScript
    4. Más de 45 excelentes recursos y repositorios de fragmentos de código

    Cómo crear un cargador de archivos de arrastrar y soltar con Vanilla JavaScript

    Cómo crear un cargador de archivos de arrastrar y soltar con Vanilla JavaScript

    Eventos de arrastrar y soltarConfigurando nuestro formularioAgregar la funcionalidad de arrastrar y soltarCaracterísticas adicionalesConclusión¡Registro! C

    programar

    es

    https://aprendeprogramando.es/static/images/programar-como-crear-un-cargador-de-archivos-de-arrastrar-y-soltar-con-vanilla-javascript-926-0.jpg

    2024-12-03

     

    Cómo crear un cargador de archivos de arrastrar y soltar con Vanilla JavaScript
    Cómo crear un cargador de archivos de arrastrar y soltar con Vanilla JavaScript

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Update cookies preferences