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

标签 javascript jquery html drag-and-drop

这是我想要实现的目标:

There are multiple dropzones on the page. Users should be able to drag files from their OS and drop them into the dropzones.

Dropzones get highlighted during dragging. There are two visually different types of highlighting: "Target" (for example, the element gets outlined with a dashed border) and "Hover" (for example, the element gets a bright background).

The Target highlighting is applied/removed on/from all dropzones at once:

  • When a user drags a file over the page, all dropzones should be highlighted with the Target highlighting.
  • When a user drags the file outside the page, or cancels the drag/drop operation, or performs the drop, then the Target highlighting should be removed from all the dropzones.

The Hover highlighting should be applied to only one dropzone:

  • When a user drags a file over a dropzone, that dropzone should be highlighted with the Hover highlighting.
  • When a user drags the file outside that dropzone, or cancels the drag/drop operation, or performs the drop, then the Target highlighting should be removed from the dropzone.

When a user drops a file on a dropzone, the file's name should appear inside the dropzone.

When a user drops a file on the page outside dropzones, all dropzones highlighting should be removed and nothing else should happen. Specifically, the dropped file should not be opened by the browser.

The solution should be as much graceful as possible: dirty hacks like using timeouts, counting dragenter/dragleave events and reapplying highlighting on every dragover are not welcome.

The solution should work in latest versions of major browsers.



以下是我到目前为止所取得的成果:attempt 1 , attempt 2 .

我已经成功解决的问题
  • 将文件拖放到拖放区之外会导致浏览器打开该文件。

    解决方案:

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

    解决方案:

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

    解决方法:使用dragout事件而不是 dragleave . dragoutjquery.event.dragout 提供的自定义事件插入。此事件不会为元素的 child 触发。

  • Unresolved 问题
  • 无法检测拖动文件离开 document 的时刻或 window ,以便可以执行“删除目标突出显示”命令。

    定制dragout插件旨在仅适用于 <body> 的 child .它不适用于 document也不是 window .

    官方dragstartdragend事件对于拖动文件根本不起作用。这是一个 expected behavior . :(
    dragenterdragleave事件绑定(bind)到 document不仅在鼠标指针进入/离开文档时触发,而且在指针进入/离开文档的子级时触发。更糟糕的是,event.target第一 $(document).on('dragenter')出现可能作为一个元素出现(它可以是 document 或其子元素),以及最后一次出现的 $(document).on('dragleave')可以显示为不同的元素,因此您无法通过比较 event.target 来解决问题s。

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

    我曾尝试使用 draghover旨在解决此问题的插件。我设法让它工作(仅在 Chrome 中测试),但它会在第一次成功下降后停止正常工作。查看失败的尝试 here .
  • 虽然无法从视觉上分辨,dragenter当文件悬停在文档上时,事件会被触发多次。因此,突出显示应用多次而不是一次 .
  • 拖动过程中的鼠标指针应显示浏览器的标准 “不能放在这里”图标 当悬停在 dropzones 和 外时“可以放在这里”图标当悬停在 dropzones 上时。


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

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

    1.

    1. Unable to detect the moment when the dragged file leaves the document or window, so that a "remove Target highlighting" command could be executed.


    $(document).on('dragleave', ... 必须做到这一点,请参阅下面的 fiddle 。

    不,这很糟糕。

    假设您在听 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();
        })
    

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

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

    这是 Not Acceptable 。只有在将文件拖到页面上并在将文件拖出页面或拖动完成(通过放置或取消)时将其移除时,才会出现目标突出显示。

    2.

    1. Though it's impossible to tell visually, the dragenter event gets fired many times while the file hovers over the document. Thus, highlighting is applied multiple times instead of one.


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

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

    其次,是在 droparea 的顶部创建一个透明的叠加层——
    鼠标只会击中那个而不是下面的元素
    将防止多次拖动进入事件。

    这些解决方案对于触发悬停突出显示是可以接受的,当鼠标指针在放置区内时应用。 (顺便说一句,我为此找到了一个更优雅的解决方案:dragout 事件插件,请参阅上面已解决问题部分中的 #3。)

    但是它们完全不适合在鼠标指针位于放置区内部和外部时应用的目标突出显示。您必须为整个页面禁用鼠标事件(使用 pointer-events: none; 或覆盖),并且放置区将不再接受放置。

    3.

    1. The mouse pointer during drag should display the browser's standard "can't drop here" icon when hovering outside dropzones and the "can drop here" icon when hovering over dropzones.

    I'm not 100% certain on this, but on MAC I can't seem to change the icon while dragging, as it uses the special default one. I assume this can't be done, but would love to learn otherwise.



    我注意到我所问的已经在 Chrome 中起作用了!请参阅下面的链接。

    不过,Firefox 不会更改鼠标指针。 :(

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

    There are also some pretty good looking libraries, like http://www.dropzonejs.com/, which I don't have experience with, yet they are a good source of "inspiration".



    我见过这个插件。它不能解决上述问题。根本不应用目标突出显示,并且当您将文件拖到放置区上时,悬停突出显示会闪烁。

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

    My personal advice would be to use per-droparea plugin approach, not per-page approach as in your examples. Those components tend to grow pretty big once you add the uploading logic, validation, etc.



    你是绝对正确的。在我的项目中,我使用了很棒的 jQuery UI Widget Factory。它是一种定义相互独立的 jQuery 插件的方法。

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

    我希望它不会太复杂。

    最佳答案

    女贞德安德烈!我最近遇到了其中的大部分,将尝试分享知识。

    1、无法检测到拖拽文件离开文档或窗口的时刻,因此可以执行“删除目标突出显示”命令。
    $(document).on('dragleave', …必须做到这一点,请参阅下面的 fiddle 。

    2. 虽然无法直观地分辨,但当文件悬停在文档上时,draenter 事件会被多次触发。因此,突出显示应用了多次而不是一次。

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

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

    其次,是在 droparea 的顶部创建一个透明的覆盖层——鼠标只会点击它而不是下面的元素,这将防止多次拖动进入事件。

    3. 拖动期间的鼠标指针在悬停区域外时应显示浏览器的标准“不能放在这里”图标,在悬停区域上悬停时应显示“可以放在这里”图标。

    我对此不是 100% 确定,但在 MAC 上我似乎无法在拖动时更改图标,因为它使用特殊的默认图标。我认为这无法完成,但很想学习其他方法。您可以使用不同的设计,例如更改背景颜色,或者添加一个将跟随鼠标的类似光标的 div。该示例显示了带有背景的技巧。

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

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

  • 使用两个(更多)组件所需的逻辑扩展了一个基本的 jQuery 插件。
  • 它处理所有拖放业务 + 共享基础 css/html 以保持一切干燥。
  • 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-03-16 19:00)

    @Andrey'lolmaus'Mikhaylov,你在这些点上是对的——一旦你开始嵌套东西,它就会一团糟。它玩得更远,结果证明它是一个真正的婊子,所以我很感兴趣。我用 dragenter 解决它的运气不佳和 dragleave事件,但我确信解决方案是存在的。不过我确实想出了一些不太吸引人的东西:http://jsfiddle.net/ianbytchek/Q6uEp/14/

    这是一个相当简洁的解决方案,我认为它比使用其他方法可以获得的更干净。同时,所有坐标检查都感觉有点麻烦。我看着它很累,如果它被打磨成更好/更整洁的版本,那就太好了。

    关于javascript - 在页面上拖放文件——缺乏一致的解决方案,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22308882/

    相关文章:

    javascript - 穿过带有文本/无图像的 Canvas 元素

    Javascript循环不等待Firebase完成上传多个文件

    javascript - Gruntjs 让文件消失

    javascript - Uploadify - 如何使用 scriptData 发送文件大小

    java - Base64转图像文件

    javascript - if/else 语句显示/隐藏图像

    html - 如何仅使用 css 在 Div 中切换效果?

    javascript - 在用 CSS 创建的框中插入 PNG 图像?

    javascript - 字体未应用于图像

    jquery - 无法在 "beforeunload"事件中设置背景图像属性