在页面上拖放文件 - 缺乏一致的解决方案

时间:2014-03-10 18:56:32

标签: javascript jquery html5 drag-and-drop

这是我想要实现的目标:

  

页面上有多个dropzones。用户应该能够拖动   来自他们操作系统的文件并将它们放入dropzones。

     

Dropzones在拖动过程中会突出显示。有两种视觉上不同的类型   突出显示:"目标" (例如,元素以虚线表示   边界)和" Hover" (例如,元素获得明亮的背景)。

     

目标突出显示在所有下拉区域中应用/删除   一旦:

     
      
  • 当用户在页面上拖动文件时,所有下拉区域都应突出显示目标突出显示。
  •   
  • 当用户在页面外拖动文件,或取消拖放操作或执行删除时,目标   应从所有下拉区域中删除突出显示。
  •   
     

悬停突出显示应仅应用于一个dropzone:

     
      
  • 当用户在dropzone上拖动文件时,应使用悬停突出显示突出显示该dropzone。
  •   
  • 当用户将文件拖放到该dropzone之外,或者取消拖放操作或执行删除时,目标   应该从dropzone中删除突出显示。
  •   
     

当用户在dropzone上删除文件时,应显示该文件的名称   在dropzone内部。

     

当用户在dropzones外的页面上删除文件时,所有的dropzones   应该删除突出显示,不应该发生任何其他事情。   具体而言,浏览器不应打开已删除的文件。

     

解决方案应该尽可能优雅:不喜欢使用超时,计算dragenter / dragleave事件并在每个dragover重新应用突出显示等肮脏的黑客行为。

     

该解决方案应该适用于主流浏览器的最新版本。

以下是我迄今为止取得的成就:attempt 1attempt 2

我成功解决的问题

  1. 删除dropzone之外的文件会导致浏览器打开文件。

    解决方案:

    $(document).on('dragover drop', function (e) {
        e.preventDefault();
    });
    
  2. 将文件拖放到dropzone会生成drop事件,其目标等于dropzone的子级而不是dropzone本身。

    解决方案:

    $dropzones.on( 'drop', function (event) {
    
      /* ... */
    
      // Find the dropzone responsible for the event
      $targetDropzone = $(event.target).closest($dropzones);
    
      /* ... */
    });
    
  3. 将文件悬停在dropzone的子级上会生成多个dragleave个事件,使得悬停突出显示立即消失(当悬停突出显示时,应从删除区域删除悬停突出显示鼠标光标离开dropzone,因此它绑定到dragleave事件。

    解决方案:使用dragout事件代替dragleavedragoutjquery.event.dragout插件提供的自定义事件。这个活动不会为元素的孩子开火。

  4. 未解决的问题

    1. 无法检测到拖动的文件离开documentwindow的时刻,以便"删除目标突出显示"命令可以执行。

      自定义dragout插件旨在仅适用于<body>的子级。它既不适用于document也不适用于window

      官方dragstartdragend事件对于拖动文件根本不起作用。这是expected behavior。 :(

      绑定到dragenter的{​​{1}}和dragleave事件不仅在鼠标指针进入/离开文档时触发,而且在指针进入/离开文档的子节点时触发。更糟糕的是,第一个document出现的event.target可能会显示为一个元素(可以是$(document).on('dragenter')或其子元素),最后一个document可能会显示为一个不同的元素,因此您无法通过比较$(document).on('dragleave')来解决问题。

      由于这些问题,我无法优雅地跟踪鼠标离开文档的时刻。

      我试图使用旨在解决此问题的draghover插件。我设法使其工作(仅在Chrome中测试),但在第一次成功下降后它将停止正常工作。查看失败的尝试here

    2. 虽然无法用视觉方式判断,但当文件悬停在文档上时,event.target事件会多次触发。因此,突出显示应用多次,而不是一次

    3. 拖动过程中的鼠标指针应显示浏览器的标准&#34;不能放在此处&#34;将鼠标悬停在dropzones之外时,&#34;可以放在此处&#34;


    4. UPD 2014-03-16 15:30,回复Ian Bytchek的回答

      你好同志!谢谢你的详细回复。不幸的是,您的解决方案存在许多问题。

      1

        
          
            
      1. 无法检测拖动的文件离开文档或窗口的时刻,以便&#34;删除目标突出显示&#34;命令可以执行。
      2.        
           

      $(文件).on(&#39; dragleave&#39;,...必须要做的伎俩,请看下面的小提琴。

      不,这非常糟糕。

      让我们说您在dragenter上收听dragenterdragleave个事件。每当拖动鼠标指针悬停在任何元素的边缘时,将触发两个事件:

        {li} <body> dragenter <body>设置为悬停元素; 在event.target上{li> dragleave<body>设置为悬停元素的父级。

      我认为目标突出显示将应用event.target选择器。通过使用.dropzone.target-higlighing选择器应用目标突出显示,您做了一个诙谐的技巧。

      看看你的代码:

      .target-highlighting .dropzone

      如果dropzone驻留在多个嵌套容器中,则跨容器拖动文件将导致目标突出显示类从最外层容器迁移到最里面的容器。由于您在CSS中使用了$('body') .on('dragenter', function (event) { $(event.target).addClass('target-highlighting'); event.preventDefault(); }) .on('dragleave drop', function (event) { $(event.target).removeClass('target-highlighting-class'); event.preventDefault(); }) 选择器,看起来目标突出显示... ...

      ...直到您将文件拖到不属于dropzone父母的元素上。它可以是侧边栏或dropzone本身。发生这种情况时,.target-highlighting .dropzone选择器停止应用,目标突出显示消失。

      这是不可接受的。目标突出显示应仅在将文件拖到页面上时显示,并在文件被拖出页面时或拖动完成时删除(通过删除或取消)。

      2

        
          
            
      1. 虽然不可能直观地说出,但是当文件悬停在文档上时,dragenter事件会被多次触发。从而,     突出显示多次应用而不是一次。
      2.        
           

      每次鼠标进入某个元素时都会触发该事件。所以,   当您在页面上拖动时,它会输入许多元素并触发许多元素   倍。要避免这种情况,您需要&#34;禁用&#34;每个下面的一切   droparea,有两种方法可以做到这一点。

           

      首先,是使用css指针事件,这是最优雅的,但是   最不友好的解决方案。它适用于最近的那些,我   个人喜欢它。

           

      其次,是在droparea上创建一个透明的叠加层 -   鼠标只会击中它,而不是下面的元素   将阻止多次拖拽输入事件。

      这些解决方案可用于触发鼠标指针位于dropzone内部时应用的悬停突出显示。 (顺便说一下,我发现了一个更优雅的解决方案:一个.target-highlighting .dropzone事件插件,请参阅上面解决问题部分中的#3。)

      但它们完全不适合Target突出显示,当鼠标指针位于dropzone的内部和外部时应该应用。您必须为整个页面禁用鼠标事件(使用dragout或覆盖),并且dropzones将不再接受丢弃。

      3

        
          
      1. 拖动过程中的鼠标指针应显示浏览器的标准&#34;不能放在这里&#34;图标   当在dropzones外面徘徊时,&#34;可以放在这里&#34;图标何时   悬停在dropzones上。
      2.         

        我不是100%肯定这一点,但在MAC上,我似乎无法改变   拖动时的图标,因为它使用特殊的默认值。我假设这个   无法完成,但又愿意学习。

      我注意到我的问题已在Chrome中使用了!请参阅以下链接。

      Firefox不会改变鼠标指针。 :(

      上测试解决方案的更好的样板
        

      还有一些非常好看的图书馆,比如http://www.dropzonejs.com/,我没有经验,但它们是&#34;灵感的良好来源&#34;。

      我已经看过这个插件了。它不能解决上述问题。目标突出显示根本不应用,当您在dropzone上拖动文件时,悬停突出显示闪烁。

      另外,我无法使用它,因为我有自己的dropzone实现。例如,我的dropzone允许用户对添加到dropzone的文件进行排序。我只需要一个解决方案来处理拖动事件。

        

      我的个人建议是使用per-droparea插件方法,而不是像示例中的每页方法。一旦添加了上传逻辑,验证等,这些组件就会变得非常大。

      你是绝对正确的。在我的项目中,我使用了精彩的jQuery UI Widget Factory。它是一种定义jQuery插件的方法,它们彼此分开。

      在这里,我创建了一个更好的样板来测试更多解决方案:http://jsbin.com/rupaloba/4/edit?html,css,js,output

      我希望它不会太复杂。

3 个答案:

答案 0 :(得分:6)

Privet Andrey!我最近遇到了大部分人,都会尝试分享知识。

<强> 1。无法检测拖动文件离开文档或窗口的时刻,因此可以执行“删除目标突出显示”命令。

$(document).on('dragleave', …必须做到这一点,请看下面的小提琴。

<强> 2。虽然不可能直观地说出,但是当文件悬停在文档上时,dragenter事件会被多次触发。因此,突出显示应用多次而不是一次。

每次鼠标进入某个元素时都会触发该事件。因此,当您在页面上拖动时,它会输入许多元素并多次触发。为避免这种情况,您需要“禁用”每个droparea下面的所有内容,有两种方法可以执行此操作。

首先,是使用css pointer events,这是最优雅但最不易浏览的解决方案。它适用于最近的,我个人喜欢它。

其次,是在droparea上创建一个透明的叠加层 - 鼠标只会点击它而不是下面的元素,这将阻止多个拖动输入事件。

第3。拖动时鼠标指针在悬停在dropzones外时应显示浏览器的标准“不能放在此处”图标,并且当将鼠标悬停在放置区域时显示“可以放在此处”图标。

我对此并不是100%肯定,但在MAC上我似乎无法在拖动时更改图标,因为它使用了特殊的默认图标。我认为这不可能做到,但是我很乐意学习。您可以使用不同的设计,例如背景颜色更改,也可以添加跟随鼠标的光标div。该示例显示了背景技巧。

摆弄示例:http://jsfiddle.net/ianbytchek/Q6uEp/8/


这涉及到问题。我的个人建议是使用per-droparea插件方法,而不是像示例中的每页方法。一旦你添加了上传逻辑,验证等,这些组件往往会变得非常大。简而言之:

  1. 基础jQuery插件使用两个(更多)组件中所需的逻辑进行扩展。
  2. 它处理所有拖放业务+共享基础css / html以保持一切干燥。
  3. index.js $(document).on('dragenter dragover drop', function…中的某处阻止在浏览器中打开文件并导航。
  4. 还有一些非常好看的图书馆,比如http://www.dropzonejs.com/,我没有经验,但它们是“灵感”的良好来源。

    我还在我的代码中使用以下内容来掩盖旧浏览器中的pointer-events(但从未真正测试过) - 它会检查鼠标是否在元素的边界之外。

    // jQuery event configuration.
    jQuery.event.props.push('dataTransfer', 'pageX', 'pageY');
    
    element.on('dragleave', function ( event) {                                                                                                                                        
        var elementPosition = element.offset();                                                                                                                                                 
        var elementWidth = element.width();                                                                                                                                                     
        var elementHeight = element.height();                                                                                                                                                   
    
        if (event.pageX < elementPosition.left || event.pageX > elementPosition.left + elementWidth || event.pageY < elementPosition.top || event.pageY > elementPosition.top + elementHeight) {
            element.removeClass(States.HIGHLIGHTED);                                                                                                                                            
        }       
        // …    
        // …    
        // …
    

    更新1(2014-03-16 19:00)

    @ Andrey'lolmaus'Mikhaylov,你在这些方面是正确的 - 一旦你开始筑巢,这是一团糟。它进一步发挥它,它结果是一个真正的婊子,所以我很感兴趣。我用dragenterdragleave事件解决它的运气很少,但我确信解决方案存在。我确实提出了一些不太吸引人的东西:http://jsfiddle.net/ianbytchek/Q6uEp/14/

    这是一个相当简洁的解决方案,我认为它会比使用其他apporaches更清晰。与此同时,所有的坐标检查都让人觉得有点蠢蠢欲动。我厌倦了看它,如果它被打磨成更好/更整洁的版本,那就太棒了。

答案 1 :(得分:3)

当我了解dragenterdragleave事件如何发挥作用时,我发现除了计算event.target元素之外别无选择。

我知道draghover.js插件非常接近我需要的地方。

所以我用$.event.special代码样式重写了它,以便更方便地使用,并对其进行了修改,以便在成功删除后不会失败。

你在这里:https://github.com/lolmaus/jquery.dragbetter

演示:http://jsbin.com/rupaloba/15/edit?html,js,output

答案 2 :(得分:2)

  
      
  1. 拖动过程中的鼠标指针应显示浏览器的标准&#34;不能放在这里&#34;徘徊在dropzones外面的图标,&#34;可以放在这里&#34;将鼠标悬停在dropzones上时的图标。
  2.   

使文档不可丢弃:

$(document).on('dragover drop', function(e) {
        e.preventDefault();
        e.stopPropagation();
        e.originalEvent.dataTransfer.dropEffect = "none";
    });

根据需要调整dropzone拖动图标:

$("[selector]").on('dragover', function(e) {
    e.dataTransfer.dropEffect = 'copy'; // or 'move' or 'link'
});

当然,事件订阅可能不适用于JQuery。按照你喜欢的方式订阅。