2010-06-29 5 views
19

Ich möchte den Drop-Bereich markieren können, sobald der Cursor, der eine Datei enthält, in das Browser-Fenster gelangt, genau wie Google Mail. Aber ich kann es nicht funktionieren lassen, und ich habe das Gefühl, dass ich etwas wirklich Offensichtliches vermisse.Wie erkenne ich ein HTML5-Drag-Ereignis, das in das Fenster gelangt und es verlässt, wie es in Gmail der Fall ist?

ich immer wieder versuchen, so etwas zu tun:

this.body = $('body').get(0) 
this.body.addEventListener("dragenter", this.dragenter, true) 
this.body.addEventListener("dragleave", this.dragleave, true)` 

Aber das löst die Ereignisse immer dann, wenn sich der Cursor über bewegt und aus anderen Elementen als Körper, die Sinn macht, aber absolut nicht funktioniert. Ich könnte ein Element auf alles legen, das ganze Fenster abdecken und erkennen, aber das wäre eine schreckliche Art, das zu tun.

Was fehlt mir?

+4

Neben den Antworten unter: bemerkte ich, dass die Abfolge der Ereignisse zumindest unter Chrom ist: ENTER ENTER VERLASSEN ENTER VERLASSEN ... VERLASSEN was bedeutet, wenn Sie Zählung halten von Ein- und Ausgängen kann man zwischen der initialen Eingabe und den internen Enter/Leave-Sequenzen unterscheiden PS: Entschuldigung für die satte Formatierung ... –

+0

Du bist der Mann @MartinWawrusch! Danke für das – aceofspades

Antwort

1

Ihr drittes Argument zu addEventListener ist true, wodurch der Listener während der Erfassungsphase ausgeführt wird (eine Visualisierung finden Sie unter http://www.w3.org/TR/DOM-Level-3-Events/#event-flow). Das bedeutet, dass es die Ereignisse erfasst, die für seine Nachkommen bestimmt sind - und für den Körper, der alle Elemente auf der Seite bedeutet. In Ihren Handlern müssen Sie prüfen, ob das Element, für das sie ausgelöst werden, der Körper selbst ist. Ich gebe dir meine sehr schmutzige Art, es zu tun. Wenn jemand einen einfacheren Weg kennt, der tatsächlich Elemente vergleicht, würde ich es gerne sehen.

this.dragenter = function() { 
    if ($('body').not(this).length != 0) return; 
    ... functional code ... 
} 

Dieser findet den Körper und entfernt this aus der Menge der Elemente gefunden. Wenn der Satz nicht leer ist, war this nicht der Körper, also mögen wir das nicht und kehren zurück. Wenn thisbody ist, ist das Set leer und der Code wird ausgeführt.

Sie können mit einem einfachen if (this == $('body').get(0)) versuchen, aber das wird wahrscheinlich kläglich scheitern.

0

Haben Sie bemerkt, dass es eine Verzögerung gibt, bevor die Dropzone in Google Mail verschwindet? Meine Vermutung ist, dass sie es auf einem Timer (~ 500ms) verschwinden lassen, der durch Dragover oder ein ähnliches Ereignis zurückgesetzt wird.

Der Kern des von Ihnen beschriebenen Problems besteht darin, dass Dragleave selbst dann ausgelöst wird, wenn Sie in ein untergeordnetes Element ziehen. Ich versuche einen Weg zu finden, dies zu erkennen, aber ich habe noch keine elegant saubere Lösung.

1

Ich hatte selbst Probleme damit und kam zu einer brauchbaren Lösung, obwohl ich nicht verrückt nach einem Overlay bin.

hinzufügen ondragover, ondragleave und ondrop zu Fenster

hinzufügen ondragenter, ondragleave und ondrop zu einem Overlay und ein Zielelement

Wenn Abfall tritt auf dem Fenster oder Overlay, wird sie ignoriert, während das Ziel Griff der Tropfen wie gewünscht. Der Grund dafür, dass wir ein Overlay brauchen, ist, dass ondragleave jedes Mal ausgelöst wird, wenn ein Element verschoben wird. Das Overlay verhindert dies, während die Drop-Zone einen höheren Z-Index erhält, sodass die Dateien gelöscht werden können. Ich verwende einige Code-Snippets, die in anderen Drag & Drop-Fragen gefunden wurden.Hier ist die vollständige HTML:

<!DOCTYPE html> 
<html> 
    <head> 
     <title>Drag and Drop Test</title> 
     <meta http-equiv="X-UA-Compatible" content="chrome=1" /> 
     <style> 
     #overlay { 
      display: none; 
      left: 0; 
      position: absolute; 
      top: 0; 
      z-index: 100; 
     } 
     #drop-zone { 
      background-color: #e0e9f1; 
      display: none; 
      font-size: 2em; 
      padding: 10px 0; 
      position: relative; 
      text-align: center; 
      z-index: 150; 
     } 
     #drop-zone.hover { 
      background-color: #b1c9dd; 
     } 
     output { 
      bottom: 10px; 
      left: 10px; 
      position: absolute; 
     } 
     </style> 
     <script> 
      var windowInitialized = false; 
      var overlayInitialized = false; 
      var dropZoneInitialized = false; 

      function handleFileSelect(e) { 
       e.preventDefault(); 

       var files = e.dataTransfer.files; 
       var output = []; 

       for (var i = 0; i < files.length; i++) { 
        output.push('<li>', 
         '<strong>', escape(files[i].name), '</strong> (', files[i].type || 'n/a', ') - ', 
         files[i].size, ' bytes, last modified: ', 
         files[i].lastModifiedDate ? files[i].lastModifiedDate.toLocaleDateString() : 'n/a', 
         '</li>'); 
       } 

       document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>'; 
      } 

      window.onload = function() { 
       var overlay = document.getElementById('overlay'); 
       var dropZone = document.getElementById('drop-zone'); 

       dropZone.ondragenter = function() { 
        dropZoneInitialized = true; 
        dropZone.className = 'hover'; 
       }; 
       dropZone.ondragleave = function() { 
        dropZoneInitialized = false; 
        dropZone.className = ''; 
       }; 
       dropZone.ondrop = function (e) { 
        handleFileSelect(e); 
        dropZoneInitialized = false; 
        dropZone.className = ''; 
       }; 

       overlay.style.width = (window.innerWidth || document.body.clientWidth) + 'px'; 
       overlay.style.height = (window.innerHeight || document.body.clientHeight) + 'px'; 
       overlay.ondragenter = function() { 
        if (overlayInitialized) { 
         return; 
        } 

        overlayInitialized = true; 
       }; 
       overlay.ondragleave = function() { 
        if (!dropZoneInitialized) { 
         dropZone.style.display = 'none'; 
        } 
        overlayInitialized = false; 
       }; 
       overlay.ondrop = function (e) { 
        e.preventDefault(); 
        dropZone.style.display = 'none'; 
       }; 

       window.ondragover = function (e) { 
        e.preventDefault(); 

        if (windowInitialized) { 
         return; 
        } 

        windowInitialized = true; 
        overlay.style.display = 'block'; 
        dropZone.style.display = 'block'; 
       }; 
       window.ondragleave = function() { 
        if (!overlayInitialized && !dropZoneInitialized) { 
         windowInitialized = false; 
         overlay.style.display = 'none'; 
         dropZone.style.display = 'none'; 
        } 
       }; 
       window.ondrop = function (e) { 
        e.preventDefault(); 

        windowInitialized = false; 
        overlayInitialized = false; 
        dropZoneInitialized = false; 

        overlay.style.display = 'none'; 
        dropZone.style.display = 'none'; 
       }; 
      }; 
     </script> 
    </head> 

    <body> 
     <div id="overlay"></div> 
     <div id="drop-zone">Drop files here</div> 
     <output id="list"><output> 
    </body> 
</html> 
20

ich es mit einem Timeout gelöst (nicht blitzsauberen, aber funktioniert):

var dropTarget = $('.dropTarget'), 
    html = $('html'), 
    showDrag = false, 
    timeout = -1; 

html.bind('dragenter', function() { 
    dropTarget.addClass('dragging'); 
    showDrag = true; 
}); 
html.bind('dragover', function(){ 
    showDrag = true; 
}); 
html.bind('dragleave', function (e) { 
    showDrag = false; 
    clearTimeout(timeout); 
    timeout = setTimeout(function(){ 
     if(!showDrag){ dropTarget.removeClass('dragging'); } 
    }, 200); 
}); 

Mein Beispiel verwendet jQuery, aber es ist nicht notwendig. Hier ist eine Zusammenfassung dessen, was vor sich geht:

  • Setzen Sie ein Flag (showDrag) zu true auf dragenter und dragover des HTML (oder Körper) Element.
  • Unter dragleave die Flagge auf false setzen. Stellen Sie dann eine kurze Zeitüberschreitung ein, um zu prüfen, ob das Flag immer noch falsch ist.
  • Im Idealfall sollten Sie den Timeout verfolgen und löschen, bevor Sie den nächsten festlegen.

Auf diese Weise jedes dragleave Ereignis gibt das DOM genug Zeit für ein neues dragover Ereignis des Flag zurückgesetzt. Die real, finaldragleave, die uns interessiert, wird sehen, dass die Flagge immer noch falsch ist.

+0

Ich bekam die besten Ergebnisse von der Kombination dieser Antwort mit Martin Wawrusch's Kommentar zu der Frage. Der Timer wird jedes Mal zurückgesetzt, wenn ein Ereignis behandelt wird, und wird vollständig gelöscht, wenn die Dragleaves gleich sind. Dies gibt mir sofortiges Feedback in Webkit-Browsern und gibt mir Feedback mit einer kurzen Verzögerung in Firefox, die Dragleave nicht zuverlässig auf dem Weg aus dem Fenster ruft. – Dave

+0

Mit anderen Worten, Sie _debounce_ der Handler des 'Dragleave'-Ereignisses. Intelligente Problemumgehung –

3

@ tyler's antwort ist das beste! Ich habe es aufgewertet. Nachdem ich so viele Stunden verbracht hatte, funktionierte dieser Vorschlag genau so, wie ich es mir vorgestellt hatte.

$(document).on('dragstart dragenter dragover', function(event) {  
    // Only file drag-n-drops allowed, http://jsfiddle.net/guYWx/16/ 
    if ($.inArray('Files', event.originalEvent.dataTransfer.types) > -1) { 
     // Needed to allow effectAllowed, dropEffect to take effect 
     event.stopPropagation(); 
     // Needed to allow effectAllowed, dropEffect to take effect 
     event.preventDefault(); 

     $('.dropzone').addClass('dropzone-hilight').show();  // Hilight the drop zone 
     dropZoneVisible= true; 

     // http://www.html5rocks.com/en/tutorials/dnd/basics/ 
     // http://api.jquery.com/category/events/event-object/ 
     event.originalEvent.dataTransfer.effectAllowed= 'none'; 
     event.originalEvent.dataTransfer.dropEffect= 'none'; 

     // .dropzone .message 
     if($(event.target).hasClass('dropzone') || $(event.target).hasClass('message')) { 
      event.originalEvent.dataTransfer.effectAllowed= 'copyMove'; 
      event.originalEvent.dataTransfer.dropEffect= 'move'; 
     } 
    } 
}).on('drop dragleave dragend', function (event) { 
    dropZoneVisible= false; 

    clearTimeout(dropZoneTimer); 
    dropZoneTimer= setTimeout(function(){ 
     if(!dropZoneVisible) { 
      $('.dropzone').hide().removeClass('dropzone-hilight'); 
     } 
    }, dropZoneHideDelay); // dropZoneHideDelay= 70, but anything above 50 is better 
}); 
0

wirklich leid, etwas zu posten, die & unterstreichen spezifische, aber die Art, wie ich das Problem (HTML5-Spezifikation arbeitet auf Chrom) gelöst Winkel ist sollte beobachten leicht zu.

.directive('documentDragAndDropTrigger', function(){ 
return{ 
    controller: function($scope, $document){ 

    $scope.drag_and_drop = {}; 

    function set_document_drag_state(state){ 
     $scope.$apply(function(){ 
     if(state){ 
      $document.context.body.classList.add("drag-over"); 
      $scope.drag_and_drop.external_dragging = true; 
     } 
     else{ 
      $document.context.body.classList.remove("drag-over"); 
      $scope.drag_and_drop.external_dragging = false; 
     } 
     }); 
    } 

    var drag_enters = []; 
    function reset_drag(){ 
     drag_enters = []; 
     set_document_drag_state(false); 
    } 
    function drag_enters_push(event){ 
     var element = event.target; 
     drag_enters.push(element); 
     set_document_drag_state(true); 
    } 
    function drag_leaves_push(event){ 
     var element = event.target; 
     var position_in_drag_enter = _.find(drag_enters, _.partial(_.isEqual, element)); 
     if(!_.isUndefined(position_in_drag_enter)){ 
     drag_enters.splice(position_in_drag_enter,1); 
     } 
     if(_.isEmpty(drag_enters)){ 
     set_document_drag_state(false); 
     } 
    } 

    $document.bind("dragenter",function(event){ 
     console.log("enter", "doc","drag", event); 
     drag_enters_push(event); 
    }); 

    $document.bind("dragleave",function(event){ 
     console.log("leave", "doc", "drag", event); 
     drag_leaves_push(event); 
     console.log(drag_enters.length); 
    }); 

    $document.bind("drop",function(event){ 
     reset_drag(); 
     console.log("drop","doc", "drag",event); 
    }); 
    } 
}; 

})

Ich verwende eine Liste der Elemente darzustellen, die eine Drag eingeben Ereignis ausgelöst haben. Wenn ein Drag-Leave-Ereignis eintritt, finde ich das Element in der Drag-Enter-Liste, das übereinstimmt, entferne es aus der Liste, und wenn die resultierende Liste leer ist, weiß ich, dass ich es aus dem Dokument/Fenster gezogen habe.

Ich muss die Liste mit gezogenen Elementen zurücksetzen, nachdem ein Drop-Ereignis eintritt, oder das nächste Mal, wenn ich etwas ziehe, wird die Liste mit Elementen aus der letzten Drag & Drop-Aktion gefüllt.

Ich habe dies bisher nur auf Chrom getestet. Ich habe dies gemacht, weil Firefox und Chrome verschiedene API-Implementierungen von HTML5 DND haben. (Ziehen und Ablegen).

wirklich hoffe, dass dies einigen Menschen hilft.

5

Das Hinzufügen der Ereignisse zu document schien zu funktionieren? Getestet mit Chrome, Firefox, IE 10.

Das erste Element, das das Ereignis erhält, ist <html>, die in Ordnung sein sollte, denke ich.

var dragCount = 0, 
    dropzone = document.getElementById('dropzone'); 

function dragenterDragleave(e) { 
    e.preventDefault(); 
    dragCount += (e.type === "dragenter" ? 1 : -1); 
    if (dragCount === 1) { 
    dropzone.classList.add('drag-highlight'); 
    } else if (dragCount === 0) { 
    dropzone.classList.remove('drag-highlight'); 
    } 
}; 

document.addEventListener("dragenter", dragenterDragleave); 
document.addEventListener("dragleave", dragenterDragleave); 
7

Sie wissen, dass es das nicht für alle Fälle funktioniert, aber es funktionierte sehr gut in meinem Fall

$('body').bind("dragleave", function(e) 
 
    { 
 
      if (!e.originalEvent.clientX && !e.originalEvent.clientY) 
 
      { 
 
      //outside body/window 
 
      } 
 
     }

+0

echte Antwort, arbeite wie ein Charme! – Pinal

+3

Es ist nicht immer möglich, das Ende eines Ziehens zu erkennen (z. B. eine Datei, die von außerhalb des Browsers gezogen und auf der Download-Leiste in Chrome abgelegt wird, sendet kein Dragleave mit 0,0 als clientX/Y –

0

Wenn die Datei betritt und verläßt Elemente Kind feuert es zusätzliche dragenter und dragleave, also müssen Sie hoch und runter zählen.

var count = 0 

document.addEventListener("dragenter", function() { 
    if (count === 0) { 
     setActive() 
    } 
    count++ 
}) 

document.addEventListener("dragleave", function() { 
    count-- 
    if (count === 0) { 
     setInactive() 
    } 
}) 

document.addEventListener("drop", function() { 
    if (count > 0) { 
     setInactive() 
    } 
    count = 0 
})