Tout coule, tout change, mais ne
input[type=file]
car cela a gâché les nerfs de tous les développeurs web novices, et continue de le faire jusqu'à présent. Souvenez-vous de vous il y a N ans, lorsque vous commenciez à peine à comprendre les bases de la création de sites Web. Jeune et inexpérimenté, vous avez été véritablement surpris lorsque le bouton de sélection de fichier a complètement refusé de changer la couleur d'arrière-plan en votre pêche préférée. C'est à ce moment que vous avez rencontré pour la première fois cet iceberg indestructible appelé «Téléchargement de fichiers», qui continue à ce jour de «noyer» les développeurs Web novices.
En utilisant l'exemple de la création d'un champ de téléchargement de fichier, je vais vous montrer comment masquer correctement l'
input[type=file]
, définir le focus sur un objet qui ne peut pas avoir le focus, gérer les événements de glisser-déposer et envoyer des fichiers via AJAX. Et je vais également vous présenter quelques bogues de navigateur et des façons de les contourner. L'article est écrit pour les débutants, mais à certains moments, il peut être utile et divertissant même pour les développeurs chevronnés.
Marquage et styles principaux
Commençons par le balisage 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>
Peut-être que le principal élément auquel vous devez faire attention est
<label for="file-input"> </label>
La spécification HTML ne nous permet pas d'imposer des propriétés visuelles directement sur l'
input[type=file]
, mais nous avons une balise
label
, cliquer sur ce qui provoque un clic sur l'élément de formulaire auquel il est attaché. À notre grande joie, cette balise n'a pas de limites de stylisation: nous pouvons en faire ce que nous voulons.
Un plan d'action émerge: nous stylisons l'étiquette à notre guise et cachons l'
input[type=file]
abri des regards. Tout d'abord, configurez les styles de page généraux:
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; }
Maintenant, stylisez notre étiquette:
#upload-container label { font-weight: bold; } #upload-container label:hover { cursor: pointer; text-decoration: underline; }
Ce que nous recherchons (
input[type=file]
supprimée du balisage):

Bien sûr, vous pouvez centrer l'étiquette, ajouter un arrière-plan et une bordure, obtenir un bouton complet, mais notre priorité est le glisser-déposer.
Masquer l'entrée
Maintenant, nous devons masquer l'
input[type=file]
. La première chose qui frappe la tête est l'
display: none
et la
visibility: hidden
propriétés
visibility: hidden
. Mais ce n'est pas si simple. Sur certains navigateurs plus anciens, cliquer sur l'étiquette n'aura plus aucun effet. Mais ce n'est pas tout. Comme vous le savez, les éléments invisibles ne peuvent pas recevoir le focus, et peu importe ce qu'ils disent, le focus est important, car pour certaines personnes, c'est la seule façon d'interagir avec le site. Cette méthode ne nous convient donc pas. Voyons cela:
#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; }
Nous positionnons absolument notre
input[type=file]
rapport à son bloc parent, la réduisons à
0.1px
, la
0.1px
transparente et définissons son
z-index
moins que celui du parent, de sorte que, pour ainsi dire, c'est sûr.
Félicitations, nous avons réalisé ce que nous voulions: notre champ ressemble exactement à l'image précédente.
Ajuster la mise au point
Puisque notre
input[type=file]
physiquement présente sur la page, elle a la capacité de recevoir le focus. Autrement dit, si nous
Tab
sur la touche
Tab
sur la page, à un moment donné, le focus passera à l'
input[type=file]
. Mais le problème est que nous ne le verrons pas: le champ que nous avons caché ressortira. Oui, si en ce moment nous
Enter
sur
Enter
, la boîte de dialogue s’ouvrira et tout fonctionnera comme il se doit, mais comment comprenons-nous qu’il est temps d’appuyer?
Notre tâche consiste à sélectionner une marque d'une certaine manière au moment où l'accent est mis sur le champ de téléchargement de fichier. Mais comment pouvons-nous faire cela si la balise ne peut pas recevoir le focus? Les connaisseurs CSS3 penseront immédiatement à la pseudo-classe
:focus
, qui définit les styles des éléments en focus, et les sélecteurs
+
ou
~
qui sélectionnent les bons voisins: les éléments situés au même niveau d'imbrication, venant après l'élément sélectionné. Étant donné que dans notre
input[type=file]
balisage
input[type=file]
est situé directement en face de la balise
label
, l'entrée suivante a lieu:
#upload-container input[type=file]:focus + label { }
Mais là encore, pas si simple. Pour commencer, voyons comment sélectionner une étiquette. Comme vous le savez, tous les navigateurs modernes et pas si ont des propriétés par défaut uniques pour les éléments en focus. Fondamentalement, il s'agit de la propriété de
outline
, qui crée un contour autour d'un élément qui diffère de la
border
en ce qu'il ne redimensionne pas l'élément et peut en être éloigné. En règle générale, les gens n'utilisent qu'un seul navigateur, ils s'habituent donc à ses normes. Pour faciliter la navigation des utilisateurs sur notre site, nous devons essayer d'ajuster la mise au point afin qu'elle soit aussi naturelle que possible pour les navigateurs modernes les plus populaires. En théorie, en utilisant JavaScript, vous pouvez obtenir des informations sur le navigateur dans lequel l'utilisateur a ouvert le site et personnaliser les styles en conséquence, mais ce sujet est trop complexe et encombrant dans le cadre d'un article destiné principalement aux débutants. On va essayer de s'en sortir avec un peu de sang.
Dans les navigateurs basés sur le moteur WebKet (Google Chrome, Opera, Safari), la propriété par défaut des éléments en focus a la forme:
:focus { outline: -webkit-focus-ring-color auto 5px; }
Ici
-webkit-focus-ring-color
est la
-webkit-focus-ring-color
mise au point spécifique à ce moteur uniquement. Autrement dit, cette ligne fonctionnera exclusivement dans les navigateurs WebKit, et c'est exactement ce dont nous avons besoin. Spécifiez cette propriété pour notre étiquette:
#upload-container input[type=file]:focus + label { outline: -webkit-focus-ring-color auto 5px; }
Nous ouvrons Google Chrome ou Opera, nous regardons. Tout fonctionne comme il se doit:

Voyons comment les choses se concentrent dans Mozilla Firefox et Microsoft Edge. Pour ces navigateurs, la propriété par défaut est:
:focus { outline: 1px solid #0078d7; }
et
:focus { outline: 1px solid #212121; }
en conséquence.
Malheureusement, le préfixe
-moz-
ne fonctionnera pas avec la propriété de
outline
. Par conséquent, nous devrons choisir laquelle de ces deux propriétés nous choisirons. Étant donné que le nombre d'utilisateurs de Firefox est beaucoup plus élevé, il est plus rationnel de privilégier ce navigateur particulier. Cela ne signifie pas que nous privons les utilisateurs d'Edge et les autres navigateurs de la possibilité de voir où se situe désormais le focus, ils seront simplement «différents» d'eux. Eh bien, vous devez faire des sacrifices.
Nous ajoutons le style de Mozilla Firefox avant le style de WebKit: d'abord tous les navigateurs appliqueront la première propriété, puis ceux qui le peuvent (Google Chrome, Opera, Safari, etc.) appliqueront la seconde.
#upload-container input[type=file]:focus + label { outline: 1px solid #0078d7; outline: -webkit-focus-ring-color auto 5px; }
Et ici, l'étrange commence: dans Edge, tout fonctionne bien, mais Firefox, pour une raison inconnue, refuse d'appliquer des propriétés à l'étiquette en mettant l'accent sur l'
input[type=file]
. Et l'événement
focus
lui-même se produit - vérifié via JavaScript. De plus, si vous forcez le focus sur le champ de sélection de fichier via les outils de développement, la propriété s'appliquera et notre trait apparaîtra! Apparemment, c'est un bug du navigateur lui-même, mais si quelqu'un a des idées pourquoi cela se produit - écrivez dans les commentaires.
Eh bien, rien, les héros normaux font toujours le tour. Comme je l'ai dit plus tôt, l'événement
focus
se produit, ce qui signifie que nous pouvons ajuster les propriétés directement à partir de JavaScript. Mais pour cela nous devrons changer la logique de notre sélecteur:
#upload-container label.focus { outline: 1px solid #0078d7; outline: -webkit-focus-ring-color auto 5px; }
Nous décrirons la classe
.focus
pour notre étiquette et nous l'ajouterons chaque fois que l'
input[type=file]
recevra le focus et la supprimera lorsqu'elle perdra.
$('#file-input').focus(function() { $('label').addClass('focus'); }) .focusout(function() { $('label').removeClass('focus'); });
Maintenant, tout fonctionne comme il se doit. Félicitations, nous avons compris l'objectif.
Glisser-déposer
Le travail avec glisser-déposer est effectué en suivant les événements spéciaux du navigateur:
drag, dragstart, dragend, dragover, dragenter, dragleave, drop
. Vous pouvez facilement trouver une description détaillée de chacun d'eux sur Internet. Nous n'en suivrons que quelques-uns.
Tout d'abord, définissez un élément glisser-déposer:
var dropZone = $('#upload-container');
Ensuite, nous décrivons en CSS une classe spéciale que nous assignerons à
dropZone
lorsque le curseur tirant le fichier sera directement au-dessus. Cela est nécessaire pour informer visuellement l'utilisateur que le fichier peut déjà être publié.
#upload-container.dragover { background-color: #fafafa; outline-offset: -17px; }
Passons maintenant au fichier JS. Pour commencer, nous devons annuler toutes les actions par défaut pour les événements glisser-déplacer. Par exemple, l'un de ces événements est l'ouverture d'un fichier lancé par un navigateur. Nous n'en avons pas besoin du tout, nous allons donc écrire les lignes suivantes:
dropZone.on('drag dragstart dragend dragover dragenter dragleave drop', function(){ return false; });
Dans jQuery, appeler l'
return false
équivaut à appeler deux fonctions à la fois:
e.preventDefault()
et
e.stopPropagation()
.
Nous commençons à décrire notre propre gestionnaire d'événements. Nous agirons de la même manière que nous l'avons fait avec le focus, mais cette fois nous allons suivre les événements de
dragenter
et de
dragover
pour ajouter une classe et l'événement
dragleave
pour la supprimer:
dropZone.on('dragover dragenter', function() { dropZone.addClass('dragover'); }); dropZone.on('dragleave', function(e) { dropZone.removeClass('dragover'); });
Et encore une fois, une surprise désagréable nous attend: lorsque vous vous déplacez le long de
dropZone
avec la souris avec le fichier, le champ commence à clignoter. Cela se produit dans les navigateurs Microsoft Edge et WebKit. Soit dit en passant, la plupart de ces navigateurs WebKit fonctionnent actuellement sur le moteur Blink (apprécié l'ironie, hein?). Mais dans Mozilla, rien ne scintille. Apparemment, j'ai décidé de le corriger après des bugs de focus.
Et ce scintillement se produit du fait que lorsque vous
dropZone
, que ce soit une image ou une
div
avec un champ de sélection de fichier et une étiquette, pour une raison quelconque, l'événement
dragleave
. Il est évident pour nous que nous ne quittons pas le terrain, mais les navigateurs, pour une raison quelconque, ne le font pas, et à cause de cela, ils suppriment
.focus
la classe
dropZone
de
dropZone
.
Et encore une fois, nous devons en quelque sorte sortir. Si le navigateur lui-même ne comprend pas que nous ne quittons pas le terrain, nous devrons l'aider. Et nous le ferons à travers des conditions supplémentaires: nous calculons les coordonnées de la souris par rapport à
dropZone
, puis vérifions si le curseur est en dehors du bloc. S'il est laissé, nous supprimons le style:
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'); }; });
Et c’est tout, le problème est résolu! Voici à quoi ressemble notre champ avec le fichier à l'intérieur:

Passons maintenant à la gestion de l'événement
drop
lui-même. Mais d'abord, rappelez-vous que, en plus du glisser-déposer, nous avons une
input[type=file]
, et chacune de ces méthodes est indépendante dans son essence, mais doit effectuer les mêmes actions: télécharger des fichiers. Par conséquent, je propose de créer une fonction distincte universelle pour les deux méthodes, dans laquelle nous transférerons des fichiers, et il décidera déjà quoi en faire. Nous l'appellerons
sendFiles()
, mais nous le décrirons un peu plus tard. Pour commencer, gérez l'événement
drop
:
dropZone.on('drop', function(e) { dropZone.removeClass('dragover'); let files = e.originalEvent.dataTransfer.files; sendFiles(files); });
Tout d'abord,
.dragover
classe
dropZone
de
dropZone
. Ensuite, nous obtenons un tableau contenant les fichiers. Si vous utilisez jQuery, le chemin sera
e.originalEvent.dataTransfer.files
, si vous écrivez en JS pur, puis
e.dataTransfer.files
. Eh bien, nous passons ensuite le tableau à notre fonction encore non réalisée.
Nous allons maintenant déterminer la méthode de chargement via l'
input[type=file]
:
$('#file-input').change(function() { let files = this.files; sendFiles(files); });
Nous suivons l'événement de
change
sur le bouton de sélection de fichier, nous obtenons le tableau via
this.files
et l'envoyons à la fonction.
Envoi de fichiers via AJAX
La dernière étape - la description de la fonction de traitement des fichiers - est unique à tout le monde. Cela dépendra directement de l'objectif que vous poursuivez. Pour un exemple, je vais montrer comment envoyer des fichiers au serveur via AJAX.
Supposons que nous créons un champ pour télécharger des photos. Nous ne voulons pas que quelque chose d'autre arrive sur notre serveur, nous allons donc décider des types de fichiers: que ce soit PNG et JPEG. Il convient également de réglementer la taille maximale d'une photo qu'un utilisateur peut envoyer. Limité à cinq mégaoctets. Nous commençons à décrire notre fonction:
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); } }); };
Dans la variable
maxFileSize
taille maximale de fichier que nous enverrons au serveur.
FormData()
, nous allons créer un nouvel objet de la classe
FormData
, ce qui nous permet de générer des ensembles de paires clé-valeur. Un tel objet peut être facilement envoyé via AJAX. Ensuite, nous utilisons la construction jQuery
.each
pour le tableau de
files
, qui appliquera la fonction que nous avons définie pour chacun de ses éléments. Le numéro de l'élément et l'élément lui-même seront passés comme arguments à la fonction, que nous traiterons respectivement comme
index
et
file
. Dans la fonction elle-même, nous vérifierons le fichier par rapport à nos critères: la taille est inférieure à cinq mégaoctets et le type est PNG ou JPEG. Si le fichier réussit le test, ajoutez-le à notre objet
FormData
en appelant la fonction
append()
. La clé est la chaîne
'photos[]'
, dont les crochets à la fin indiquent qu'il s'agit d'un tableau dans lequel il peut y avoir plusieurs objets. L'objet lui-même sera
file
.
Maintenant, tout est prêt pour l'envoi de fichiers via AJAX. Ajoutez les lignes suivantes à notre fonction:
$.ajax({ url: dropZone.attr('action'), type: dropZone.attr('method'), data: Data, contentType: false, processData: false, success: function(data) { alert(' '); } });
En tant que paramètres
url
et
type
nous indiquons respectivement les valeurs des attributs
action
et
method
d'
input[type=file]
. Pour passer par AJAX, nous serons un objet
Data
. Les paramètres
contentType: false
et
processData: false
sont nécessaires pour que le navigateur ne traduise pas par inadvertance nos fichiers dans un autre format. Dans le paramètre
success
, nous spécifions la fonction qui sera exécutée si les fichiers sont correctement transférés vers le serveur. Son contenu dépend de votre imagination, mais je me limiterai à une modeste sortie d'un message sur un téléchargement réussi.
Félicitations, vous pouvez maintenant créer votre propre champ de téléchargement de fichiers! Bien sûr, je ne positionne pas ma méthode comme la seule vraie et correcte. Mon objectif était de montrer le cours général de la résolution de ce problème, adapté principalement aux débutants. Si vous pensez que quelque part, vous pourriez faire quelque chose de mieux - écrivez dans les commentaires, nous en discuterons!
C’est tout. Merci de votre attention!
Téléchargez:
- Version finale
- Problème de focalisation
- Problème de scintillement
Touchez:
- Version finale
- Problème de focalisation
- Problème de scintillement