O campo de upload de arquivo que merecemos

Tudo flui, tudo muda, mas apenas input[type=file] , pois estragou os nervos de todos os desenvolvedores da Web iniciantes e continua a fazê-lo até agora. Lembre-se de N anos atrás, quando você estava começando a entender o básico da criação de sites. Jovem e inexperiente, você ficou realmente surpreso quando o botão de seleção de arquivo se recusou completamente a mudar a cor de fundo para o seu pêssego favorito. Foi nesse momento que você encontrou pela primeira vez esse iceberg indestrutível chamado "Downloading Files", que até hoje continua a "afogar" desenvolvedores web novatos.

Usando o exemplo da criação de um campo de upload de arquivo, mostrarei como ocultar a input[type=file] corretamente, definir o foco em um objeto que não pode ter foco, lidar com eventos de arrastar e soltar e enviar arquivos via AJAX. E também apresentarei a você alguns bugs do navegador e maneiras de contorná-los. O artigo foi escrito para iniciantes, mas em alguns momentos pode ser útil e divertido, mesmo para desenvolvedores experientes.

Marcação e estilos principais


Vamos começar com a marcação 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> 

Talvez o principal elemento ao qual você deva prestar atenção seja

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

A especificação HTML não nos permite impor propriedades visuais diretamente na input[type=file] , mas temos uma tag label , clicando na qual causa um clique no elemento do formulário ao qual está anexado. Para nossa alegria, essa marca não tem limitações na estilização: podemos fazer o que quisermos com ela.

Um plano de ação emerge: estilizamos o rótulo como desejamos e ocultamos a input[type=file] fora da vista. Primeiro, configure os estilos gerais de página:

 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; } 

Agora estilize nosso rótulo:

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

O que estamos buscando ( input[type=file] removido da marcação):

Obviamente, você pode centralizar o rótulo, adicionar um plano de fundo e uma borda, obtendo um botão completo, mas nossa prioridade é arrastar e soltar.

Ocultando entrada


Agora precisamos ocultar a input[type=file] . A primeira coisa que impressiona é a display: none e visibility: hidden propriedades visibility: hidden . Mas isso não é tão simples. Em alguns navegadores antigos, clicar no rótulo não terá mais efeito. Mas isso não é tudo. Como você sabe, os elementos invisíveis não podem receber o foco, e não importa o que eles digam, o foco é importante, pois para algumas pessoas essa é a única maneira de interagir com o site. Portanto, este método não nos convém. Vamos contornar isso:

 #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; } 

Posicionamos absolutamente nossa input[type=file] relação ao seu bloco pai, reduzimos para 0.1px , o tornamos transparente e definimos seu z-index menor que o do pai, de modo que é, por assim dizer, com certeza.

Parabéns, conseguimos o que queríamos: nosso campo se parece exatamente com a imagem anterior.

Ajustar o foco


Como nossa input[type=file] fisicamente presente na página, ela pode receber o foco. Ou seja, se pressionarmos a tecla Tab na página, em algum momento o foco mudará para a input[type=file] . Mas o problema é que não veremos isso: o campo que ocultamos se destacará. Sim, se neste momento pressionarmos Enter , a caixa de diálogo será aberta e tudo funcionará como deveria, mas como entendemos que é hora de pressionar?

Nossa tarefa é selecionar uma marca de uma certa maneira no momento em que o foco está no campo de upload de arquivo. Mas como podemos fazer isso se a tag não pode receber foco? Os conhecedores do CSS3 pensarão imediatamente na pseudo-classe :focus , que define estilos para elementos em foco, e + ou ~ seletores que selecionam vizinhos certos: elementos localizados no mesmo nível de aninhamento, após o elemento selecionado. Considerando que em nossa marcação input[type=file] está localizado diretamente em frente à tag label , ocorre a seguinte entrada:

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

Mas, novamente, não é tão simples. Para começar, vamos discutir como devemos selecionar um rótulo. Como você sabe, todos os navegadores modernos, ou não, têm propriedades padrão exclusivas para elementos em foco. Basicamente, essa é a propriedade de outline , que cria um traçado ao redor de um elemento que difere da border , pois não redimensiona o elemento e pode ser movido para longe dele. Como regra, as pessoas usam apenas um navegador e se acostumam a seus padrões. Para facilitar a navegação do site, as pessoas devem tentar ajustar o foco para que pareça o mais natural possível para os navegadores modernos mais populares. Em teoria, usando JavaScript, você pode obter informações sobre em qual navegador o usuário abriu o site e personalizar os estilos de acordo, mas esse tópico é muito complicado e complicado como parte de um artigo destinado principalmente a iniciantes. Vamos tentar sobreviver com um pouco de sangue.

Nos navegadores baseados no mecanismo WebKet (Google Chrome, Opera, Safari), a propriedade padrão para os elementos em foco tem a forma:

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

Aqui -webkit-focus-ring-color é a -webkit-focus-ring-color curso do foco específica apenas para esse mecanismo. Ou seja, esta linha funcionará exclusivamente nos navegadores WebKit, e é exatamente isso que precisamos. Especifique esta propriedade para o nosso rótulo:

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

Abrimos o Google Chrome ou Opera, olhamos. Tudo funciona como deveria:

Vamos ver como as coisas estão focadas no Mozilla Firefox e Microsoft Edge. Para esses navegadores, a propriedade padrão é:

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

e

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

em conformidade.

Infelizmente, o prefixo -moz- não funcionará com a propriedade de outline . Portanto, teremos que escolher qual dessas duas propriedades escolheremos. Como o número de usuários do Firefox é muito maior, é mais racional dar preferência a esse navegador em particular. Isso não significa que privamos os usuários do Edge e outros navegadores da oportunidade de ver onde o foco está agora, apenas parecerá "diferente" deles. Bem, você tem que fazer sacrifícios.

Adicionamos o estilo do Mozilla Firefox antes do estilo do WebKit: primeiro, todos os navegadores aplicarão a primeira propriedade e, em seguida, os que puderem (Google Chrome, Opera, Safari, etc.) aplicarão a segunda.

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

E aqui começa o estranho: no Edge tudo funciona bem, mas o Firefox, por algum motivo desconhecido, se recusa a aplicar propriedades ao rótulo com foco na input[type=file] . E o próprio evento de focus acontece - verificado através do JavaScript. Além disso, se você forçar o foco no campo de seleção de arquivos por meio das ferramentas do desenvolvedor, a propriedade será aplicada e nosso toque aparecerá! Aparentemente, isso é um bug do próprio navegador, mas se alguém tiver idéias para isso, escreva nos comentários.

Bem, nada, heróis normais sempre andam por aí. Como eu disse anteriormente, o evento de focus acontece, o que significa que podemos ajustar as propriedades diretamente do JavaScript. Mas, para isso, teremos que mudar a lógica do nosso seletor:

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

Nós descreveremos a classe .focus para o nosso rótulo e a adicionaremos sempre que input[type=file] receber o foco e removê-lo quando ele perder.

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

Agora tudo funciona como deveria. Parabéns, descobrimos o foco.

Arrastar e soltar


O trabalho com Arrastar e Soltar é realizado rastreando eventos especiais do navegador: drag, dragstart, dragend, dragover, dragenter, dragleave, drop . Você pode encontrar facilmente uma descrição detalhada de cada um deles na Internet. Vamos rastrear apenas alguns deles.

Primeiro, defina um elemento de arrastar e soltar:
 var dropZone = $('#upload-container'); 

Em seguida, descrevemos no CSS uma classe especial que atribuiremos ao dropZone quando o cursor que estiver puxando o arquivo estiver diretamente acima dele. Isso é necessário para informar visualmente o usuário que o arquivo já pode ser liberado.

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

Agora vamos para o arquivo JS. Para começar, precisamos desfazer todas as ações padrão dos eventos de Arrastar e Soltar. Por exemplo, um desses eventos é a abertura de um arquivo lançado por um navegador. Como não precisamos disso, escreveremos as seguintes linhas:

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

No jQuery, chamar a return false equivalente a chamar duas funções ao mesmo tempo: e.preventDefault() e e.stopPropagation() .

Começamos a descrever nosso próprio manipulador de eventos. Agiremos da mesma maneira que fizemos com o foco, mas desta vez rastrearemos os eventos de dragenter e dragover para adicionar uma classe e o evento dragleave para removê-lo:

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

E, novamente, uma surpresa desagradável nos espera: quando você move o dropZone com o mouse com o arquivo, o campo começa a piscar. Isso acontece nos navegadores Microsoft Edge e WebKit. A propósito, a maioria desses navegadores WebKit está atualmente em execução no mecanismo Blink (apreciada a ironia, hein?). Mas no Mozilla, nada pisca. Aparentemente, eu decidi corrigi-lo após os bugs do foco.

E essa oscilação ocorre devido ao fato de que quando você passa o mouse sobre o dropZone , seja uma imagem ou uma div com um campo de seleção de arquivo e um rótulo, por algum motivo o evento dragleave . É óbvio para nós que não estamos deixando o campo, mas os navegadores, por algum motivo, não o fazem, e por isso eles .focus removem a classe dropZone do dropZone .

E, novamente, temos que sair de alguma forma. Se o próprio navegador não entender que não estamos deixando o campo, teremos que ajudá-lo. E faremos isso através de condições adicionais: calculamos as coordenadas do mouse em relação ao dropZone e depois verificamos se o cursor está fora do bloco. Se deixado, removeremos o 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'); }; }); 

E isso é tudo, o problema está resolvido! É assim que nosso campo com o arquivo interno se parece:


Vamos passar a manipular o próprio evento drop . Mas primeiro, lembre-se de que, além do Arrastar e Soltar, input[type=file] e cada um desses métodos é independente em sua essência, mas deve executar as mesmas ações: fazer upload de arquivos. Portanto, proponho criar uma função separada universal para ambos os métodos, para a qual transferiremos arquivos, e ela já decidirá o que fazer com eles. Vamos chamá-lo de sendFiles() , mas o descreveremos um pouco mais tarde. Para começar, manipule o evento drop :

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

Primeiro, .dragover remover a classe dropZone do dropZone . Então temos uma matriz contendo os arquivos. Se você usar jQuery, o caminho será e.originalEvent.dataTransfer.files ; se você escrever em JS puro, e.dataTransfer.files . Bem, então passamos o array para nossa função ainda não realizada.

Agora, elaboraremos o método de carregamento via input[type=file] :

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

Nós rastreamos o evento de change no botão de seleção de arquivo, obtemos a matriz por this.files e a enviamos para a função

Enviando arquivos via AJAX


O último passo - a descrição da função de processamento de arquivo - é exclusivo para todos. Isso dependerá diretamente do objetivo que você perseguir. Por exemplo, mostrarei como enviar arquivos para o servidor através do AJAX.

Suponha que criamos um campo para fazer upload de fotos. Como não queremos que mais nada chegue ao nosso servidor, decidimos os tipos de arquivo: seja PNG e JPEG. Também vale a pena regular o tamanho máximo de uma foto que um usuário pode enviar. Limitado a cinco megabytes. Começamos a descrever nossa função:

 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); } }); }; 

Na variável maxFileSize tamanho máximo do arquivo que enviaremos ao servidor. FormData() função FormData() , criaremos um novo objeto da classe FormData , que nos permite gerar conjuntos de pares de valores-chave. Esse objeto pode ser facilmente enviado via AJAX. Em seguida, usamos a construção .each do jQuery para a matriz de files , que aplicará a função que definimos para cada um de seus elementos. O número do elemento e o próprio elemento serão passados ​​como argumentos para a função, que processaremos como index e file respectivamente. Na própria função, verificaremos o arquivo de acordo com nossos critérios: o tamanho é menor que cinco megabytes e o tipo é PNG ou JPEG. Se o arquivo passar no teste, adicione-o ao nosso objeto FormData chamando a função append() . A chave é a sequência 'photos[]' , cujos colchetes no final indicam que é uma matriz na qual pode haver vários objetos. O próprio objeto será o file .

Agora tudo está pronto para enviar arquivos através do AJAX. Adicione as seguintes linhas à nossa função:

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

Como parâmetros url e type indicamos os valores dos atributos de action e method de input[type=file] respectivamente. Para passar pelo AJAX, seremos um objeto Data . Os parâmetros contentType: false e processData: false são necessários para que o navegador não traduza inadvertidamente nossos arquivos para outro formato. No parâmetro success , especificamos a função que será executada se os arquivos forem transferidos com sucesso para o servidor. Seu conteúdo depende da sua imaginação, mas vou me limitar a uma saída modesta de uma mensagem sobre um download bem-sucedido.

Parabéns, agora você pode criar seu próprio campo de upload de arquivos! Obviamente, não posiciono meu método como o único verdadeiro e correto. Meu objetivo era mostrar o curso geral de solução desse problema, adequado principalmente para iniciantes. Se você acha que em algum lugar você poderia fazer algo melhor - escreva nos comentários, discutiremos!

Só isso. Obrigado pela atenção!

Baixar:

  1. Versão final
  2. Problema de foco
  3. Problema de cintilação

Toque em:

  1. Versão final
  2. Problema de foco
  3. Problema de cintilação

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


All Articles