glisser-déposer des fichiers dans l'entrée de fichier html standard

163

De nos jours, nous pouvons faire glisser et déposer des fichiers dans un conteneur spécial et les télécharger avec XHR 2. Plusieurs à la fois. Avec des barres de progression en direct, etc. Trucs très cool.Exemple ici.

Mais parfois, nous ne voulons pas autant de fraîcheur. Ce que je voudrais est de déplacer les fichiers - plusieurs à la fois - dans une entrée de fichier standard HTML : <input type=file multiple>.

Est-ce possible? Existe-t-il un moyen de «remplir» l'entrée de fichier avec les bons noms de fichiers (?) À partir du fichier drop? (Les chemins de fichiers complets ne sont pas disponibles pour des raisons de sécurité du système de fichiers.)

Pourquoi? Parce que j'aimerais soumettre un formulaire normal. Pour tous les navigateurs et tous les appareils. Le glisser-déposer n'est qu'une amélioration progressive pour améliorer et simplifier l'UX. Le formulaire standard avec entrée de fichier standard (+multiple attribut) sera là. Je voudrais ajouter l'amélioration HTML5.

modifier
Je sais que dans certains navigateurs, vous pouvez parfois (presque toujours) déposer des fichiers dans l'entrée de fichier elle-même. Je sais que Chrome le fait généralement, mais parfois il échoue et charge ensuite le fichier dans la page actuelle (un gros échec si vous remplissez un formulaire). Je veux le tromper et le protéger du navigateur.

Rudie
la source
1
Préparez-vous à un peu de douleur si vous souhaitez inclure mac / safari dans vos compatibilités.
Shark8
1
@ Shark8 en fait Safari / Mac est l'un des rares navigateurs qui le supporte déjà.
Ricardo Tomasi
En fait, aucun des navigateurs ne le prend en charge. Le champ de saisie du fichier est en lecture seule (pour la sécurité) et c'est le problème. Sécurité stupide!
Rudie
2
J'entendais par "glisser-déposer des fichiers - plusieurs à la fois - dans une entrée de fichier HTML standard".
Ricardo Tomasi
3
glisser / déposer plusieurs fichiers pour input type="file" multiplefonctionner correctement dans Safari
Lloyd

Réponses:

71

Ce qui suit fonctionne dans Chrome et FF, mais je n'ai pas encore trouvé de solution qui couvre également IE10 +:

// dragover and dragenter events need to have 'preventDefault' called
// in order for the 'drop' event to register. 
// See: https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Drag_operations#droptargets
dropContainer.ondragover = dropContainer.ondragenter = function(evt) {
  evt.preventDefault();
};

dropContainer.ondrop = function(evt) {
  // pretty simple -- but not for IE :(
  fileInput.files = evt.dataTransfer.files;

  // If you want to use some of the dropped files
  const dT = new DataTransfer();
  dT.items.add(evt.dataTransfer.files[0]);
  dT.items.add(evt.dataTransfer.files[3]);
  fileInput.files = dT.files;

  evt.preventDefault();
};
<!DOCTYPE html>
<html>
<body>
<div id="dropContainer" style="border:1px solid black;height:100px;">
   Drop Here
</div>
  Should update here:
  <input type="file" id="fileInput" />
</body>
</html>

Vous voudrez probablement utiliser addEventListenerou jQuery (etc.) pour enregistrer vos gestionnaires evt - c'est juste pour des raisons de concision.

jlb
la source
3
Waaaaaaaat! Ça marche!? C'est exactement ce que je cherchais. N'a pas fonctionné il y a 2 ans. Impressionnant! Bien sûr, cela ne fonctionne pas dans IE =) La question importante: y a-t-il une détection fiable des fonctionnalités ?, donc vous pouvez cacher la zone de dépôt dans IE, bc cela ne fonctionnera pas.
Rudie
D'oh, un peu tard alors :) En ce moment, je n'utilise que de simples vérifications d'agent utilisateur dans JS. Bien sûr, vous devez tester pour MSIE , Trident/(IE11) et Edge/(IE12) ...
jlb
FF 48.0.2 (Mac) lance "TypeError: définition d'une propriété qui n'a qu'un getter" à la ligne fileInput.files = evt.dataTransfer.files;. Safari et Chrome fonctionnent cependant tous les deux correctement.
Risadinha
2
Cet exemple ne fonctionne pas sur Firefox 45 sur Linux, mais cela fonctionne pour moi sur Chrome. Je n'obtiens aucune erreur de console, cela ne montre tout simplement pas qu'un fichier a été supprimé.
Bryan Oakley
1
en fait, j'ai fait un post pour essayer de trouver une solution, mais j'ai compris par moi-même. Changement assez simple, juste fileInputs [index] = ... pour passer les données du fichier à une entrée particulière, puis appeler une fonction showNext pour ajouter une nouvelle entrée stackoverflow.com/a/43397640/6392779
pseudo
51

J'ai fait une solution pour cela.

La fonctionnalité de glisser-déposer de cette méthode ne fonctionne qu'avec Chrome, Firefox et Safari. (Je ne sais pas si cela fonctionne avec IE10), mais pour les autres navigateurs, le bouton "Ou cliquez ici" fonctionne très bien.

Le champ de saisie suit simplement votre souris lorsque vous faites glisser un fichier sur une zone, et j'ai également ajouté un bouton.

Opacité sans commentaire: 0; l'entrée de fichier n'est visible que pour que vous puissiez voir ce qui se passe.

BjarkeCK
la source
C'est pourquoi j'ai ajouté un bouton aussi ^^ Mais yah vous avez raison. Je ne l'utiliserais pas par temps ... Ou le ferais-je!?
BjarkeCK
J'aurais aimé savoir comment cela est censé fonctionner ... il semble que toutes les fonctions de glisser / déposer doivent gérer l'ajout de l'effet de survol ... mais je ne peux vraiment pas le dire. Ça a l'air bien dans le violon, mais je ne pense pas pouvoir l'utiliser car je dois prendre en charge Internet Explorer
nmz787
1
@PiotrKowalski Je pense que cela déclencherait potentiellement un appel récursif jusqu'à ce que la pile d'appels déborde
John
2
J'ai fini par utiliser uniquement le style. Rendre l'entrée 100% largeur et hauteur fonctionnait mieux que de la déplacer.
Eddie
2
Existe-t-il un moyen de se débarrasser du "aucun fichier choisi" qui continue de planer avec le pointeur de la souris? @BjarkeCK
Abhishek Singh
27

C'est la manière HTML5 "DTHML" de le faire. Entrée de forme normale (qui EST lue uniquement comme l'a souligné Ricardo Tomasi). Ensuite, si un fichier est glissé, il est joint au formulaire. Cela nécessitera une modification de la page d'action pour accepter le fichier téléchargé de cette façon.

function readfiles(files) {
  for (var i = 0; i < files.length; i++) {
    document.getElementById('fileDragName').value = files[i].name
    document.getElementById('fileDragSize').value = files[i].size
    document.getElementById('fileDragType').value = files[i].type
    reader = new FileReader();
    reader.onload = function(event) {
      document.getElementById('fileDragData').value = event.target.result;}
    reader.readAsDataURL(files[i]);
  }
}
var holder = document.getElementById('holder');
holder.ondragover = function () { this.className = 'hover'; return false; };
holder.ondragend = function () { this.className = ''; return false; };
holder.ondrop = function (e) {
  this.className = '';
  e.preventDefault();
  readfiles(e.dataTransfer.files);
}
#holder.hover { border: 10px dashed #0c0 !important; }
<form method="post" action="http://example.com/">
  <input type="file"><input id="fileDragName"><input id="fileDragSize"><input id="fileDragType"><input id="fileDragData">
  <div id="holder" style="width:200px; height:200px; border: 10px dashed #ccc"></div>
</form>

C'est encore plus efficace si vous pouvez faire de la fenêtre entière une zone de dépôt, voir Comment détecter un événement de glisser HTML5 entrant et sortant de la fenêtre, comme le fait Gmail?

William Entriken
la source
1
Bonne solution mais cela ne fonctionne pas sur IE <10 car IE 9 et moins ne prend pas en charge les fichiers HTML5 API :(
Develoger
1
Cette ligne: document.getElementById ('fileDragData'). Value = files [i] .slice (); n'est pas nécessaire, car elle est remplacée dans la fonction
reader.onload
Voici une autre application de glisser-déposer mignonne qui n'implique PAS de téléchargement de fichiers. Lien juste au cas où quelqu'un voudrait étudier plus. codepen.io/anon/pen/MOPvZK?editors=1010
William Entriken
1
La solution IE 10 est de dégrader et d'afficher uniquement leinput type=file
William Entriken
Est-ce que je manque quelque chose ou est-ce que vous écrasez constamment la .valuepropriété avec le fichier le plus récent, chaque fois que vous parcourez la boucle antérieure?
Kevin Burke
13

//----------App.js---------------------//
$(document).ready(function() {
    var holder = document.getElementById('holder');
    holder.ondragover = function () { this.className = 'hover'; return false; };
    holder.ondrop = function (e) {
      this.className = 'hidden';
      e.preventDefault();
      var file = e.dataTransfer.files[0];
      var reader = new FileReader();
      reader.onload = function (event) {
          document.getElementById('image_droped').className='visible'
          $('#image_droped').attr('src', event.target.result);
      }
      reader.readAsDataURL(file);
    };
});
.holder_default {
    width:500px; 
    height:150px; 
    border: 3px dashed #ccc;
}

#holder.hover { 
    width:400px; 
    height:150px; 
    border: 3px dashed #0c0 !important; 
}

.hidden {
    visibility: hidden;
}

.visible {
    visibility: visible;
}
<!DOCTYPE html>

<html>
    <head>
        <title> HTML 5 </title>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.js"></script>
    </head>
    <body>
      <form method="post" action="http://example.com/">
        <div id="holder" style="" id="holder" class="holder_default">
          <img src="" id="image_droped" width="200" style="border: 3px dashed #7A97FC;" class=" hidden"/>
        </div>
      </form>
    </body>
</html>

Dipak
la source
2
Que montre-t-il à l'utilisateur? Pouvez-vous faire un violon ou un exemple en ligne?
Rudie
@Rudie s'il vous plaît cliquez sur exécuter un extrait de code et faites glisser-déposer une image pour l'afficher, il affichera l'aperçu de l'image déposée.
Dipak
6

En théorie, vous pouvez ajouter un élément superposant le <input/>, puis utiliser son dropévénement pour capturer les fichiers (à l'aide de l'API File) et les transmettre au filestableau d' entrée .

Sauf qu'une entrée de fichier est en lecture seule . C'est un vieux problème.

Vous pouvez cependant contourner complètement le contrôle de formulaire et télécharger via XHR (pas sûr de la prise en charge de cela):

Vous pouvez également utiliser un élément dans la zone environnante pour annuler l'événement de dépôt dans Chrome et empêcher le comportement par défaut de chargement du fichier.

Déposer plusieurs fichiers sur l'entrée fonctionne déjà dans Safari et Firefox.

Ricardo Tomasi
la source
6
Comme je l'ai dit dans la question: je connais XHR2 et je ne veux pas l'utiliser. J'imagine la partie importante: "l'entrée du fichier est en lecture seule". Ça craint ... Annuler l'événement drop n'est pas une mauvaise idée! Pas aussi bien que je l'espérais, mais probablement le meilleur. La suppression de plusieurs fichiers fonctionne également dans Chrome. Chrome permet désormais également de télécharger des répertoires. Tous très kewl et ne pas aider mon cas = (
Rudie
5

C'est ce que je suis sorti.

Utilisation de Jquery et Html. Cela l'ajoutera aux fichiers d'insertion.

var dropzone = $('#dropzone')


dropzone.on('drag dragstart dragend dragover dragenter dragleave drop', function(e) {
    e.preventDefault();
    e.stopPropagation();
  })

dropzone.on('dragover dragenter', function() {
    $(this).addClass('is-dragover');
  })
dropzone.on('dragleave dragend drop', function() {
    $(this).removeClass('is-dragover');
  })  
  
dropzone.on('drop',function(e) {
	var files = e.originalEvent.dataTransfer.files;
	// Now select your file upload field 
	// $('input_field_file').prop('files',files)
  });
input {	margin: 15px 10px !important;}

.dropzone {
	padding: 50px;
	border: 2px dashed #060;
}

.dropzone.is-dragover {
  background-color: #e6ecef;
}

.dragover {
	bg-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<div class="" draggable='true' style='padding: 20px'>
	<div id='dropzone' class='dropzone'>
		Drop Your File Here
	</div>
	</div>

Lionel Yeo
la source
4

Pour une solution CSS uniquement:

<div class="file-area">
    <input type="file">
    <div class="file-dummy">
        <span class="default">Click to select a file, or drag it here</span>
        <span class="success">Great, your file is selected</span>
    </div>
</div>

.file-area {
    width: 100%;
    position: relative;
    font-size: 18px;
}
.file-area input[type=file] {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    opacity: 0;
    cursor: pointer;
}
.file-area .file-dummy {
    width: 100%;
    padding: 50px 30px;
    border: 2px dashed #ccc;
    background-color: #fff;
    text-align: center;
    transition: background 0.3s ease-in-out;
}
.file-area .file-dummy .success {
    display: none;
}
.file-area:hover .file-dummy {
    border: 2px dashed #1abc9c;
}
.file-area input[type=file]:valid + .file-dummy {
    border-color: #1abc9c;
}
.file-area input[type=file]:valid + .file-dummy .success {
    display: inline-block;
}
.file-area input[type=file]:valid + .file-dummy .default {
    display: none;
}

Modifié à partir de https://codepen.io/Scribblerockerz/pen/qdWzJw

Jonathan
la source
4

Je sais que certaines astuces fonctionnent dans Chrome:

Lorsque vous déposez des fichiers dans la zone de dépôt, vous obtenez un dataTransfer.filesobjet, c'est-à-dire un FileListtype d'objet, qui contient tous les fichiers que vous avez glissés. Pendant ce temps, l' <input type="file" />élément a la propriété files, c'est-à-dire le même FileListobjet de type.

Ainsi, vous pouvez simplement affecter l' dataTransfer.filesobjet à la input.filespropriété.

Timur Gilauri
la source
3
Oui, c'est le cas ces jours-ci. Pas un truc. Très intentionnel. Aussi très intentionnellement très restreint. Vous ne pouvez pas du tout ajouter des fichiers à la liste ou modifier la liste. Le glisser-déposer permet de mémoriser des fichiers et de les ajouter, mais input.filesne peut pas = (
Rudie
3

Pour tous ceux qui cherchent à faire cela en 2018, j'ai une solution bien meilleure et plus simple que toutes les anciennes choses publiées ici. Vous pouvez créer une jolie boîte de glisser-déposer avec juste du HTML, du JavaScript et du CSS.

(Fonctionne uniquement dans Chrome jusqu'à présent)

Commençons par le HTML.

<div>
<input type="file" name="file" id="file" class="file">
<span id="value"></span>
</div>

Ensuite, nous aborderons le style.

    .file {
        width: 400px;
        height: 50px;
        background: #171717;
        padding: 4px;
        border: 1px dashed #333;
        position: relative;
        cursor: pointer;
    }

    .file::before {
        content: '';
        position: absolute;
        background: #171717;
        font-size: 20px;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 100%;
        height: 100%;
    }

    .file::after {
        content: 'Drag & Drop';
        position: absolute;
        color: #808080;
        font-size: 20px;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
    }

Une fois que vous avez fait cela, cela semble déjà bien. Mais j'imagine que vous aimeriez voir quel fichier vous avez effectivement téléchargé, donc nous allons faire du JavaScript. Rappelez-vous cette plage de valeurs pfp? C'est là que nous imprimerons le nom du fichier.

let file = document.getElementById('file');
file.addEventListener('change', function() {
    if(file && file.value) {
        let val = file.files[0].name;
        document.getElementById('value').innerHTML = "Selected" + val;
    }
});

Et c'est tout.

Michael
la source
J'obtiens un TypeError Uncaught: impossible de lire la propriété 'addEventListener' de null lorsque j'utilise ce code - sous Chrome - ne fonctionne-t-il pas dans les dernières versions de Chrome?
Fight Fire With Fire
Cela fonctionne bien pour moi dans la dernière version de Chrome. Assurez-vous d'utiliser les bons ID
Michael
1

Excellent travail de @BjarkeCK. J'ai apporté quelques modifications à son travail, pour l'utiliser comme méthode dans jquery:

$.fn.dropZone = function() {
  var buttonId = "clickHere";
  var mouseOverClass = "mouse-over";

  var dropZone = this[0];
  var $dropZone = $(dropZone);
  var ooleft = $dropZone.offset().left;
  var ooright = $dropZone.outerWidth() + ooleft;
  var ootop = $dropZone.offset().top;
  var oobottom = $dropZone.outerHeight() + ootop;
  var inputFile = $dropZone.find("input[type='file']");
  dropZone.addEventListener("dragleave", function() {
    this.classList.remove(mouseOverClass);
  });
  dropZone.addEventListener("dragover", function(e) {
    console.dir(e);
    e.preventDefault();
    e.stopPropagation();
    this.classList.add(mouseOverClass);
    var x = e.pageX;
    var y = e.pageY;

    if (!(x < ooleft || x > ooright || y < ootop || y > oobottom)) {
      inputFile.offset({
        top: y - 15,
        left: x - 100
      });
    } else {
      inputFile.offset({
        top: -400,
        left: -400
      });
    }

  }, true);
  dropZone.addEventListener("drop", function(e) {
    this.classList.remove(mouseOverClass);
  }, true);
}

$('#drop-zone').dropZone();

Violon de travail

Monsieur green
la source
1
FYI: Le lien Fiddle est rompu.
jimiayler
1

Quelques années plus tard, j'ai construit cette bibliothèque pour déposer des fichiers dans n'importe quel élément HTML.

Vous pouvez l'utiliser comme

const Droppable = require('droppable');

const droppable = new Droppable({
    element: document.querySelector('#my-droppable-element')
})

droppable.onFilesDropped((files) => {
    console.log('Files were dropped:', files);
});

// Clean up when you're done!
droppable.destroy();
Joël Hernandez
la source
comment récupérer le fichier sélectionné plus tard lors de la soumission du formulaire?
Nikhil VJ le
-1

Ce que vous pouvez faire, c'est afficher une entrée de fichier et la superposer avec votre zone de dépôt transparente, en prenant soin d'utiliser un nom comme file[1]. {Assurez-vous d'avoir leenctype="multipart/form-data" intérieur de votre balise FORM.}

Ensuite, demandez à la zone de dépôt de gérer les fichiers supplémentaires en créant dynamiquement plus d'entrées de fichier pour les fichiers 2..number_of_files, assurez-vous d'utiliser le même nom de base, en remplissant correctement l'attribut valeur.

Enfin (front-end) soumettez le formulaire.


Tout ce qui est nécessaire pour gérer cette méthode est de modifier votre procédure pour gérer un tableau de fichiers.

Requin8
la source
1
L'entrée de fichier a un multipleattribut de nos jours. Pas besoin de plus d'une entrée de fichier. Ce n'est pas le problème cependant. Comment obtenir les Fileobjets dans l'entrée de fichier? Je pense que cela nécessite un exemple de code ...
Rudie
1
@Rudie tu ne peux pas, c'est ça le problème.
Ricardo Tomasi
1
Vous ne pouvez pas quoi? Plusieurs? Oui, vous pouvez. Je viens de dire que. Le multiple n'est pas le problème. L'obtention des fichiers d'un objet File (glissé) dans une entrée de fichier, c'est le problème.
Rudie
@Rudie obtenir des fichiers glissés dans une entrée de fichier est possible avec Chrome / FF (en utilisant la filespropriété), mais je n'ai pas réussi dans IE - avez-vous eu de la chance?
jlb
1
@jlb Que voulez-vous dire par "utilisation de la propriété files"? Pourriez-vous faire une réponse avec un code pertinent? Ce que je cherchais ne fonctionne / n'existe dans aucun navigateur.
Rudie