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

And*_*aus 14 javascript jquery html5 drag-and-drop

这就是我想要实现的目标:

页面上有多个dropzones.用户应该能够从他们的操作系统中拖动文件并将其放入dropzones.

Dropzones在拖动过程中会突出显示.有两种视觉上不同类型的突出显示:"目标"(例如,元素以虚线边框勾勒出轮廓)和"悬停"(例如,元素获得明亮的背景).

目标突出显示同时在所有下拉列表中应用/删除:

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

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

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

当用户在dropzone上删除文件时,该文件的名称应该出现在dropzone中.

当用户在dropzones外部的页面上删除文件时,应删除所有突出显示的区域,并且不会发生任何其他情况.具体而言,浏览器不应打开已删除的文件.

解决方案应尽可能优雅:不喜欢使用超时,计数dragenter/ dragleave事件和重新应用突出显示等脏兮兮的黑客行为dragover.

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

这是我到目前为止所取得的成就:尝试1,尝试2.

我成功解决的问题

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

    解:

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

    解:

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

    解决方案:使用dragout事件代替dragleave.dragoutjquery.event.dragout插件提供的自定义事件.元素的孩子不会触发此事件.

未解决的问题

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

    自定义dragout插件仅适用于儿童<body>.它既不适用document也不适用window.

    官方dragstartdragend事件根本不会用于拖动文件.这是预期的行为.:(

    dragenterdragleave必然的事件document被触发不仅当鼠标指针进入/离开该文件还可以当指针进入/离开文档的儿童.更糟糕的是event.target,第一次$(document).on('dragenter')出现可能会显示为一个元素(它可以是document它的子元素),最后$(document).on('dragleave')一个元素可以显示为不同的元素,因此您无法通过比较event.targets 来解决问题.

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

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

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

  3. 拖动期间的鼠标指针应当在悬停在dropzones外时显示浏览器的标准"不能放在这里"图标,并且当悬停在dropzones上时,"can drop here"图标.


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

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

1.

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

$(文件).on('dragleave',...必须做的伎俩,请看下面的小提琴.

不,这非常糟糕.

让我们说你听dragenterdragleave事件<body>.每当拖动鼠标指针悬停在任何元素的边缘时,将触发两个事件:

  • dragenter<body>event.target设置到悬停的元件;
  • dragleave<body>event.target设置为悬停的元素的父节点.

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

看看你的代码:

$('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();
    })
Run Code Online (Sandbox Code Playgroud)

如果dropzone驻留在多个嵌套容器中,则跨容器拖动文件将导致目标突出显示类从最外层容器迁移到最里面的容器.由于你.target-highlighting .dropzone在CSS中使用了一个选择器,它看起来像目标突出显示...

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

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

2.

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

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

首先,是使用css指针事件,这是最优雅但最不易浏览的解决方案.它适用于最新的,我个人喜欢它.

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

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

但是,当鼠标指针位于dropzone的内部和外部时,它们完全不适合Target强调应该应用.您必须pointer-events: none;为整个页面禁用鼠标事件(使用任一或覆盖),并且dropzones将不再接受丢弃.

3.

  1. 拖动期间的鼠标指针应当在悬停在dropzones外时显示浏览器的标准"不能放在这里"图标,并且当悬停在dropzones上时,"can drop here"图标.

我不是100%肯定这一点,但在MAC上我似乎无法在拖动时更改图标,因为它使用特殊的默认值.我认为这不可能做到,但是我很乐意学习.

我注意到我的问题已经在Chrome中运行了!请参阅以下链接.

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

一个更好的样板来测试解决方案

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

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

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

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

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

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

我希望它不是太复杂.

Ian*_*hek 6

Privet Andrey!我最近面临的大多数人,都会尝试分享知识.

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

$(document).on('dragleave', … 必须要做的伎俩,请看下面的小提琴.

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

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

首先,是使用css指针事件,这是最优雅但最不易浏览的解决方案.它适用于最新的,我个人喜欢它.

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

3.拖动时鼠标指针在悬停在dropzones外时应显示浏览器的标准"不能放在这里"图标,当悬停在dropzones上时,"can drop here"图标.

我不是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…阻止在浏览器中打开文件并导航.

还有一些非常好看的图书馆,比如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);                                                                                                                                            
    }       
    // …    
    // …    
    // …
Run Code Online (Sandbox Code Playgroud)

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

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

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


And*_*aus 3

dragenter当我了解和事件如何工作时,我发现除了计算元素dragleave之外没有其他选择。event.target

我知道Draghover.js插件非常接近我所需要的。

于是我用代码风格重写了,$.event.special以便更方便使用,并且还修改了它,使其在成功掉落后不会失败。

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

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