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

14

这是我想要实现的:

页面上有多个放置区。用户应该能够从其操作系统中拖动文件并将其放入放置区。
在拖动期间,放置区会被突出显示。有两种视觉上不同的突出显示类型:“目标”(例如,元素用虚线边框勾勒出来)和“悬停”(例如,元素获得明亮的背景)。
目标突出显示同时适用于所有放置区:
当用户将文件拖动到页面上时,所有放置区都应该使用目标突出显示进行突出显示。 当用户将文件拖到页面外部、取消拖放操作或执行拖放操作时,所有放置区的目标突出显示应该被移除。
悬停突出显示只适用于一个放置区:
当用户将文件拖动到放置区时,该放置区应该使用悬停突出显示进行突出显示。 当用户将文件拖到该放置区之外、取消拖放操作或执行拖放操作时,该放置区的目标突出显示应该被移除。
当用户将文件放置在放置区上时,文件名应该出现在放置区内。
当用户将文件放置在放置区之外的页面上时,所有放置区的突出显示应该被移除,不应发生其他任何情况。具体而言,浏览器不应打开已放置的文件。
解决方案应尽可能优雅:不欢迎使用脏技巧,如使用超时、计算dragenter/dragleave事件并在每个dragover上重新应用突出显示。
解决方案应在主要浏览器的最新版本中工作。

这是我目前为止已经取得的成果: 尝试1, 尝试2

我成功解决的问题

  1. Dropping a file outside the dropzone resulted in the browser opening the file.

    Solution:

    $(document).on('dragover drop', function (e) {
        e.preventDefault();
    });
    
  2. Dropping a file into a dropzone generates a drop event with target equal to a child of the dropzone rather than the dropzone itself.

    Solution:

    $dropzones.on( 'drop', function (event) {
    
      /* ... */
    
      // Find the dropzone responsible for the event
      $targetDropzone = $(event.target).closest($dropzones);
    
      /* ... */
    });
    
  3. Hovering the file over dropzone's children generates multiple dragleave events, making the Hover highlighting disappear immediately (the Hover highlighting should be removed from a dropzone when the mouse cursor leaves the dropzone, so it's bound to the dragleave event).

    Solution: use the dragout event instead of dragleave. dragout is a custom event provided by the jquery.event.dragout plugin. This event won't fire for element's children.

未解决的问题
无法检测拖动文件离开documentwindow的时刻,以便执行“删除目标高亮”命令。
自定义dragout插件仅设计用于<body>的子元素。它不适用于documentwindow
官方的dragstartdragend事件在拖动文件时根本不起作用。这是一种预期行为。:(
绑定到documentdragenterdragleave事件不仅在鼠标指针进入/离开文档时触发,而且在指针进入/离开文档的子元素时也会触发。更糟糕的是,第一个$(document).on('dragenter')出现的event.target可能会出现为一个元素(可以是document或其子元素),而最后一个$(document).on('dragleave')的出现可能会出现为不同的元素,因此您无法通过比较event.target来解决问题。
由于这些问题,我未能优雅地跟踪鼠标离开文档的时刻。
我尝试使用旨在解决此问题的draghover插件。我设法使其正常工作(仅在Chrome中测试),但在第一次成功拖放后,它将停止正确工作。请参见失败的尝试here
虽然无法在视觉上告知,但当文件悬停在文档上方时,dragenter事件会被触发多次。因此,高亮显示应该应用多次而不是一次
拖动时鼠标指针应该在落点之外悬停时显示浏览器的标准“不能在此处放置”图标,在悬停在落点上方时显示“可以在此处放置”图标

更新 2014-03-16 15:30,回复 Ian Bytchek 的答案

嗨,同志!谢谢你详细的回答。不幸的是,你的解决方案存在一些问题。

1.

  1. 无法检测拖动文件离开文档或窗口的时刻,以便执行“删除目标高亮”命令。

$(document).on('dragleave', … 应该可以解决这个问题,请参见下面的 fiddle。

不,这很糟糕。

假设您在 <body> 上监听 dragenterdragleave 事件。每当拖动鼠标指针悬停在任何元素的边缘上时,会触发两个事件:

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

如果拖放区域位于多个嵌套容器中,将文件拖动到这些容器上会导致目标高亮类从最外层容器迁移到最内层容器。由于您在CSS中使用了.target-highlighting .dropzone选择器,看起来目标高亮是固定的,直到您将文件拖动到不是dropzone父元素的元素上时才消失,比如侧边栏或dropzone本身。
这是不可接受的。目标高亮应该只在拖放文件到页面上时出现,并在拖出页面或拖放完成(通过放置或取消)时消失。
2.
尽管视觉上无法确定,但在文件悬停在文档上方时,dragenter事件会被多次触发。因此,高亮显示会被应用多次而不是一次。 该事件在鼠标进入某个元素时触发。因此,在拖动页面时,它会进入许多元素并多次触发。为避免这种情况,您需要在每个droparea下面“禁用”所有内容,有两种方法可以实现此目的。 第一种方法是使用CSS指针事件,这是最优雅但最不浏览器友好的解决方案。它适用于大多数最新版本,并且我个人非常喜欢它。 第二种方法是在droparea上创建一个透明覆盖层-鼠标仅会击中该覆盖层而不是下面的元素,这将防止多个拖动进入事件的发生。 这些解决方案可用于触发当鼠标指针位于dropzone内部时应用的Hover高亮显示。(顺便说一句,我已经找到了更优美的解决方案:一个dragout事件插件,请参见上面“已解决问题”部分中的#3。)
但它们对于在鼠标指针位于投放区域内外时应用的目标高亮显示完全不合适。您必须禁用整个页面的鼠标事件(使用pointer-events: none;或叠加层),并且投放区域将不再接受投放。
3. 拖动期间的鼠标指针应在悬停在投放区域外部时显示浏览器的标准“无法在此处投放”图标,并在悬停在投放区域上方时显示“可以在此处投放”图标。
我不确定这一点,但在MAC上似乎无法更改拖动时的图标,因为它使用特殊的默认图标。我认为这是不可能的,但很想了解其他情况。
我注意到我所要求的在Chrome中已经起作用了!请参见下面的链接。
但Firefox不会更改鼠标指针。 :(
一个更好的样板文件以测试解决方案
以下是翻译:

还有一些看起来不错的库,比如http://www.dropzonejs.com/,虽然我没有使用过,但它们是一个很好的“灵感”来源。

我见过这个插件。它并没有解决上述问题。目标高亮根本没有应用,当你将文件拖到dropzone上时,悬停高亮会闪烁。

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

我的个人建议是采用每个droparea插件的方法,而不是像您的示例中那样采用每个页面的方法。一旦添加了上传逻辑、验证等,这些组件往往会变得非常庞大。

你完全正确。在我的项目中,我使用了精妙的jQuery UI Widget Factory。这是一种定义与彼此独立的jQuery插件行为的方法。

我创建了一个更好的样板文件,用于测试进一步的解决方案:http://jsbin.com/rupaloba/4/edit?html,css,js,output。希望它不会太复杂。
3个回答

6

你好,Andrey!我最近遇到了大部分这些问题,我会尝试分享我的经验。

1. 无法检测拖动文件离开文档或窗口的时刻,以便执行“删除目标高亮”命令。

$(document).on('dragleave', … 可以解决这个问题,可以参考下面的 fiddle 示例。

2. 虽然在视觉上无法看出来,但是当文件悬停在文档上方时,dragenter 事件会被多次触发。因此,高亮会被多次应用而不是一次。

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

首先,使用css pointer events,这是最优雅但最不兼容的解决方案。它适用于大多数最新的浏览器,我个人非常喜欢它。

其次,在 droparea 上面创建一个透明的覆盖层 - 鼠标只会触及它而不是下面的元素,这将防止多次拖入事件。

3. 在拖动时,鼠标指针在悬停在 dropzones 外部时应显示浏览器的标准“无法在此处放置”图标,在悬停在 dropzones 上方时应显示“可以在此处放置”图标。

我不能完全确定这一点,但在 MAC 上我似乎无法在拖动时更改图标,因为它使用特殊的默认图标。我认为这是不可能的,但很想了解其他情况。你可以使用不同的设计,比如背景颜色变化或者添加一个类似光标的 div,跟随鼠标移动。示例展示了使用背景的技巧。

带有示例的 Fiddle:http://jsfiddle.net/ianbytchek/Q6uEp/8/


这些都是关于问题的内容。我的个人建议是使用每个 droparea 插件方法,而不是像你的示例那样使用每个页面方法。一旦添加了上传逻辑、验证等,这些组件往往会变得非常庞大。简而言之:

  1. 一个基础的 jQuery 插件通过所需的逻辑扩展为两个(或更多)组件。
  2. 它处理所有的拖放业务 + 共享基本 css / html 以保持一切DRY。
  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);                                                                                                                                            
    }       
    // …    
    // …    
    // …

更新1(2014年3月16日19:00)

@Andrey'lolmaus'Mikhaylov,你在这些问题上是正确的——一旦你开始嵌套东西,它就会变得混乱。我进一步尝试了一下,结果发现它真的很棘手,所以我很感兴趣。我用dragenterdragleave事件尝试解决它,但是我相信解决方案是存在的。不过,我确实想出了一些不太吸引人的东西:http://jsfiddle.net/ianbytchek/Q6uEp/14/

这是一个相当简洁的解决方案,我认为它比其他方法得到的更加干净。同时,由于所有坐标检查都感觉有点黑客行为。我看腻了,如果它被打磨成更好/更整洁的版本,那就太好了。


嗨,Ian!我的回复无法适应评论框,所以我已经编辑了问题以包含回复。请阅读并回复。如果您也通过编辑答案进行回复,请不要忘记写一个跟进评论,以便我能注意到您的编辑。 - Andrey Mikhaylov - lolmaus
@Andrey'lolmaus'Mikhaylov,已更新帖子。你说得对 - 我错过了一些要点。看看新版本是否解决了问题。 - Ian Bytchek
嗨,伊恩,你的更新1解决方案似乎也不起作用:当我将文件拖到放置区时,目标高亮显示会被禁用。我最终想出了一个可行的解决方案。我使用了jquery.draghover.js,对其进行了修改以在成功拖放后保持,并将其移植到$.event.special代码风格中,使其更加方便使用。我将结果插件称为“dragbetter”(不是最终名称)。请查看:http://jsbin.com/rupaloba/12/edit 我将在Github上发布它,在这里发布答案,并且,嗯,领取自己的赏金。 - Andrey Mikhaylov - lolmaus
1
我非常确定你不能领取自己的赏金。做正确的事情,把它给伊恩。 - net.uk.sweet
1
干得好!我不想看到赏金消失无踪。在我看来,即使你最终没有使用他的解决方案,Ian也值得得到它。 - net.uk.sweet
显示剩余2条评论

3

甜蜜,谢谢。这实际上是一个相当不错的想法。你应该尽量避免使用jQuery来处理数组,并坚持使用本地js。这样会快999999999倍。此外,不要使用jQuery obj.size(),它已经被弃用并替换为'obj.length`。 - Ian Bytchek
谢谢Ian,我已经创建了相应的问题。 - Andrey Mikhaylov - lolmaus

2
  1. 拖拽时鼠标指针在放置区域之外时应显示浏览器标准的“无法放置”图标,在放置区域上方时应显示“可以放置”图标。

使文档不可放置:

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

调整你的投放区拖动图标,使其符合你的要求:
$("[selector]").on('dragover', function(e) {
    e.dataTransfer.dropEffect = 'copy'; // or 'move' or 'link'
});

当然,事件订阅不一定要使用JQuery。您可以选择自己喜欢的方式进行订阅。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接