2010-06-29 8 views
19

Me gustaría poder resaltar el área de colocación tan pronto como el cursor que transporta un archivo entre en la ventana del navegador, exactamente como lo hace Gmail. Pero no puedo hacer que funcione, y siento que me falta algo realmente obvio.¿Cómo puedo detectar un evento de arrastrar HTML5 que ingresa y sale de la ventana, como lo hace Gmail?

Sigo tratando de hacer algo como esto:

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

Pero que dispara los eventos cada vez que el cursor se mueve sobre y fuera de elementos distintos del cuerpo, lo cual tiene sentido, pero absolutamente no funciona. Podría colocar un elemento encima de todo, cubriendo toda la ventana y detectando eso, pero sería una forma horrible de hacerlo.

¿Qué me estoy perdiendo?

+4

Además de las respuestas a continuación: Me di cuenta de que por lo menos en cromo de la secuencia de eventos es: ENTER ENTER LICENCIA ENTER LICENCIA ... LICENCIA lo que significa que si se mantiene el recuento de entradas y salidas podrá diferenciar entre la entrada inicial y las secuencias internas de entrada/salida PD: Perdón por el formato harto ... –

+0

¡Eres el hombre @MartinWawrusch! Gracias por este – aceofspades

Respuesta

1

Su tercer argumento para addEventListener es true, lo que hace que el oyente se ejecute durante la fase de captura (vea http://www.w3.org/TR/DOM-Level-3-Events/#event-flow para una visualización). Esto significa que capturará los eventos destinados a sus descendientes, y para el cuerpo que significa todos los elementos en la página. En tus manejadores, deberás verificar si el elemento para el cual se activan es el cuerpo mismo. Te daré mi manera muy sucia de hacerlo. Si alguien sabe de manera más simple que realmente compara los elementos, me encantaría verlo.

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

Esto encuentra el cuerpo y elimina this partir del conjunto de elementos que se encuentran. Si el conjunto no está vacío, this no era el cuerpo, por lo que no nos gusta y regresamos. Si this es body, el conjunto estará vacío y el código se ejecutará.

Puede probar con un simple if (this == $('body').get(0)), pero eso probablemente fallará miserablemente.

0

¿Ha notado que hay un retraso antes de que la zona de lanzamiento desaparezca en Gmail? Supongo que lo tienen desaparecer en un temporizador (~ 500 ms) que se restablece por dragover o algún evento de ese tipo.

El problema principal que describió es que dragleave se activa incluso cuando se arrastra a un elemento secundario. Intento encontrar una forma de detectar esto, pero todavía no tengo una solución elegantemente limpia.

1

Estaba teniendo problemas con esto yo mismo y se me ocurrió una solución utilizable, aunque no estoy loco por tener que usar una superposición.

Añadir ondragover, ondragleave y ondrop a la ventana

Añadir ondragenter, ondragleave y ondrop a una superposición y un elemento de destino

Si la caída se produce en la ventana o superposición, ésta se ignora, mientras que las asas de destino la gota como se desee La razón por la que necesitamos una superposición es porque ondragleave se activa cada vez que un elemento está suspendido, por lo que la superposición impide que eso ocurra, mientras que a la zona de colocación se le asigna un índice z más alto para que los archivos se puedan descartar. Estoy usando algunos fragmentos de código que se encuentran en otras preguntas relacionadas con arrastrar y soltar, por lo que no puedo tomar todo el crédito.Aquí está el código HTML completo:

<!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

Lo resuelto con un tiempo de espera (no la inmaculada, pero funciona):

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

Mi ejemplo se utiliza jQuery, pero no es necesario. He aquí un resumen de lo que está pasando:

  • Establecer un indicador (showDrag) a true en dragenter y dragover del elemento html (o cuerpo).
  • En dragleave configure la bandera en false. Luego establezca un tiempo de espera breve para verificar si la bandera sigue siendo falsa.
  • Lo ideal es realizar un seguimiento del tiempo de espera y borrarlo antes de configurar el siguiente.

De esta manera, cada evento dragleave da el tiempo suficiente DOM para una nueva dragover evento para restablecer el indicador. El real, finaldragleave que nos importa verá que la bandera sigue siendo falsa.

+0

Obtuve los mejores resultados al combinar esta respuesta con el comentario de Martin Wawrusch sobre la pregunta. El temporizador se reinicia cada vez que se maneja un evento, y se cancela por completo cuando arrastra objetos iguales. Esto me da una respuesta instantánea en los navegadores de webkits, y me da retroalimentación con un breve retraso en Firefox, que no llama confiablemente a dragleave cuando sale de la ventana. – Dave

+0

En otras palabras, _debounce_ el controlador del evento 'dragleave'. Solución inteligente. –

3

¡La respuesta de @ tyler es la mejor! Lo he votado arriba. Después de pasar tantas horas, obtuve esa sugerencia trabajando exactamente como estaba previsto.

$(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

realmente lo siento a publicar algo que es angular & subrayan específica, sin embargo, la forma en que resolvió el problema (especificación HTML 5, que funciona en el cromo) debe ser fácil de observar.

.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); 
    }); 
    } 
}; 

})

utilizo una lista para representar los elementos que han desencadenado un lastre para entrar evento. cuando ocurre un evento de parada de arrastre, encuentro el elemento en la lista de arrastre que coincide, lo elimino de la lista, y si la lista resultante está vacía, sé que he arrastrado fuera del documento/ventana.

Necesito restablecer la lista que contiene elementos arrastrados después de que ocurre un evento de caída, o la próxima vez que empiezo a arrastrar algo la lista se completará con elementos de la última acción de arrastrar y soltar.

Solo he probado esto en Chrome hasta el momento. Hice esto porque Firefox y Chrome tienen diferentes implementaciones de API de DND de HTML5. (arrastrar y soltar).

realmente espero que esto ayude a algunas personas.

5

Agregar los eventos al document parece que funcionó? Probado con Chrome, Firefox, IE 10.

El primer elemento que obtiene el evento es <html>, lo que debería estar bien, creo.

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

No sabe que esto funciona para todos los casos, pero en mi caso funcionó muy bien

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

+0

respuesta real, funciona como un encanto! – Pinal

+3

Esto no siempre logra detectar el final de un arrastre (EG: archivo arrastrado desde fuera del navegador y soltado en la barra de 'descargas' en cromo no envía un dragleave con 0,0 como clienteX/Y –

0

Cuando el archivo entra y sale de los elementos secundarios que otros incendios dragenter y dragleave, por lo que debe contar hacia arriba y hacia abajo.

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 
}) 
Cuestiones relacionadas