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:
- Versão final
- Problema de foco
- Problema de cintilação
Toque em:
- Versão final
- Problema de foco
- Problema de cintilação