it-roy-ru.com

"перетаскивание" родительского элемента срабатывает при перетаскивании дочерних элементов

Обзор

У меня есть следующая структура HTML, и я прикрепил события dragenter и dragleave к элементу <div id="dropzone">.

<div id="dropzone">
    <div id="dropzone-content">
        <div id="drag-n-drop">
            <div class="text">this is some text</div>
            <div class="text">this is a container with text and images</div>
        </div>
    </div>
</div>

Проблема

Когда я перетаскиваю файл через <div id="dropzone">, событие dragenter запускается, как и ожидалось. Однако, когда я перемещаю мышь над дочерним элементом, таким как <div id="drag-n-drop">, событие dragenter запускается для элемента <div id="drag-n-drop">, а затем событие dragleave запускается для элемента <div id="dropzone">.

Если я снова наведу указатель мыши на элемент <div id="dropzone">, событие dragenter снова будет запущено, что здорово, но затем событие dragleave сработает для только что оставленного дочернего элемента, так что будет выполнена инструкция removeClass, что не круто.

Такое поведение проблематично по 2 причинам:

  1. Я только присоединяю dragenter & dragleave к <div id="dropzone">, поэтому я не понимаю, почему дочерним элементам также присоединяются эти события.

  2. Я все еще перетаскиваю элемент <div id="dropzone"> при наведении на его дочерние элементы, поэтому я не хочу запускать dragleave!

jsFiddle

Вот jsFiddle, с которым нужно повозиться: http://jsfiddle.net/yYF3S/2/

Вопрос

Итак ... как я могу сделать так, чтобы при перетаскивании файла через элемент <div id="dropzone">, dragleave не срабатывал, даже если я перетаскивал какие-либо дочерние элементы ... он должен срабатывать только при выходе из <div id="dropzone"> элемент ... зависание/перетаскивание в пределах границ элемента не должно вызывать событие dragleave.

Мне нужно, чтобы он был совместим с разными браузерами, по крайней мере, в браузерах, которые поддерживают перетаскивание HTML5, поэтому этот ответ не подходит.

Кажется, что Google и Dropbox выяснили это, но их исходный код минимизирован/сложен, поэтому я не смог понять это из их реализации.

146
Hristo

Я наконец нашел решение, которым я доволен. Я действительно нашел несколько способов сделать то, что я хочу, но ни один из них не был столь же успешным, как текущее решение ... в одном решении я испытывал частое мерцание в результате добавления/удаления границы элемента #dropzone ... в другом, Граница никогда не удалялась, если вы зависли от браузера.

В любом случае, мое лучшее хакерское решение таково:

var dragging = 0;

attachEvent(window, 'dragenter', function(event) {

    dragging++;
    $(dropzone).addClass('drag-n-drop-hover');

    event.stopPropagation();
    event.preventDefault();
    return false;
});

attachEvent(window, 'dragover', function(event) {

    $(dropzone).addClass('drag-n-drop-hover');

    event.stopPropagation();
    event.preventDefault();
    return false;
});

attachEvent(window, 'dragleave', function(event) {

    dragging--;
    if (dragging === 0) {
        $(dropzone).removeClass('drag-n-drop-hover');
    }

    event.stopPropagation();
    event.preventDefault();
    return false;
});

Это работает довольно хорошо, но проблемы возникли в Firefox, потому что Firefox дважды вызывал dragenter, поэтому мой счетчик был выключен. Но, тем не менее, это не очень элегантное решение.

Затем я наткнулся на этот вопрос: Как обнаружить событие Dragleave в Firefox при перетаскивании за пределы окна

Поэтому я взял ответ и применил его к моей ситуации:

$.fn.dndhover = function(options) {

    return this.each(function() {

        var self = $(this);
        var collection = $();

        self.on('dragenter', function(event) {
            if (collection.size() === 0) {
                self.trigger('dndHoverStart');
            }
            collection = collection.add(event.target);
        });

        self.on('dragleave', function(event) {
            /*
             * Firefox 3.6 fires the dragleave event on the previous element
             * before firing dragenter on the next one so we introduce a delay
             */
            setTimeout(function() {
                collection = collection.not(event.target);
                if (collection.size() === 0) {
                    self.trigger('dndHoverEnd');
                }
            }, 1);
        });
    });
};

$('#dropzone').dndhover().on({
    'dndHoverStart': function(event) {

        $('#dropzone').addClass('drag-n-drop-hover');

        event.stopPropagation();
        event.preventDefault();
        return false;
    },
    'dndHoverEnd': function(event) {

        $('#dropzone').removeClass('drag-n-drop-hover');

        event.stopPropagation();
        event.preventDefault();
        return false;
    }
});

Это чисто и элегантно и, похоже, работает во всех браузерах, которые я тестировал до сих пор (еще не тестировал IE).

52
Hristo

Если вам не нужно привязывать события к дочерним элементам, вы всегда можете использовать свойство pointer-events.

.child-elements {
  pointer-events: none;
}
152
Ben Rudolph

Это немного уродливо, но это работает, черт возьми! ...

В вашем обработчике «dragenter» сохраните event.target (в переменной внутри вашего замыкания или где-либо еще), затем в вашем обработчике «dragleave» запустите ваш код, только если event.target === тот, который вы сохранили.

Если ваш 'dragenter' срабатывает, когда вы этого не хотите (т.е. когда он входит после выхода из дочерних элементов), то в последний раз, когда он срабатывает до того, как мышь покидает родительский элемент, он находится на родительском элементе, поэтому родительский элемент всегда будет последний «драгентер» перед намеченным «драглайвом».

(function () {

    var droppable = $('#droppable'),
        lastenter;

    droppable.on("dragenter", function (event) {
        lastenter = event.target;
        droppable.addClass("drag-over");            
    });

    droppable.on("dragleave", function (event) {
        if (lastenter === event.target) {
            droppable.removeClass("drag-over");
        }
    });

}());
35
hacklikecrack

Сначала я согласился с людьми, отказывающимися от подхода pointer-events: none. Но потом я спросил себя:

Вам действительно нужны события указателя для работы с дочерними элементами во время перетаскивания

В моем случае у меня много чего происходит, например, с детьми. наведите указатель мыши на кнопки для дополнительных действий, встроенного редактирования и т. д. Однако нет этого необходимо или фактически даже желательно во время перетаскивания.

В моем случае я использую что-то вроде этого, чтобы выборочно отключить события указателя для всех дочерних узлов родительского контейнера:

  div.drag-target-parent-container.dragging-in-progress * {
    pointer-events: none;
  }

Используйте ваш любимый подход для добавления/удаления класса dragging-in-progress в обработчиках событий dragEnter/dragLeave, как я делал или делаю то же самое в dragStart, et. и др.

21
bargar

Это похоже на ошибку Chrome.

Единственный обходной путь, о котором я мог подумать, - это создать прозрачный оверлейный элемент для захвата ваших событий: http://jsfiddle.net/yYF3S/10/

JS:

$(document).ready(function() {
    var dropzone = $('#overlay');

    dropzone.on('dragenter', function(event) {
        $('#dropzone-highlight').addClass('dnd-hover');
    });

    dropzone.on('dragleave', function(event) {
        $('#dropzone-highlight').removeClass('dnd-hover');
    });

});​

HTML:

<div id="dropzone-highlight">
    <div id="overlay"></div>

    <div id="dropzone" class="zone">
        <div id="drag-n-drop">
            <div class="text1">this is some text</div>
            <div class="text2">this is a container with text and images</div>
        </div>
    </div>
</div>

<h2 draggable="true">Drag me</h2>
​
12
Blender

Проблема в том, что ваши элементы внутри дропзоны, конечно, являются частью дропзоны, и когда вы входите в дочерние элементы, вы оставляете родителя. Решить это нелегко. Вы можете попробовать добавить события к детям, добавив ваш класс снова к родителю.

$("#dropzone,#dropzone *").on('dragenter', function(event) {

    // add a class to #dropzone

    event.stopPropagation(); // might not be necessary
    event.preventDefault();
    return false;
});

Ваши события будут по-прежнему срабатывать несколько раз, но никто не увидит.

// Правка: использовать событие dragmove, чтобы навсегда перезаписать событие dragleave:

$("#dropzone,#dropzone *").on('dragenter dragover', function(event) {

    // add a class to #dropzone

    event.stopPropagation(); // might not be necessary
    event.preventDefault();
    return false;
});

Определите событие Dragleave только для dropzone.

5
Oliver

Как benr упомянуто в этот ответ , вы можете запретить дочерним узлам запускать события, но если вам нужно привязать некоторые события, сделайте это:

#dropzone.dragover *{
   pointer-events: none;
}

И добавьте это в свой код JS:

$("#dropzone").on("dragover", function (event) {
   $("#dropzone").addClass("dragover");
});

$("#dropzone").on("dragleave", function (event) {
   $("#dropzone").removeClass("dragover");
});
3
Vahid Ashrafian

Если вы используете jQuery, проверьте это: https://github.com/dancork/jquery.event.dragout

Это действительно потрясающе.

Специальное событие, созданное для обработки истинной функциональности Dragleave.

HTML5 событие dragleave работает больше как мышление. Этот плагин был создан, чтобы копировать функциональность стиля mouseleave, пока перетаскивание.

Пример использования:

$ ('# myelement'). on ('dragout', функция (событие) { // ВАШ КОД });

Правка: на самом деле, я не думаю, что это зависит от jQuery, вы можете просто использовать код даже без него.

3
nkkollaw

@ Христо У меня есть более элегантное решение. Проверьте это, если это то, что вы могли бы использовать.

Ваши усилия не были потрачены впустую в конце концов. Сначала мне удалось использовать ваш, но у меня были разные проблемы в FF, Chrome. Потратив так много часов, я понял, что это предложение работает точно так, как задумано.

Вот как это делается. Я также воспользовался визуальными подсказками, чтобы правильно направлять пользователей о зоне сброса.

$(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
});
2
visitsb

Мои два цента: Скройте слой над зоной сброса, затем покажите его при перетаскивании и наведите курсор на него. 

Демо: https://jsfiddle.net/t6q4shat/

HTML

<div class="drop-zone">
  <h2 class="drop-here">Drop here</h2>
  <h2 class="drop-now">Drop now!</h2>
  <p>Or <a href="#">browse a file</a></p>
  <div class="drop-layer"></div>
</div>

CSS

.drop-zone{
  padding:50px;
  border:2px dashed #999;
  text-align:center;
  position:relative;
}
.drop-layer{
  display:none;
  position:absolute;
  top:0;
  left:0;
  bottom:0;
  right:0;
  z-index:5;
}
.drop-now{
  display:none;
}

JS

$('.drop-zone').on('dragenter', function(e){
    $('.drop-here').css('display','none');
    $('.drop-now').css('display','block');
    $(this).find('.drop-layer').css('display','block');
    return false;
});

$('.drop-layer').on('dragleave', function(e){
    $('.drop-here').css('display','block');
    $('.drop-now').css('display','none');
    $(this).css('display','none');
    return false;
});
2
jeremie.b

Супер простое быстрое решение для этого, не проверено, но работает в Chrome прямо сейчас.

Извините, Coffeescript.

  dragEndTimer = no

  document.addEventListener 'dragover', (event) ->
    clearTimeout dragEndTimer
    $('body').addClass 'dragging'
    event.preventDefault()
    return no

  document.addEventListener 'dragenter', (event) ->
    $('section').scrollTop(0)
    clearTimeout dragEndTimer
    $('body').addClass 'dragging'

  document.addEventListener 'dragleave', (event) ->
    dragEndTimer = setTimeout ->
      $('body').removeClass 'dragging'
    , 50

Это исправляет ошибку мерцания Chrome или, по крайней мере, ее перестановку, которая вызывала у меня проблемы.

1
Dom Vinyard

Так что для меня подход pointer-events: none; не сработал слишком хорошо ... Итак, вот мое альтернативное решение:

    #dropzone {
        position: relative;
    }

    #dropzone(.active)::after {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        content: '';
    }

Таким образом, невозможно dragleave родительский (на дочернем) или dragover дочерний элемент. надеюсь это поможет :)

* .Active-класс, который я добавляю, когда я dragenter или dragleave. Но если вы работаете без этого, просто не посещайте занятия.

1
MMachinegun

Я пытался реализовать это сам для ящика для выгрузки файлов, где цвет ящика изменился бы, когда пользователи перетаскивали файл в пространство.

Я нашел решение, которое представляет собой хорошее сочетание Javascript и CSS. Скажем, у вас есть сбрасываемая зона с div с идентификатором #drop. Добавьте это к вашему Javascript:

$('#drop').on('dragenter', function() {
    $(this).addClass('dragover');
    $(this).children().addClass('inactive');
});

$('#drop').on('dragleave', function() {
    $(this).removeClass('dragover');
    $(this).children().removeClass('inactive');
});

Затем добавьте это в свой CSS, чтобы инактивировать всех детей, будет класс .inactive:

#drop *.inactive {
    pointer-events: none;
}

Таким образом, дочерние элементы будут неактивны до тех пор, пока пользователь перетаскивает элемент над блоком.

0
David A

У меня была похожая проблема, и я исправил ее так:

Проблема: Функция drop (ev) запускается, когда пользователь удаляет элемент в «зоне удаления» (элемент ul), но, к сожалению, также, когда элемент удаляется в одном из его дочерних элементов (элементы li). ,.

Исправление:

function drop(ev) { 
ev.preventDefault(); 
data=ev.dataTransfer.getData('Text'); 
if(ev.target=="[object HTMLLIElement]")  
{ev.target.parentNode.appendChild(document.getElementById(data));}
else{ev.target.appendChild(document.getElementById(data));} 
} 
0
Pao

Этот ответ можно найти здесь:

HTML5 dragleave запускается при наведении на дочерний элемент

var counter = 0;

$('#drop').bind({
    dragenter: function(ev) {
        ev.preventDefault(); // needed for IE
        counter++;
        $(this).addClass('red');
    },

    dragleave: function() {
        counter--;
        if (counter === 0) { 
            $(this).removeClass('red');
        }
    }
});
0
zeros-and-ones

Мне действительно нравится то, что я вижу на https://github.com/lolmaus/jquery.dragbetter/ но я хотел бы поделиться возможной альтернативой . Моя общая стратегия заключалась в том, чтобы применить стиль фона к dropzone (не его потомки) при перетаскивании его или любого его потомка (через пузыри). Затем я удаляю стиль, когда перетаскиваю зону сброса. Идея заключалась в том, чтобы при переходе к ребенку, даже если я удаляю стиль из дропзоны, когда покидаю его (огни драконьей волны), я просто повторно применяю стиль к родительской дропзоне при перетаскивании любого потомка. Проблема, конечно, заключается в том, что при переходе из промежуточной зоны в дочернюю из зоны перетаскивания драгентер срабатывает перед дочерним элементом, поэтому мои стили были применены не по порядку. Решением для меня было использование таймера, чтобы принудительно вернуть событие dragenter обратно в очередь сообщений, что позволило мне обработать его ПОСЛЕ завершения перетаскивания. Я использовал замыкание для доступа к событию по обратному вызову таймера.

$('.dropzone').on('dragenter', function(event) {
  (function (event) {
    setTimeout(function () {
      $(event.target).closest('.dropzone').addClass('highlight');
    }, 0);
  }) (event.originalEvent); 
});

Похоже, это работает в Chrome, то есть Firefox и работает независимо от количества детей в dropzone. Мне немного не по себе из-за тайм-аута, гарантирующего повторное упорядочение событий, но, похоже, он хорошо работает для моего варианта использования.

0
mefopolis

Я не чувствовал себя удовлетворенным ни одним из обходных путей, представленных здесь, потому что я не хочу терять контроль над дочерними элементами.

Поэтому я использовал другой логический подход, переводя его в плагин jQuery, называемый jquery-draghandler . Он абсолютно не манипулирует DOM, гарантируя высокие характеристики. Его использование просто:

$(document).ready(function() {

    $(selector).draghandler({
        onDragEnter: function() {
            // $(this).doSomething();
        },
        onDragLeave: function() {
            // $(this).doSomethingElse();
        }
    });

});

Он без проблем справляется с проблемой, не ставя под угрозу функциональность DOM.

Скачать, подробности и объяснения на его Git хранилище .

0
Black Shell

Здесь одно из самых простых решений ● ︿ ●

Взгляните на это скрипку <- попробуйте перетащить какой-нибудь файл в поле

Вы можете сделать что-то вроде этого:

var dropZone= document.getElementById('box');
var dropMask = document.getElementById('drop-mask');


dropZone.addEventListener('dragover', drag_over, false);
dropMask.addEventListener('dragleave', drag_leave, false);
dropMask.addEventListener('drop', drag_drop, false);

По этому вы уже должны знать, что здесь происходит.
Просто посмотрите на скрипку, вы знаете.

0
Diego T. Yamaguchi

Я пытался реализовать это сам, и я не хотел Jquery или любой плагин либо. 

Я хотел обработать загрузку файла следующим образом:

Структура файлов: 

---/uploads {uploads directory}

--- /js/slyupload.js {файл javascript.}

--- index.php

--- upload.php

--- styles.css {просто немного стайлинга ..}

HTML код:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>my Dzone Upload...</title>
<link rel="stylesheet" href="styles.css" />
</head>

<body>
    <div id="uploads"></div>        
    <div class="dropzone" id="dropzone">Drop files here to upload</div>     
    <script type="text/javascript" src="js/slyupload.js"></script>      
</body>
</html>

Тогда это прикрепленный файл Javascript :: 'js/slyupload.js'

<!-- begin snippet:  -->

<!-- language: lang-js -->

    // JavaScript Document

    //ondragover, ondragleave...


    (function(){        

        var dropzone = document.getElementById('dropzone');
        var intv;

        function displayUploads(data){
            //console.log(data, data.length);
            var uploads = document.getElementById('uploads'),anchor,x;

            for(x=0; x < data.length; x++){

                //alert(data[x].file);
                anchor = document.createElement('a');
                anchor.href = data[x].file;
                anchor.innerText = data[x].name;

                uploads.appendChild(anchor);
            }               
        }

        function upload(files){
            //console.log(files);
            var formData = new FormData(), 
                xhr      = new XMLHttpRequest(),    //for ajax calls...
                x;                                  //for the loop..

                for(x=0;x<files.length; x++){
                    formData.append('file[]', files[x]);

                    /*//do this for every file...
                    xhr = new XMLHttpRequest();

                    //open... and send individually..
                    xhr.open('post', 'upload.php');
                    xhr.send(formData);*/
                }

                xhr.onload = function(){
                    var data = JSON.parse(this.responseText);   //whatever comes from our php..
                    //console.log(data);
                    displayUploads(data);

                    //clear the interval when upload completes... 
                    clearInterval(intv);
                }                   

                xhr.onerror = function(){
                    console.log(xhr.status);
                }

                //use this to send all together.. and disable the xhr sending above...

                //open... and send individually..
                intv = setInterval(updateProgress, 50);
                xhr.open('post', 'upload.php');
                xhr.send(formData);

                //update progress... 
                 /* */                   
        }

        function updateProgress(){
            console.log('hello');
         }          

        dropzone.ondrop = function(e){
            e.preventDefault(); //prevent the default behaviour.. of displaying images when dropped...
            this.className = 'dropzone';
            //we can now call the uploading... 
            upload(e.dataTransfer.files); //the event has a data transfer object...
        }

        dropzone.ondragover = function(){
            //console.log('hello');
            this.className = 'dropzone dragover';
            return false;
        }

        dropzone.ondragleave = function(){
            this.className = 'dropzone';
            return false;
        }           
    }(window));

CSS: 

body{
	font-family:Arial, Helvetica, sans-serif; 
	font-size:12px;
}

.dropzone{
	width:300px; 
	height:300px;
	border:2px dashed #ccc;
	color:#ccc;
	line-height:300px;
	text-align:center;
}

.dropzone.dragover{
	border-color:#000;
	color:#000;
}

0
Andaeiii

Моя версия:

$(".dropzone").bind("dragover", function(e){
    console.log('dragover');
});

$(".dropzone").bind("dragleave", function(e) {
  var stopDrag = false;
  if (!e.relatedTarget) stopDrag = true;
  else {
    var parentDrop = $(e.relatedTarget).parents('.dropzone');
    if (e.relatedTarget != this && !parentDrop.length) stopDrag = true;
  }

  if (stopDrag) {
    console.log('dragleave');
  }
});

С этим макетом:

<div class="dropzone">
  <div class="inner-zone">Inner-zone</div>
</div>

Я сделал дамп классов элементов для e.target, e.currentTarget, e.relatedTarget для событий dragover и dragleave.

Он показал мне, что при выходе из родительского блока (.dropzone) e.relatedTarget не является дочерним для этого блока, поэтому я знаю, что я вне зоны сброса.

0
mortalis