El campo de carga de archivos que merecemos

Todo fluye, todo cambia, pero solo input[type=file] ya que arruinó los nervios de todos los desarrolladores web novatos, y continúa haciéndolo hasta ahora. Recuerda a ti mismo hace N años, cuando empezaste a entender los conceptos básicos de la creación de sitios web. Joven e inexperto, quedó realmente sorprendido cuando el botón de selección de archivos se negó por completo a cambiar el color de fondo a su melocotón favorito. Fue en ese momento que se encontró por primera vez con este iceberg indestructible llamado "Descarga de archivos", que hasta el día de hoy continúa "ahogando" a los desarrolladores web novatos.

Usando el ejemplo de crear un campo de carga de archivos, le mostraré cómo ocultar la input[type=file] correctamente, establecer el foco en un objeto que no puede tener foco, manejar eventos de arrastrar y soltar y enviar archivos a través de AJAX. Y también te presentaré un par de errores del navegador y formas de solucionarlos. El artículo está escrito para principiantes, pero en algunos puntos puede ser útil y entretenido incluso para desarrolladores experimentados.

Marcado y estilos primarios


Comencemos con el marcado HTML:

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>  ,   </title> <link rel="stylesheet" href="style.css"> <script type="text/javascript" src="jquery-3.3.1.min.js"></script> <script type="text/javascript" src="script.js"></script> </head> <body> <form id="upload-container" method="POST" action="send.php"> <img id="upload-image" src="upload.svg"> <div> <input id="file-input" type="file" name="file" multiple> <label for="file-input"> </label> <span>   </span> </div> </form> </body> </html> 

Quizás el elemento principal al que debe prestar atención es

 <label for="file-input"> </label> 

La especificación HTML no nos permite imponer propiedades visuales directamente en la input[type=file] , pero tenemos una etiqueta que hace clic en el elemento del formulario al que está adjunto. Para nuestra alegría, esta etiqueta no tiene limitaciones en la estilización: podemos hacer lo que queramos con ella.

Surge un plan de acción: estilizamos la etiqueta a nuestro antojo y ocultamos la input[type=file] fuera de la vista. Primero, configure los estilos de página generales:

 body { padding: 0; margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; } #upload-container { display: flex; justify-content: center; align-items: center; flex-direction: column; width: 400px; height: 400px; outline: 2px dashed #5d5d5d; outline-offset: -12px; background-color: #e0f2f7; font-family: 'Segoe UI'; color: #1f3c44; } #upload-container img { width: 40%; margin-bottom: 20px; user-select: none; } 

Ahora diseñe nuestra etiqueta:

 #upload-container label { font-weight: bold; } #upload-container label:hover { cursor: pointer; text-decoration: underline; } 

A qué nos estamos esforzando ( input[type=file] eliminada del marcado):

Por supuesto, puede centrar la etiqueta, agregar un fondo y un borde, obteniendo un botón completo, pero nuestra prioridad es arrastrar y soltar.

Ocultar entrada


Ahora necesitamos ocultar la input[type=file] . Lo primero que llama la atención es la display: none y visibility: hidden propiedades visibility: hidden . Pero esto no es tan simple. En algunos navegadores antiguos, hacer clic en la etiqueta ya no tendrá ningún efecto. Pero eso no es todo. Como sabes, los elementos invisibles no pueden recibir foco, y no importa lo que digan, el foco es importante, ya que para algunas personas esta es la única forma de interactuar con el sitio. Entonces este método no nos conviene. Veamos esto:

 #upload-container div { position: relative; z-index: 10; } #upload-container input[type=file] { width: 0.1px; height: 0.1px; opacity: 0; position: absolute; z-index: -10; } 

Colocamos absolutamente nuestra input[type=file] relación con su bloque padre, la reducimos a 0.1px , la hacemos transparente y establecemos su z-index menos que el del padre, por lo que es, por así decirlo, seguro.

Felicitaciones, logramos lo que queríamos: nuestro campo se ve exactamente como en la imagen anterior.

Ajustar el enfoque


Como nuestra input[type=file] físicamente presente en la página, tiene la capacidad de recibir foco. Es decir, si presionamos la tecla Tab en la página, en algún momento el foco cambiará a input[type=file] . Pero el problema es que no veremos esto: el campo que hemos ocultado se destacará. Sí, si en este momento presionamos Enter , el cuadro de diálogo se abrirá y todo funcionará como debería, pero ¿cómo entendemos que es hora de presionar?

Nuestra tarea es seleccionar una marca de cierta manera en el momento en que el foco está en el campo de carga de archivos. Pero, ¿cómo podemos hacer esto si la etiqueta no puede recibir el foco? Los entendidos de CSS3 pensarán inmediatamente en la pseudoclase :focus , que define los estilos para los elementos en foco, y los selectores + o ~ que seleccionan a los vecinos correctos: elementos ubicados en el mismo nivel de anidamiento, después del elemento seleccionado. Teniendo en cuenta que en nuestra input[type=file] marcado input[type=file] se encuentra directamente en frente de la etiqueta, se produce la siguiente entrada:

 #upload-container input[type=file]:focus + label { /*  */ } 

Pero, de nuevo, no es tan simple. Para comenzar, analicemos cómo debemos seleccionar una etiqueta. Como sabe, todos los navegadores modernos y no tan exclusivos tienen propiedades predeterminadas únicas para los elementos en foco. Básicamente, esta es la propiedad de outline , que crea un trazo alrededor de un elemento que difiere del border en que no cambia el tamaño del elemento y puede alejarse de él. Como regla general, las personas usan solo un navegador, por lo que se acostumbran a sus estándares. Para facilitar que las personas naveguen por nuestro sitio, debemos tratar de ajustar el enfoque para que se vea lo más natural posible para los navegadores modernos más populares. En teoría, usando JavaScript, puede obtener información sobre en qué navegador el usuario abrió el sitio y personalizar los estilos en consecuencia, pero este tema es demasiado complicado y engorroso como parte de un artículo destinado principalmente a principiantes. Intentaremos sobrevivir con un poco de sangre.

En los navegadores basados ​​en el motor WebKet (Google Chrome, Opera, Safari), la propiedad predeterminada para los elementos en foco tiene la forma:

 :focus { outline: -webkit-focus-ring-color auto 5px; } 

Aquí -webkit-focus-ring-color es el -webkit-focus-ring-color trazo de enfoque específico de este motor solamente. Es decir, esta línea funcionará exclusivamente en los navegadores WebKit, y esto es exactamente lo que necesitamos. Especifique esta propiedad para nuestra etiqueta:

 #upload-container input[type=file]:focus + label { outline: -webkit-focus-ring-color auto 5px; } 

Abrimos Google Chrome u Opera, miramos. Todo funciona como debería:

Veamos cómo están las cosas con enfoque en Mozilla Firefox y Microsoft Edge. Para estos navegadores, la propiedad predeterminada es:

 :focus { outline: 1px solid #0078d7; } 

y

 :focus { outline: 1px solid #212121; } 

en consecuencia

Desafortunadamente, el prefijo -moz- no funcionará con la propiedad del outline . Por lo tanto, tendremos que elegir cuál de estas dos propiedades elegimos. Dado que el número de usuarios de Firefox es mucho mayor, es más racional dar preferencia a este navegador en particular. Esto no significa que privemos a los usuarios de Edge y otros navegadores de la oportunidad de ver dónde está el foco ahora, simplemente se verá "diferente" de ellos. Bueno, tienes que hacer sacrificios.

Agregamos el estilo de Mozilla Firefox antes que el estilo de WebKit: primero todos los navegadores aplicarán la primera propiedad, y luego los que puedan (Google Chrome, Opera, Safari, etc.) aplicarán la segunda.

 #upload-container input[type=file]:focus + label { outline: 1px solid #0078d7; outline: -webkit-focus-ring-color auto 5px; } 

Y aquí comienza lo extraño: en Edge todo funciona bien, pero Firefox por alguna razón desconocida se niega a aplicar propiedades a la etiqueta con foco en la input[type=file] . Y el evento de focus sí ocurre, verificado a través de JavaScript. Además, si fuerza el enfoque en el campo de selección de archivos a través de las herramientas del desarrollador, la propiedad se aplicará y aparecerá nuestro trazo. Aparentemente, este es un error del navegador en sí, pero si alguien tiene ideas de por qué sucede esto, escriba en los comentarios.

Bueno, nada, los héroes normales siempre andan por ahí. Como dije anteriormente, el evento de focus ocurre, lo que significa que podemos ajustar las propiedades directamente desde JavaScript. Pero para esto tendremos que cambiar la lógica de nuestro selector:

 #upload-container label.focus { outline: 1px solid #0078d7; outline: -webkit-focus-ring-color auto 5px; } 

Describiremos la clase .focus para nuestra etiqueta y la agregaremos cada vez que input[type=file] reciba el foco y lo eliminaremos cuando pierda.

 $('#file-input').focus(function() { $('label').addClass('focus'); }) .focusout(function() { $('label').removeClass('focus'); }); 

Ahora todo funciona como debería. Felicitaciones, descubrimos el enfoque.

Arrastrar y soltar


El trabajo con Arrastrar y soltar se realiza mediante el seguimiento de eventos especiales del navegador: drag, dragstart, dragend, dragover, dragenter, dragleave, drop . Puede encontrar fácilmente una descripción detallada de cada uno de ellos en Internet. Rastrearemos solo algunos de ellos.

Primero, defina un elemento de arrastrar y soltar:
 var dropZone = $('#upload-container'); 

Luego describimos en CSS una clase especial a la que asignaremos dropZone cuando el cursor que tira del archivo esté directamente sobre él. Esto es necesario para informar visualmente al usuario que el archivo ya se puede liberar.

 #upload-container.dragover { background-color: #fafafa; outline-offset: -17px; } 

Ahora pasemos al archivo JS. Para comenzar, necesitamos deshacer todas las acciones predeterminadas para los eventos de arrastrar y soltar. Por ejemplo, uno de esos eventos es la apertura de un archivo lanzado por un navegador. No necesitamos esto en absoluto, por lo que escribiremos las siguientes líneas:

 dropZone.on('drag dragstart dragend dragover dragenter dragleave drop', function(){ return false; }); 

En jQuery, llamar a la return false equivalente a llamar a dos funciones a la vez: e.preventDefault() y e.stopPropagation() .

Comenzamos a describir nuestro propio controlador de eventos. dragenter de la misma manera que lo hicimos con foco, pero esta vez rastrearemos los eventos de dragenter y dragover para agregar una clase y el evento dragleave para eliminarlo:

 dropZone.on('dragover dragenter', function() { dropZone.addClass('dragover'); }); dropZone.on('dragleave', function(e) { dropZone.removeClass('dragover'); }); 

Y nuevamente nos espera una desagradable sorpresa: cuando te mueves por dropZone con el mouse con el archivo, el campo comienza a parpadear. Esto sucede en los navegadores Microsoft Edge y WebKit. Por cierto, la mayoría de estos navegadores WebKit se están ejecutando actualmente en el motor Blink (apreciamos la ironía, ¿eh?). Pero en Mozilla, nada parpadea. Aparentemente, decidí arreglarlo después de los errores de enfoque.

Y este parpadeo se produce debido al hecho de que cuando pasa el cursor sobre el dropZone , ya sea una imagen o div con un campo de selección de archivo y una etiqueta, por alguna razón se dragleave evento dragleave . Es obvio para nosotros que no estamos abandonando el campo, pero los navegadores, por alguna razón, no lo hacen, y debido a esto eliminan .focus la clase dropZone de dropZone .

Y de nuevo, tenemos que salir de alguna manera. Si el navegador en sí no comprende que no estamos abandonando el campo, tendremos que ayudarlo. Y lo haremos a través de condiciones adicionales: calculamos las coordenadas del mouse con respecto a dropZone , y luego verificamos si el cursor está fuera del bloque. Si se deja, eliminamos el estilo:

 dropZone.on('dragleave', function(e) { let dx = e.pageX - dropZone.offset().left; let dy = e.pageY - dropZone.offset().top; if ((dx < 0) || (dx > dropZone.width()) || (dy < 0) || (dy > dropZone.height())) { dropZone.removeClass('dragover'); }; }); 

Y todo, ¡el problema está resuelto! Así es como se ve nuestro campo con el archivo adentro:


Pasemos a manejar el evento de drop sí. Pero primero, recuerde que, además de arrastrar y soltar, tenemos input[type=file] , y cada uno de estos métodos es independiente en su esencia, pero debe realizar las mismas acciones: cargar archivos. Por lo tanto, propongo crear una función separada universal para ambos métodos, a la cual transferiremos archivos, y ya decidirá qué hacer con ellos. Lo llamaremos sendFiles() , pero lo describiremos un poco más tarde. Para comenzar, maneje el evento drop :

 dropZone.on('drop', function(e) { dropZone.removeClass('dragover'); let files = e.originalEvent.dataTransfer.files; sendFiles(files); }); 

Primero, eliminemos la clase dropZone de dropZone . Luego obtenemos una matriz que contiene los archivos. Si usa jQuery, la ruta será e.originalEvent.dataTransfer.files , si escribe en JS puro, entonces e.dataTransfer.files . Bueno, entonces pasamos la matriz a nuestra función aún no realizada.

Ahora resolveremos el método de carga mediante la input[type=file] :

 $('#file-input').change(function() { let files = this.files; sendFiles(files); }); 

Hacemos un seguimiento del evento de change en el botón de selección de archivo, obtenemos la matriz a través de this.files y la enviamos a la función.

Envío de archivos a través de AJAX


El último paso, la descripción de la función de procesamiento de archivos, es exclusivo de todos. Dependerá directamente del objetivo que persigas. Por ejemplo, mostraré cómo enviar archivos al servidor a través de AJAX.

Supongamos que creamos un campo para subir fotos. No queremos que otra cosa llegue a nuestro servidor, por lo que decidiremos sobre los tipos de archivos: que sean PNG y JPEG. También vale la pena regular el tamaño máximo de una foto que un usuario puede enviar. Limitado a cinco megabytes. Comenzamos a describir nuestra función:

 function sendFiles(files) { let maxFileSize = 5242880; let Data = new FormData(); $(files).each(function(index, file) { if ((file.size <= maxFileSize) && ((file.type == 'image/png') || (file.type == 'image/jpeg'))) { Data.append('images[]', file); } }); }; 

En la variable maxFileSize tamaño máximo de archivo que enviaremos al servidor. FormData() , crearemos un nuevo objeto de la clase FormData , que nos permite generar conjuntos de pares clave-valor. Tal objeto se puede enviar fácilmente a través de AJAX. A continuación, usamos la construcción jQuery .each para la matriz de files , que aplicará la función que establecemos para cada uno de sus elementos. El número del elemento y el elemento en sí se pasarán como argumentos a la función, que procesaremos como index y file respectivamente. En la función en sí, comprobaremos el archivo según nuestros criterios: el tamaño es inferior a cinco megabytes y el tipo es PNG o JPEG. Si el archivo pasa la prueba, agréguelo a nuestro objeto FormData llamando a la función append() . La clave es la cadena 'photos[]' , cuyos corchetes al final indican que es una matriz en la que puede haber varios objetos. El objeto en sí será file .

Ahora todo está listo para enviar archivos a través de AJAX. Agregue las siguientes líneas a nuestra función:

 $.ajax({ url: dropZone.attr('action'), type: dropZone.attr('method'), data: Data, contentType: false, processData: false, success: function(data) { alert('   '); } }); 

Como parámetros url y type indicamos los valores de los atributos de action y method de input[type=file] respectivamente. Para pasar por AJAX seremos un objeto de Data . Los parámetros contentType: false y processData: false son necesarios para que el navegador no traduzca inadvertidamente nuestros archivos a otro formato. En el parámetro de success , especificamos la función que se ejecutará si los archivos se transfieren correctamente al servidor. Su contenido depende de su imaginación, pero me limitaré a una salida modesta de un mensaje sobre una descarga exitosa.

¡Felicitaciones, ahora puede crear su propio campo de carga de archivos! Por supuesto, no posiciono mi método como el único verdadero y correcto. Mi objetivo era mostrar el curso general de resolución de este problema, adecuado principalmente para principiantes. Si crees que en algún lugar podrías hacer algo mejor, ¡escribe en los comentarios, lo discutiremos!

Eso es todo. Gracias por su atencion!

Descargar:

  1. Versión final
  2. Problema de enfoque
  3. Problema parpadeante

Toque:

  1. Versión final
  2. Problema de enfoque
  3. Problema parpadeante

Source: https://habr.com/ru/post/es423035/


All Articles