【问题标题】:drag drop files into standard html file input将文件拖放到标准 html 文件输入中
【发布时间】:2011-12-21 20:17:34
【问题描述】:

如今,我们可以将文件拖放到一个特殊的容器中,然后使用 XHR 2 上传它们。一次很多。带有实时进度条等。非常酷的东西。 Example here.

但有时我们并不想要那么酷。我想要将文件(一次很多)拖放到标准 HTML 文件输入中:<input type=file multiple>

这可能吗?有没有办法用正确的文件名(?)从文件拖放中“填充”文件输入? (出于文件系统安全原因,完整文件路径不可用。)

为什么?因为我想提交一个普通的表单。适用于所有浏览器和所有设备。拖放只是渐进式增强,以增强和简化 UX。带有标准文件输入的标准表单(+ multiple 属性)将在那里。我想添加 HTML5 增强功能。

编辑
我知道在 一些 浏览器中,您可以有时(几乎总是)将文件放入文件输入本身。我知道 Chrome 通常会这样做,但有时它会失败,然后将文件加载到当前页面中(如果你正在填写表格,那就大失败了)。我想对它进行欺骗和浏览器防护。

【问题讨论】:

  • 如果您想在兼容性中包含 mac/safari,请做好准备。
  • @Shark8 实际上 Safari/Mac 是少数已经支持此功能的浏览器之一。
  • 实际上,没有一个浏览器支持这个。文件输入字段是只读的(出于安全考虑),这就是问题所在。愚蠢的安全!
  • this 我的意思是“将文件(一次很多)拖放到标准 HTML 文件输入中”。
  • 将多个文件拖放到 input type="file" multiple 在 Safari 中可以正常工作

标签: javascript html file-upload drag-and-drop


【解决方案1】:

对于没有任何 JS 的原生解决方案:

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

<style>
    .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;
    }
</style>

改编自https://codepen.io/Scribblerockerz/pen/qdWzJw

【讨论】:

  • 对我来说,这是最酷的解决方案,没有 JS。感谢您在此处添加。我从来不知道&lt;input type="file"&gt; 原生支持这个拖动选项!
  • 酷,即使没有选择文件(FF),似乎输入也是:valid
  • 哦,当输入具有required 属性时它可以工作。但我不希望需要输入:)
  • 最后替换为.file-area input[type=file]:not([value=""]) + ...,但添加jQuery( 'input[type=file]' ).change( function( e ) { this.defaultValue = this.value;} );,因为DOM value 属性不会更新。所以不再是纯 CSS 了。
【解决方案2】:

简单易行。您不需要创建新的 FormData 或执行 Ajax 来发送图像。您可以将拖动的文件放在输入字段中。

$dropzone.ondrop = function (e) {
    e.preventDefault();
    input.files = e.dataTransfer.files;
}

var $dropzone = document.querySelector('.dropzone');
var input = document.getElementById('file-upload');

$dropzone.ondragover = function (e) { 
  e.preventDefault(); 
  this.classList.add('dragover');
};
$dropzone.ondragleave = function (e) { 
    e.preventDefault();
    this.classList.remove('dragover');
};
$dropzone.ondrop = function (e) {
    e.preventDefault();
    this.classList.remove('dragover');
    input.files = e.dataTransfer.files;
}
.dropzone {
  padding: 10px;
  border: 1px dashed black;
}
.dropzone.dragover {
  background-color: rgba(0, 0, 0, .3);
}
<div class="dropzone">Drop here</div>
<input type="file" id="file-upload" style="display:none;">

【讨论】:

    【解决方案3】:

    这是对 William Entriken 在此处给出的示例的改进、错误修复和修改。有一些问题。例如来自&lt;input type="file" /&gt; 的普通按钮没有做任何事情(以防用户想以这种方式上传文件)。

    注意:我正在制作一个只有我使用的 web 应用程序,因此仅针对 Firefox 进行了测试(和改进)。我确信即使您针对跨浏览器情况进行开发,这段代码也是有价值的。

    function readFile(e) {
      var files;
      if (e.target.files) {
        files=e.target.files
      } else {
        files=e.dataTransfer.files
      }
      if (files.length==0) {
        alert('What you dropped is not a file.');
        return;
      }
      var file=files[0];
      document.getElementById('fileDragName').value = file.name
      document.getElementById('fileDragSize').value = file.size
      document.getElementById('fileDragType').value = file.type
      reader = new FileReader();
      reader.onload = function(e) {
        document.getElementById('fileDragData').value = e.target.result;
      }
      reader.readAsDataURL(file);
    }
    function getTheFile(e) {
      e.target.style.borderColor='#ccc';
      readFile(e);
    }
    <input type="file" onchange="readFile(event)">
    <input id="fileDragName">
    <input id="fileDragSize">
    <input id="fileDragType">
    <input id="fileDragData">
    <div style="width:200px; height:200px; border: 10px dashed #ccc"
         ondragover="this.style.borderColor='#0c0';return false;"       
         ondragleave="this.style.borderColor='#ccc'"       
         ondrop="getTheFile(event); return false;"       
    ></div>

    【讨论】:

      【解决方案4】:

      以下适用于 Chrome 和 FF,但我还没有找到涵盖 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>

      您可能希望使用 addEventListener 或 jQuery(等)来注册您的 evt 处理程序 - 这只是为了简洁起见。

      【讨论】:

      • 哇啊啊啊!这样可行!?这正是我所寻找的正是。 2年前没有工作。惊人的!当然它在 IE 中不起作用 =) 重要的问题:是否有可靠的特征检测?,所以你可以在 IE 中隐藏 dropzone,因为它不起作用。
      • D'oh,有点晚了 :) 现在我只是在 JS 中使用简单的用户代理检查。当然你必须测试MSIE Trident/ (IE11) 和Edge/ (IE12)...
      • FF 48.0.2 (Mac) 在fileInput.files = evt.dataTransfer.files; 行抛出“TypeError: setting a property that has only a getter”。 Safari 和 Chrome 都可以正常工作。
      • 这个例子不适用于 linux 上的 firefox 45,但它适用于我在 chrome 上。我没有收到任何控制台错误,它根本没有显示任何文件已被删除。
      • 实际上我发了一个帖子来尝试找到解决方案,但我自己想通了。非常简单的更改,只需 fileInputs[index] = ... 将文件数据传递给特定输入,然后调用函数 showNext 添加新输入stackoverflow.com/a/43397640/6392779
      【解决方案5】:

      //----------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>

      【讨论】:

      • 它向用户展示了什么?你能做一个小提琴或在线例子吗?
      • @Rudie 请点击运行代码 sn-p 并拖放一张图片查看,它会显示拖放图片的预览。
      【解决方案6】:

      我知道 Chrome 有一些技巧:

      将文件放入拖放区时,您会得到一个dataTransfer.files 对象,即FileList 类型的对象,其中包含您拖动的所有文件。同时&lt;input type="file" /&gt;元素具有files属性,也就是FileList类型对象。

      因此,您可以简单地将 dataTransfer.files 对象分配给 input.files 属性。

      【讨论】:

      • 是的,现在确实如此。不是诡计。非常有意。也很刻意很受限制。您无法将文件添加到列表中,或者根本无法更改列表。拖拽可以记住文件,添加到上面,但是input.files不能=(
      【解决方案7】:

      这就是我想出来的。

      使用 Jquery 和 Html。这会将其添加到插入文件中。

      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>

      【讨论】:

        【解决方案8】:

        对于希望在 2018 年执行此操作的任何人,我有一个比此处发布的所有旧内容更好、更简单的解决方案。你可以用普通的 HTML、JavaScript 和 CSS 制作一个漂亮的拖放框。

        (目前仅适用于 Chrome)

        让我们从 HTML 开始。

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

        然后我们将进入样式。

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

        完成此操作后,它看起来已经很好了。但我想你想看看你实际上传了什么文件,所以我们要做一些 JavaScript。还记得那个 pfp 值跨度吗?这就是我们将打印出文件名的地方。

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

        就是这样。

        【讨论】:

        • 我得到一个 Uncaught TypeError: Cannot read property 'addEventListener' of null when I use this code - 在 Chrome 下 - 它在最新版本的 Chrome 中不起作用吗?
        • 在最新版本的 Chrome 中对我来说效果很好。确保使用正确的 ID
        【解决方案9】:

        几年后,我构建了 this library 来将文件拖放到任何 HTML 元素中。

        你可以像这样使用它

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

        【讨论】:

        • 提交表单时如何获取选定的文件?
        【解决方案10】:

        这是实现它的“DTHML”HTML5 方式。正常形式的输入(正如 Ricardo Tomasi 指出的那样,它是只读的)。然后,如果将文件拖入,则将其附加到表单中。这将需要修改操作页面以接受以这种方式上传的文件。

        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>

        如果你能把整个窗口做成一个拖放区就更厉害了,见How do I detect a HTML5 drag event entering and leaving the window, like Gmail does?

        【讨论】:

        • 很好的解决方案,但它不适用于 IE
        • 这一行:document.getElementById('fileDragData').value = files[i].slice();不需要,因为它在 reader.onload 函数中被取代
        • 这是另一个不涉及文件上传的可爱拖放应用程序。链接以防万一有人想学习更多。 codepen.io/anon/pen/MOPvZK?editors=1010
        • IE 10的解决方案是降级,只显示input type=file
        • 我是否遗漏了什么,或者您是否只是在每次迭代前循环时不断地用最新文件覆盖.value 属性?
        【解决方案11】:

        我为此做了一个解决方案。

        $(function () {
            var dropZoneId = "drop-zone";
            var buttonId = "clickHere";
            var mouseOverClass = "mouse-over";
        
            var dropZone = $("#" + dropZoneId);
            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");
            document.getElementById(dropZoneId).addEventListener("dragover", function (e) {
                e.preventDefault();
                e.stopPropagation();
                dropZone.addClass(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);
        
            if (buttonId != "") {
                var clickZone = $("#" + buttonId);
        
                var oleft = clickZone.offset().left;
                var oright = clickZone.outerWidth() + oleft;
                var otop = clickZone.offset().top;
                var obottom = clickZone.outerHeight() + otop;
        
                $("#" + buttonId).mousemove(function (e) {
                    var x = e.pageX;
                    var y = e.pageY;
                    if (!(x < oleft || x > oright || y < otop || y > obottom)) {
                        inputFile.offset({ top: y - 15, left: x - 160 });
                    } else {
                        inputFile.offset({ top: -400, left: -400 });
                    }
                });
            }
        
            document.getElementById(dropZoneId).addEventListener("drop", function (e) {
                $("#" + dropZoneId).removeClass(mouseOverClass);
            }, true);
        
        })
        #drop-zone {
            /*Sort of important*/
            width: 300px;
            /*Sort of important*/
            height: 200px;
            position:absolute;
            left:50%;
            top:100px;
            margin-left:-150px;
            border: 2px dashed rgba(0,0,0,.3);
            border-radius: 20px;
            font-family: Arial;
            text-align: center;
            position: relative;
            line-height: 180px;
            font-size: 20px;
            color: rgba(0,0,0,.3);
        }
        
            #drop-zone input {
                /*Important*/
                position: absolute;
                /*Important*/
                cursor: pointer;
                left: 0px;
                top: 0px;
                /*Important This is only comment out for demonstration purposes.
                opacity:0; */
            }
        
            /*Important*/
            #drop-zone.mouse-over {
                border: 2px dashed rgba(0,0,0,.5);
                color: rgba(0,0,0,.5);
            }
        
        
        /*If you dont want the button*/
        #clickHere {
            position: absolute;
            cursor: pointer;
            left: 50%;
            top: 50%;
            margin-left: -50px;
            margin-top: 20px;
            line-height: 26px;
            color: white;
            font-size: 12px;
            width: 100px;
            height: 26px;
            border-radius: 4px;
            background-color: #3b85c3;
        
        }
        
            #clickHere:hover {
                background-color: #4499DD;
        
            }
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
        <div id="drop-zone">
            Drop files here...
            <div id="clickHere">
                or click here..
                <input type="file" name="file" id="file" />
            </div>
        </div>

        此方法的拖放功能仅适用于 Chrome、Firefox 和 Safari。 (不知道IE10能不能用),但是对于其他浏览器,“或点击这里”按钮可以正常使用。

        在一个区域上拖动文件时,输入字段只需跟随鼠标,我还添加了一个按钮..

        取消注释 opacity:0;文件输入只是可见的,所以你可以看到发生了什么。

        【讨论】:

        • 这就是为什么我也添加了一个按钮^^ 但是你是对的。我不会用它...或者我会!?
        • 我希望我知道它应该如何工作......似乎所有的拖放功能都必须处理添加悬停效果......但我真的不知道。小提琴看起来不错,但我认为我不能使用它,因为我需要支持 Internet Explorer
        • @PiotrKowalski 我认为这可能会触发递归调用,直到调用堆栈溢出
        • 我最终只使用了该样式。将输入设置为 100% 的宽度和高度比移动它更有效。
        • 有没有办法摆脱鼠标指针一直悬停的“未选择文件”? @BjarkeCK
        【解决方案12】:

        @BjarkeCK 的出色工作。我对他的工作进行了一些修改,将其用作 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();
        

        Working Fiddle

        【讨论】:

        • 仅供参考:小提琴链接已损坏。
        【解决方案13】:

        理论上,您可以添加一个覆盖&lt;input/&gt; 的元素,然后使用它的drop 事件来捕获文件(使用文件API)并将它们传递给输入files 数组。

        除了文件输入是只读。这是一个老问题。

        但是,您可以完全绕过表单控件并通过 XHR 上传(不确定是否支持):

        您也可以使用周围区域的元素来取消 Chrome 中的 drop 事件,并阻止加载文件的默认行为。

        在 Safari 和 Firefox 中已经可以在输入上放置多个文件。

        【讨论】:

        • 就像我在问题中所说:我知道 XHR2,但我不想使用它。我猜重要的部分:“文件输入是只读的”。太糟糕了...取消丢弃事件不是一个坏主意!没有我希望的那么好,但可能是最好的。顺便说一句,删除多个文件也适用于 Chrome。 Chrome 现在还允许上传目录。都非常kewl,对我的情况没有帮助=(
        【解决方案14】:

        你可以做的是显示一个文件输入并用你的透明放置区域覆盖它,小心使用像file[1]这样的名称。 {确保在您的 FORM 标记中包含 enctype="multipart/form-data"。}

        然后让放置区域通过为文件 2..number_of_files 动态创建更多文件输入来处理额外文件,确保使用相同的基本名称,并适当地填充值属性。

        最后(前端)提交表单。


        处理这个方法所需要做的就是改变你的过程来处理一个文件数组。

        【讨论】:

        • 现在文件输入具有multiple 属性。无需超过 1 个文件输入。但这不是问题。如何将File 对象放入文件输入中?我认为这需要一些代码示例...
        • @Rudie 你不能,这就是问题所在。
        • 不能什么?多种的?是的你可以。我就是这么说的。倍数不是问题。将文件从(拖动的)文件对象中获取到文件输入中,这就是问题所在。
        • @Rudie 可以使用 Chrome/FF 将文件拖入文件输入(使用 files 属性),但我没有在 IE 中管理 - 你运气好吗?
        • @jlb “使用文件属性”是什么意思?你能用相关代码回答吗?我正在寻找的东西在任何浏览器中都不起作用/不存在。
        猜你喜欢
        • 2014-02-05
        • 1970-01-01
        • 1970-01-01
        • 2019-02-27
        • 1970-01-01
        • 2012-06-01
        • 2013-03-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多