Kéo lê HTML5 được bắn khi di chuột một phần tử con


300

Vấn đề tôi gặp phải là dragleavesự kiện của một phần tử được kích hoạt khi di chuột một phần tử con của phần tử đó. Ngoài ra, dragenterkhông được bắn khi di chuột trở lại phần tử cha.

Tôi đã thực hiện một fiddle đơn giản hóa: http://jsfiddle.net/pimvdb/HU6Mk/1/ .

HTML:

<div id="drag" draggable="true">drag me</div>

<hr>

<div id="drop">
    drop here
    <p>child</p>
    parent
</div>

với JavaScript sau:

$('#drop').bind({
                 dragenter: function() {
                     $(this).addClass('red');
                 },

                 dragleave: function() {
                     $(this).removeClass('red');
                 }
                });

$('#drag').bind({
                 dragstart: function(e) {
                     e.allowedEffect = "copy";
                     e.setData("text/plain", "test");
                 }
                });

Những gì nó được cho là làm là thông báo cho người dùng bằng cách làm cho divmàu đỏ rơi khi kéo một cái gì đó ở đó. Điều này hoạt động, nhưng nếu bạn kéo vào pđứa trẻ, dragleavenó sẽ bị bắn và divkhông còn màu đỏ nữa. Di chuyển trở lại thả divcũng không làm cho nó đỏ trở lại. Nó là cần thiết để di chuyển hoàn toàn ra khỏi thả divvà kéo trở lại vào nó một lần nữa để làm cho nó màu đỏ.

Có thể ngăn không cho dragleavebắn khi kéo vào một phần tử con?

Cập nhật 2017: TL; DR, Tra cứu CSS pointer-events: none;như được mô tả trong câu trả lời của @ HD bên dưới hoạt động trong các trình duyệt hiện đại và IE11.


Lỗi pimvdb được báo cáo vẫn tồn tại trong Webkit kể từ tháng 5 năm 2012. Tôi đã chống lại nó bằng cách thêm một lớp trong trình kéo, không ở đâu gần đẹp vì nó thường phát sinh, nhưng dường như khắc phục vấn đề một chút.
ajm

2
@ajm: Cảm ơn, điều đó hoạt động đến một mức độ. Tuy nhiên, trên Chrome, có một đèn flash khi nhập hoặc rời phần tử con, có lẽ vì dragleavevẫn bị bắn trong trường hợp đó.
pimvdb

Tôi đã mở một upvote lỗi UI UI được chào đón để họ có thể quyết định đưa tài nguyên vào nó
fguillen

@fguillen: Tôi xin lỗi nhưng điều này không liên quan gì đến jQuery UI. Trong thực tế, jQuery thậm chí không cần thiết để kích hoạt lỗi. Tôi đã gửi một lỗi WebKit nhưng hiện tại không có bản cập nhật nào.
pimvdb

2
@pimvdb Tại sao bạn không chấp nhận câu trả lời của HD?
TylerH

Câu trả lời:


338

Bạn chỉ cần giữ một bộ đếm tham chiếu, tăng nó khi bạn có một trình kéo, giảm khi bạn nhận được một dragleave. Khi bộ đếm ở mức 0 - loại bỏ lớp.

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

Lưu ý: Trong trường hợp thả, đặt lại bộ đếm về 0 và xóa lớp đã thêm.

Bạn có thể chạy nó ở đây


8
OMG đây là giải pháp rõ ràng nhất và chỉ có MỘT phiếu bầu ... Thôi nào mọi người, bạn có thể làm tốt hơn. Tôi đã suy nghĩ về điều đó nhưng sau khi thấy mức độ tinh vi của một vài câu trả lời đầu tiên, tôi gần như đã loại bỏ nó. Bạn có bất kỳ nhược điểm?
Arthur Corenzan

11
Điều này không hoạt động khi cạnh của phần tử bị kéo chạm vào cạnh của phần tử có thể kéo khác. Ví dụ, một danh sách có thể sắp xếp; kéo phần tử xuống dưới, qua mục có thể kéo tiếp theo không làm giảm bộ đếm về 0, thay vào đó nó bị kẹt ở 1. Nhưng nếu tôi kéo nó sang một bên, ra khỏi bất kỳ phần tử có thể kéo nào khác, nó sẽ hoạt động. Tôi đã đi với pointer-events: nonecác em. Khi tôi bắt đầu kéo, tôi nối thêm một lớp có thuộc tính này, khi kéo qua tôi sẽ xóa lớp đó. Hoạt động độc đáo trên Safari và Chrome, nhưng không hoạt động trên Firefox.
Arthur Corenzan 14/03/2015

13
Điều này làm việc cho tôi trong tất cả các trình duyệt, nhưng Firefox. Tôi có một giải pháp mới hoạt động ở mọi nơi trong trường hợp của tôi. Ngày đầu tiên dragentertôi lưu event.currentTargettrong một biến mới dragEnterTarget. Miễn là dragEnterTargetđược thiết lập, tôi bỏ qua các dragentersự kiện tiếp theo , bởi vì chúng là từ trẻ em. Trong tất cả các dragleavesự kiện tôi kiểm tra dragEnterTarget === event.target. Nếu điều này là sai, sự kiện sẽ bị bỏ qua vì nó đã bị một đứa trẻ sa thải. Nếu đây là sự thật, tôi thiết lập lại dragEnterTargetđể undefined.
Pipo

3
Giải pháp tuyệt vời. Tôi cần phải đặt lại bộ đếm về 0 trong chức năng xử lý chấp nhận thả mặc dù vậy, các lần kéo tiếp theo không hoạt động như mong đợi.
Liam Johnston

2
@Woody Tôi không thấy bình luận của anh ấy trong danh sách bình luận lớn ở đây, nhưng vâng, đó là cách khắc phục. Tại sao không kết hợp điều đó vào câu trả lời của bạn?
BT

93

Có thể ngăn chặn dragleave bắn khi kéo vào một phần tử con không?

Đúng.

#drop * {pointer-events: none;}

CSS đó dường như là đủ cho Chrome.

Trong khi sử dụng nó với Firefox, #drop không nên có các nút văn bản trực tiếp (nếu không có vấn đề lạ trong đó một phần tử "để nó cho riêng nó" ), vì vậy tôi khuyên bạn chỉ nên để nó với một phần tử (ví dụ: sử dụng div bên trong # để đặt mọi thứ vào trong)

Đây là một jsfiddle giải quyết ví dụ câu hỏi ban đầu (bị hỏng) .

Tôi cũng đã tạo một phiên bản đơn giản hóa từ ví dụ @Theodore Brown, nhưng chỉ dựa trên CSS này.

Tuy nhiên, không phải tất cả các trình duyệt đều triển khai CSS này: http://caniuse.com/pulum-events

Nhìn thấy mã nguồn Facebook tôi có thể tìm thấy mã này pointer-events: none;nhiều lần, tuy nhiên có lẽ nó được sử dụng cùng với các dự phòng suy thoái duyên dáng. Ít nhất nó rất đơn giản và giải quyết vấn đề cho nhiều môi trường.


Thuộc tính sự kiện con trỏ là giải pháp phù hợp trong tương lai, nhưng thật không may, nó không hoạt động trong IE8-IE10, vẫn được sử dụng rộng rãi. Ngoài ra, tôi nên chỉ ra rằng jsFiddle hiện tại của bạn thậm chí không hoạt động trong IE11, vì nó không thêm các trình lắng nghe sự kiện cần thiết và ngăn chặn hành vi mặc định.
Theodore Brown

2
Sử dụng các sự kiện con trỏ thực sự là một câu trả lời tốt, tôi đã đấu tranh một chút trước khi tự mình tìm ra, câu trả lời đó nên cao hơn.
floribon

Tùy chọn tuyệt vời cho những người trong chúng ta đủ may mắn để không phải hỗ trợ các trình duyệt cũ.
Luke Hansford

7
Nếu con bạn là Nút thì sao? oO
David

9
nó chỉ hoạt động nếu bạn không có các thành phần điều khiển khác (chỉnh sửa, xóa) trong khu vực, bởi vì giải pháp này cũng chặn chúng ..
Zoltán Süle

45

Ở đây, giải pháp Cross-Browser đơn giản nhất (nghiêm túc):

jsfiddle <- thử kéo một số tệp trong hộp

Bạn có thể làm một cái gì đó như thế:

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

Trong một vài từ, bạn tạo một "mặt nạ" bên trong dropzone, với chiều rộng và chiều cao được kế thừa, vị trí tuyệt đối, sẽ chỉ hiển thị khi quá trình kéo bắt đầu.
Vì vậy, sau khi hiển thị mặt nạ đó, bạn có thể thực hiện thủ thuật bằng cách đính kèm các sự kiện kéo và thả khác trên đó.

Sau khi rời hoặc thả, bạn chỉ cần giấu mặt nạ một lần nữa.
Đơn giản và không có biến chứng.

(Quan sát: Lời khuyên của Greg Pettit - Bạn phải chắc chắn rằng mặt nạ lơ lửng toàn bộ hộp, bao gồm cả đường viền)


Không chắc tại sao, nhưng nó không hoạt động ổn định với Chrome. Đôi khi rời khỏi khu vực giữ cho mặt nạ nhìn thấy.
Greg Pettit

2
Thật ra, đó là biên giới. Có mặt nạ chồng lên đường viền, không có đường viền của riêng nó và nó sẽ hoạt động tốt.
Greg Pettit

1
Lưu ý, jsfiddle của bạn có một lỗi trong đó, trong drag_drop, bạn nên xóa lớp di chuột trên "#box" chứ không phải "# box-a"
entropy

Đây là một giải pháp tốt, nhưng bằng cách nào đó nó không hiệu quả với tôi. Cuối cùng tôi đã tìm ra một cách giải quyết. Đối với những người bạn đang tìm kiếm một cái gì đó khác. bạn có thể thử điều này: github.com/bingjie2680/jquery-draghover
bingjie2680

Không. Tôi không muốn mất quyền kiểm soát các yếu tố con. Tôi đã tạo một plugin jQuery đơn giản giải quyết vấn đề đang thực hiện công việc cho chúng tôi. Kiểm tra câu trả lời của tôi .
Luca Fagioli

39

Đã khá lâu sau khi câu hỏi này được hỏi và rất nhiều giải pháp (bao gồm cả những bản hack xấu xí) được cung cấp.

Tôi đã cố gắng khắc phục vấn đề tương tự mà tôi gặp phải gần đây nhờ câu trả lời trong câu trả lời này và nghĩ rằng nó có thể hữu ích cho ai đó đi qua trang này. Toàn bộ ý tưởng là để lưu trữ các evenet.targettrong ondrageentermọi lúc nó được gọi vào bất kỳ yếu tố cha mẹ hoặc con. Sau đó ondragleavekiểm tra xem mục tiêu hiện tại ( event.target) có bằng với đối tượng bạn lưu trữ không ondragenter.

Trường hợp duy nhất hai cái này được khớp là khi kéo của bạn rời khỏi cửa sổ trình duyệt.

Lý do điều này hoạt động tốt là khi chuột rời một phần tử (nói el1) và nhập phần tử khác (nói el2), đầu tiên el2.ondragenterđược gọi và sau đó el1.ondragleave. Chỉ khi kéo rời / vào cửa sổ trình duyệt, event.targetsẽ ''ở cả hai el2.ondragenterel1.ondragleave.

Đây là mẫu làm việc của tôi. Tôi đã thử nghiệm nó trên IE9 +, Chrome, Firefox và Safari.

(function() {
    var bodyEl = document.body;
    var flupDiv = document.getElementById('file-drop-area');

    flupDiv.onclick = function(event){
        console.log('HEy! some one clicked me!');
    };

    var enterTarget = null;

    document.ondragenter = function(event) {
        console.log('on drag enter: ' + event.target.id);
        enterTarget = event.target;
        event.stopPropagation();
        event.preventDefault();
        flupDiv.className = 'flup-drag-on-top';
        return false;
    };

    document.ondragleave = function(event) {
        console.log('on drag leave: currentTarget: ' + event.target.id + ', old target: ' + enterTarget.id);
        //Only if the two target are equal it means the drag has left the window
        if (enterTarget == event.target){
            event.stopPropagation();
            event.preventDefault();
            flupDiv.className = 'flup-no-drag';         
        }
    };
    document.ondrop = function(event) {
        console.log('on drop: ' + event.target.id);
        event.stopPropagation();
        event.preventDefault();
        flupDiv.className = 'flup-no-drag';
        return false;
    };
})();

Và đây là một trang html đơn giản:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Multiple File Uploader</title>
<link rel="stylesheet" href="my.css" />
</head>
<body id="bodyDiv">
    <div id="cntnr" class="flup-container">
        <div id="file-drop-area" class="flup-no-drag">blah blah</div>
    </div>
    <script src="my.js"></script>
</body>
</html>

Với kiểu dáng phù hợp, những gì tôi đã làm là làm cho div bên trong (# file-drop-area) lớn hơn nhiều bất cứ khi nào một tệp được kéo vào màn hình để người dùng có thể dễ dàng thả các tệp vào đúng vị trí.


4
Đây là giải pháp tốt nhất, nó tốt hơn so với quầy (đặc biệt là nếu bạn ủy thác các sự kiện) và nó cũng hoạt động với trẻ em có thể kéo được.
Fred

3
Điều này không giúp ích gì cho vấn đề trùng lặp các sự kiện kéo
BT

Điều này có hiệu quả với "trẻ em" là các phần tử giả css hoặc các lớp giả không? Tôi không thể làm được, nhưng có lẽ tôi đã làm sai.
Josh Bleecher Snyder

1
Kể từ tháng 3 năm 2020 tôi cũng đã có may mắn nhất với giải pháp này. Lưu ý: một số linters có thể phàn nàn về việc sử dụng ==hơn ===. Bạn đang so sánh các tham chiếu đối tượng mục tiêu sự kiện, do đó cũng ===hoạt động tốt.
Alex

33

Cách "đúng" để giải quyết vấn đề này là vô hiệu hóa các sự kiện con trỏ trên các phần tử con của mục tiêu thả (như trong câu trả lời của @ HD). Đây là một jsFiddle tôi đã tạo để thể hiện kỹ thuật này . Thật không may, điều này không hoạt động trong các phiên bản Internet Explorer trước IE11, vì chúng không hỗ trợ các sự kiện con trỏ .

May mắn thay, tôi đã có thể tìm ra một cách giải quyết mà không làm việc trong các phiên bản cũ của trình duyệt IE. Về cơ bản, nó liên quan đến việc xác định và bỏ qua dragleavecác sự kiện xảy ra khi kéo qua các phần tử con. Vì dragentersự kiện được kích hoạt trên các nút con trước dragleavesự kiện trên cha mẹ, nên các trình lắng nghe sự kiện riêng biệt có thể được thêm vào từng nút con để thêm hoặc xóa lớp "bỏ qua kéo-rời" khỏi mục tiêu thả. Sau đó, trình dragleavelắng nghe sự kiện của mục tiêu thả có thể đơn giản bỏ qua các cuộc gọi xảy ra khi lớp này tồn tại. Đây là một jsFiddle chứng minh cách giải quyết này . Nó được thử nghiệm và hoạt động trong Chrome, Firefox và IE8 +.

Cập nhật:

Tôi đã tạo một jsFiddle trình diễn giải pháp kết hợp bằng tính năng phát hiện tính năng, trong đó các sự kiện con trỏ được sử dụng nếu được hỗ trợ (hiện tại là Chrome, Firefox và IE11) và trình duyệt quay lại để thêm các sự kiện vào các nút con nếu hỗ trợ sự kiện con trỏ không có sẵn (IE8 -10).


Câu trả lời này cho thấy một cách giải quyết cho việc bắn không mong muốn nhưng lại bỏ qua câu hỏi "Có thể ngăn chặn dragleave bắn khi kéo vào một phần tử con không?" hoàn toàn
HD

Hành vi có thể trở nên lạ khi kéo tệp từ bên ngoài trình duyệt. Trong Firefox tôi đã có "Nhập con -> Nhập cha mẹ -> Bỏ con -> Nhập con -> Bỏ con" mà không rời khỏi cha mẹ, lớp còn lại với lớp "kết thúc". IE cũ sẽ cần một sự thay thế Đính kèm cho addEventListener.
HD

Giải pháp đó phụ thuộc mạnh mẽ vào sự sủi bọt, "sai" trong tất cả addEventListener nên được nhấn mạnh là điều cần thiết (mặc dù đó là hành vi mặc định), vì nhiều người có thể không biết về điều đó.
HD

Điều đó có thể kéo được thêm một hiệu ứng cho dropzone không xuất hiện khi dropzone được sử dụng cho các đối tượng có thể kéo khác không kích hoạt sự kiện kéo. Có lẽ sử dụng mọi thứ như một dropzone cho hiệu ứng kéo trong khi giữ dropzone thực sự với các trình xử lý khác sẽ làm điều đó.
HD

1
@HD Tôi đã cập nhật câu trả lời của mình với thông tin về việc sử dụng thuộc tính CSS sự kiện con trỏ để ngăn dragleavesự kiện phát sinh trong Chrome, Firefox và IE11 +. Tôi cũng đã cập nhật cách giải quyết khác của mình để hỗ trợ IE8 và IE9, ngoài IE10. Điều này có chủ ý là hiệu ứng dropzone chỉ được thêm vào khi kéo liên kết "Kéo tôi". Những người khác có thể cảm thấy thoải mái để thay đổi hành vi này khi cần thiết để hỗ trợ các trường hợp sử dụng của họ.
Theodore Brown

14

Vấn đề là dragleavesự kiện đang được kích hoạt khi con chuột đi trước phần tử con.

Tôi đã thử nhiều phương pháp kiểm tra để xem e.targetphần tử có giống với thisphần tử không, nhưng không thể cải thiện được.

Cách tôi khắc phục vấn đề này là một chút hack, nhưng hoạt động 100%.

dragleave: function(e) {
               // Get the location on screen of the element.
               var rect = this.getBoundingClientRect();

               // Check the mouseEvent coordinates are outside of the rectangle
               if(e.x > rect.left + rect.width || e.x < rect.left
               || e.y > rect.top + rect.height || e.y < rect.top) {
                   $(this).removeClass('red');
               }
           }

1
Cảm ơn! Tuy nhiên, tôi không thể làm điều này để hoạt động trong Chrome. Bạn có thể cung cấp một fiddle làm việc của hack của bạn?
pimvdb

3
Tôi đã nghĩ đến việc làm nó bằng cách kiểm tra các coords quá. Bạn đã làm hầu hết các công việc cho tôi, thx :). Tôi đã phải thực hiện một số điều chỉnh mặc dù:if (e.x >= (rect.left + rect.width) || e.x <= rect.left || e.y >= (rect.top + rect.height) || e.y <= rect.top)
Christof

1
Nó sẽ không hoạt động trong Chrome vì sự kiện này không có e.xe.y.
Hengjie

Tôi thích giải pháp này. Không hoạt động trong Firefox trước. Nhưng nếu bạn thay thế ex bằng e.clientX và ey bằng e.clientY thì nó hoạt động. Cũng hoạt động trong Chrome.
Nicole Stutz

Không làm việc với chrome đối với tôi, cũng không làm những gì Chris hoặc Daniel Stuts đề xuất
Timo Huovinen

14

nếu bạn đang sử dụng HTML5, bạn có thể nhận clientRect của cha mẹ:

let rect = document.getElementById("drag").getBoundingClientRect();

Sau đó, trong ngoặc đơn.dragleave ():

dragleave(e) {
    if(e.clientY < rect.top || e.clientY >= rect.bottom || e.clientX < rect.left || e.clientX >= rect.right) {
        //real leave
    }
}

đây là một jsfiddle


2
Câu trả lời tuyệt vời.
broc.seib

Cảm ơn bạn, tôi thích cách tiếp cận javascript đơn giản.
Tim Gerhard

Khi sử dụng trên các phần tử có border-radius, di chuyển con trỏ đến gần góc sẽ thực sự rời khỏi phần tử, nhưng mã này sẽ vẫn nghĩ rằng chúng ta đang ở bên trong (chúng ta đã để lại phần tử nhưng chúng ta vẫn ở trong hình chữ nhật giới hạn). Sau đó, dragleavexử lý sự kiện sẽ không được gọi ở tất cả.
Jay Dadhania

11

Một giải pháp rất đơn giản là sử dụng thuộc tính pointer-eventsCSS . Chỉ cần đặt giá trị của nó thành nonekhi kéo bắt đầu trên mọi phần tử con. Các yếu tố này sẽ không kích hoạt các sự kiện liên quan đến chuột nữa, vì vậy chúng sẽ không bắt chuột trên chúng và do đó sẽ không kích hoạt kéo chuột trên cha mẹ.

Đừng quên đặt lại thuộc tính này autokhi hoàn thành việc kéo;)


11

Giải pháp khá đơn giản này vẫn hoạt động với tôi cho đến nay, giả sử sự kiện của bạn được gắn vào từng yếu tố kéo riêng lẻ.

if (evt.currentTarget.contains(evt.relatedTarget)) {
  return;
}

Điều đó thật tuyệt! cảm ơn vì đã chia sẻ @kenneth, bạn đã cứu ngày của tôi! Rất nhiều câu trả lời khác chỉ áp dụng nếu bạn có một khu vực nhỏ giọt duy nhất.
antoni

Đối với tôi, nó hoạt động trong Chrome và Firefox, nhưng id không hoạt động trong Edge. Vui lòng xem: jsfiddle.net/iwiss/t9pv24jo
iwis

8

Giải pháp rất đơn giản:

parent.addEventListener('dragleave', function(evt) {
    if (!parent.contains(evt.relatedTarget)) {
        // Here it is only dragleave on the parent
    }
}

Điều này dường như không hoạt động trong Safari 12.1: jsfiddle.net/6d0qc87m
Adam Taylor

7

Bạn có thể sửa nó trong Firefox với một chút cảm hứng từ mã nguồn jQuery :

dragleave: function(e) {
    var related = e.relatedTarget,
        inside = false;

    if (related !== this) {

        if (related) {
            inside = jQuery.contains(this, related);
        }

        if (!inside) {

            $(this).removeClass('red');
        }
    }

}

Thật không may, nó không hoạt động trong Chrome vì relatedTargetdường như không tồn tại trên dragleavecác sự kiện và tôi cho rằng bạn đang làm việc trong Chrome vì ví dụ của bạn không hoạt động trong Firefox. Đây là một phiên bản với mã trên được thực hiện.


Cảm ơn rất nhiều, nhưng thực sự đó là Chrome Tôi đang cố gắng giải quyết vấn đề này.
pimvdb

5
@pimvdb Tôi thấy bạn đã đăng nhập một lỗi , tôi sẽ chỉ để lại một tham chiếu đến nó trong trường hợp có ai khác bắt gặp câu trả lời này.
robertc

Tôi đã thực sự, nhưng tôi quên thêm một liên kết đến nó ở đây. Cảm ơn vì đã làm điều đó.
pimvdb

Lỗi Chrome đã được sửa trong thời gian trung bình.
phk

7

Và đây là một giải pháp cho Chrome:

.bind('dragleave', function(event) {
                    var rect = this.getBoundingClientRect();
                    var getXY = function getCursorPosition(event) {
                        var x, y;

                        if (typeof event.clientX === 'undefined') {
                            // try touch screen
                            x = event.pageX + document.documentElement.scrollLeft;
                            y = event.pageY + document.documentElement.scrollTop;
                        } else {
                            x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
                            y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
                        }

                        return { x: x, y : y };
                    };

                    var e = getXY(event.originalEvent);

                    // Check the mouseEvent coordinates are outside of the rectangle
                    if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) {
                        console.log('Drag is really out of area!');
                    }
                })

Nó có đi vào «if (typeof event.clientX === 'không xác định')» không?
Aldekein

1
Hoạt động độc đáo nhưng có thể có một cửa sổ khác trên trình duyệt, do đó, việc xác định vị trí chuột và so sánh nó với khu vực màn hình hình chữ nhật là không đủ.
HD

Tôi đồng ý với @HD Ngoài ra, điều này sẽ gây ra sự cố khi phần tử lớn border-radiusnhư tôi đã giải thích trong nhận xét của mình về câu trả lời của @ azlar ở trên.
Jay Dadhania

7

Đây là một giải pháp khác sử dụng document.elementFromPoint:

 dragleave: function(event) {
   var event = event.originalEvent || event;
   var newElement = document.elementFromPoint(event.pageX, event.pageY);
   if (!this.contains(newElement)) {
     $(this).removeClass('red');
   }
}

Hy vọng điều này hoạt động, đây là một fiddle .


3
Điều này đã không làm việc cho tôi ra khỏi hộp. Tôi cần sử dụng event.clientX, event.clientYthay thế, vì chúng liên quan đến chế độ xem chứ không phải trang. Tôi hy vọng điều đó sẽ giúp một linh hồn bị mất ngoài kia.
Jon Abrams

7

Một giải pháp đơn giản là thêm quy tắc css pointer-events: nonevào thành phần con để ngăn chặn việc kích hoạt ondragleave. Xem ví dụ:

function enter(event) {
  document.querySelector('div').style.border = '1px dashed blue';
}

function leave(event) {
  document.querySelector('div').style.border = '';
}
div {
  border: 1px dashed silver;
  padding: 16px;
  margin: 8px;
}

article {
  border: 1px solid silver;
  padding: 8px;
  margin: 8px;
}

p {
  pointer-events: none;
  background: whitesmoke;
}
<article draggable="true">drag me</article>

<div ondragenter="enter(event)" ondragleave="leave(event)">
  drop here
  <p>child not triggering dragleave</p>
</div>


Tôi đã có cùng một vấn đề và giải pháp của bạn hoạt động tuyệt vời. Mẹo cho người khác, đây là trường hợp của tôi: nếu phần tử con bạn đang cố gắng bỏ qua sự kiện kéo là bản sao của phần tử được kéo (cố gắng đạt được bản xem trước trực quan), bạn có thể sử dụng phần này bên trong kéo khi tạo bản sao :dragged = event.target;clone = dragged.cloneNode();clone.style.pointerEvents = 'none';
Scaramouche

4

Không chắc chắn nếu trình duyệt chéo này, nhưng tôi đã thử nghiệm trong Chrome và nó giải quyết vấn đề của tôi:

Tôi muốn kéo và thả một tập tin trên toàn bộ trang, nhưng dragleave của tôi bị bắn khi tôi kéo qua phần tử con. Cách khắc phục của tôi là nhìn vào x và y của chuột:

tôi có một div che phủ toàn bộ trang của tôi, khi trang tải tôi sẽ ẩn nó.

Khi bạn kéo qua tài liệu tôi hiển thị nó, và khi bạn thả vào cha mẹ, nó sẽ xử lý nó và khi bạn rời khỏi cha mẹ, tôi kiểm tra x và y.

$('#draganddrop-wrapper').hide();

$(document).bind('dragenter', function(event) {
    $('#draganddrop-wrapper').fadeIn(500);
    return false;
});

$("#draganddrop-wrapper").bind('dragover', function(event) {
    return false;
}).bind('dragleave', function(event) {
    if( window.event.pageX == 0 || window.event.pageY == 0 ) {
        $(this).fadeOut(500);
        return false;
    }
}).bind('drop', function(event) {
    handleDrop(event);

    $(this).fadeOut(500);
    return false;
});

1
Hacky nhưng thông minh. Tôi thích. Đã làm cho tôi.
cupcakekid

1
Ồ, làm thế nào tôi muốn câu trả lời này có nhiều phiếu hơn! Cảm ơn bạn
Kirby

4

Tôi đã viết một thư viện nhỏ tên là Dragster để xử lý vấn đề chính xác này, hoạt động ở mọi nơi ngoại trừ âm thầm không làm gì trong IE (không hỗ trợ Trình tạo sự kiện DOM, nhưng sẽ khá dễ dàng để viết một cái gì đó tương tự bằng cách sử dụng các sự kiện tùy chỉnh của jQuery)


Rất hữu ích (ít nhất là đối với tôi, nơi tôi chỉ quan tâm đến Chrome).
M Katz

4

Tôi đã có cùng một vấn đề và đã cố gắng sử dụng giải pháp pk7s. Nó hoạt động nhưng nó có thể được thực hiện tốt hơn một chút mà không có bất kỳ yếu tố dom nào.

Về cơ bản, ý tưởng là như nhau - thêm một lớp phủ không mong muốn vào khu vực có thể đổ được. Chỉ cho phép làm điều này mà không có bất kỳ yếu tố dom thêm. Đây là phần đã được các phần tử giả CSS đến chơi.

Javascript

var dragOver = function (e) {
    e.preventDefault();
    this.classList.add('overlay');
};

var dragLeave = function (e) {
    this.classList.remove('overlay');
};


var dragDrop = function (e) {
    this.classList.remove('overlay');
    window.alert('Dropped');
};

var dropArea = document.getElementById('box');

dropArea.addEventListener('dragover', dragOver, false);
dropArea.addEventListener('dragleave', dragLeave, false);
dropArea.addEventListener('drop', dragDrop, false);

CSS

Điều này sau quy tắc sẽ tạo ra một lớp phủ được bao phủ đầy đủ cho khu vực có thể rơi.

#box.overlay:after {
    content:'';
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    z-index: 1;
}

Đây là giải pháp đầy đủ: http://jsfiddle.net/F6GDq/8/

Tôi hy vọng nó sẽ giúp bất cứ ai có cùng một vấn đề.


1
Thỉnh thoảng không hoạt động trên chrome (không bắt đúng cách kéo)
Timo Huovinen

4

Chỉ cần kiểm tra xem phần tử được kéo qua có phải là con không, nếu có, thì đừng xóa lớp kiểu 'kéo' của bạn. Khá đơn giản và hiệu quả với tôi:

 $yourElement.on('dragleave dragend drop', function(e) {
      if(!$yourElement.has(e.target).length){
           $yourElement.removeClass('is-dragover');
      }
  })

Có vẻ như giải pháp đơn giản nhất đối với tôi, và nó đã giải quyết vấn đề của tôi. Cảm ơn!
Francesco Marchetti-Stasi

3

Tôi đã vấp phải cùng một vấn đề và đây là giải pháp của tôi - điều mà tôi nghĩ là dễ dàng hơn nhiều ở trên. Tôi không chắc liệu nó có phải là con lai hay không (có thể phụ thuộc vào thứ tự sủi bọt)

Tôi sẽ sử dụng jQuery để đơn giản, nhưng giải pháp nên độc lập với khung.

Bong bóng sự kiện cho cha mẹ bằng cách nào đó được đưa ra:

<div class="parent">Parent <span>Child</span></div>

Chúng tôi đính kèm các sự kiện

el = $('.parent')
setHover = function(){ el.addClass('hovered') }
onEnter  = function(){ setTimeout(setHover, 1) }
onLeave  = function(){ el.removeClass('hovered') } 
$('.parent').bind('dragenter', onEnter).bind('dragleave', onLeave)

Và đó là về nó. :) nó hoạt động bởi vì mặc dù on on on con bắn trước khi bật cha mẹ, chúng tôi trì hoãn nó đảo ngược thứ tự một chút, vì vậy lớp được loại bỏ trước sau đó được áp dụng lại sau một phần nghìn giây.


Điều duy nhất đoạn trích này làm là ngăn chặn lớp 'lơ lửng' bằng cách áp dụng lại nó trong chu kỳ đánh dấu tiếp theo (làm cho sự kiện 'dragleave' trở nên vô dụng).
null

Nó không vô dụng. Nếu bạn rời khỏi cha mẹ, nó sẽ hoạt động như mong đợi. Sức mạnh của giải pháp này là sự đơn giản, không lý tưởng hay tốt nhất là có. Giải pháp tốt hơn sẽ là đánh dấu một mục nhập vào một đứa trẻ, trong kiểm tra trực tuyến nếu chúng ta vừa nhập con, và nếu không kích hoạt sự kiện nghỉ phép. Nó sẽ cần người thử nghiệm, bảo vệ thêm, checing cho cháu, v.v.
Marcin Raczkowski

3

Tôi đã viết một mô-đun kéo và thả gọi là nhỏ giọt để sửa hành vi kỳ quặc này, trong số những người khác. Nếu bạn đang tìm kiếm một mô-đun kéo và thả ở mức độ thấp tốt, bạn có thể sử dụng làm cơ sở cho mọi thứ (tải lên tệp, kéo và thả trong ứng dụng, kéo từ hoặc đến các nguồn bên ngoài), bạn nên kiểm tra điều này mô-đun ra:

https://github.com/fresheneesz/drip-drop

Đây là cách bạn sẽ làm những gì bạn đang cố gắng thực hiện trong nhỏ giọt:

$('#drop').each(function(node) {
  dripDrop.drop(node, {
    enter: function() {
      $(node).addClass('red')  
    },
    leave: function() {
      $(node).removeClass('red')
    }
  })
})
$('#drag').each(function(node) {
  dripDrop.drag(node, {
    start: function(setData) {
      setData("text", "test") // if you're gonna do text, just do 'text' so its compatible with IE's awful and restrictive API
      return "copy"
    },
    leave: function() {
      $(node).removeClass('red')
    }
  })
})

Để làm điều này mà không có thư viện, kỹ thuật truy cập là những gì tôi đã sử dụng trong nhỏ giọt, câu trả lời được đánh giá cao nhất bỏ qua các bước quan trọng sẽ khiến mọi thứ bị phá vỡ cho tất cả mọi thứ trừ lần thả đầu tiên. Đây là cách làm đúng:

var counter = 0;    
$('#drop').bind({
    dragenter: function(ev) {
        ev.preventDefault()
        counter++
        if(counter === 1) {
          $(this).addClass('red')
        }
    },

    dragleave: function() {
        counter--
        if (counter === 0) { 
            $(this).removeClass('red');
        }
    },
    drop: function() {
        counter = 0 // reset because a dragleave won't happen in this case
    }
});

2

Một giải pháp làm việc thay thế, đơn giản hơn một chút.

//Note: Due to a bug with Chrome the 'dragleave' event is fired when hovering the dropzone, then
//      we must check the mouse coordinates to be sure that the event was fired only when 
//      leaving the window.
//Facts:
//  - [Firefox/IE] e.originalEvent.clientX < 0 when the mouse is outside the window
//  - [Firefox/IE] e.originalEvent.clientY < 0 when the mouse is outside the window
//  - [Chrome/Opera] e.originalEvent.clientX == 0 when the mouse is outside the window
//  - [Chrome/Opera] e.originalEvent.clientY == 0 when the mouse is outside the window
//  - [Opera(12.14)] e.originalEvent.clientX and e.originalEvent.clientY never get
//                   zeroed if the mouse leaves the windows too quickly.
if (e.originalEvent.clientX <= 0 || e.originalEvent.clientY <= 0) {

Điều này dường như không luôn hoạt động trong Chrome. Đôi khi tôi sẽ nhận được clientXtrên 0 khi con chuột ở ngoài hộp. Cấp, các yếu tố của tôi làposition:absolute
Hengjie

Nó xảy ra mọi lúc hay chỉ đôi khi? Bởi vì nếu chuột di chuyển quá nhanh (ig. Bên ngoài cửa sổ), bạn có thể nhận được các giá trị sai.
Profet

Nó xảy ra 90% thời gian. Có một số trường hợp hiếm hoi (1 trên 10 lần) khi tôi có thể đạt tới 0. Tôi sẽ thử di chuyển chuột chậm hơn, nhưng tôi không thể nói rằng tôi đang di chuyển nhanh (có lẽ bạn gọi tốc độ bình thường).
Hengjie

2

Sau khi dành rất nhiều giờ, tôi nhận được đề nghị đó hoạt động chính xác như dự định. Tôi chỉ muốn cung cấp một gợi ý khi các tệp được kéo qua và kéo tài liệu, dragleave đã gây ra các nhấp nháy đau đớn trên trình duyệt Chrome.

Đây là cách tôi giải quyết nó, cũng ném vào tín hiệu thích hợp cho người dùng.

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

1

Tôi đã gặp một vấn đề tương tự - mã của tôi để ẩn dropzone trong sự kiện dragleave cho cơ thể đã bị bắn liên tục khi di chuột vào các phần tử con làm cho dropzone nhấp nháy trong Google Chrome.

Tôi đã có thể giải quyết điều này bằng cách lên lịch cho chức năng ẩn dropzone thay vì gọi nó ngay lập tức. Sau đó, nếu một thao tác kéo hoặc kéo khác được thực hiện, lệnh gọi chức năng theo lịch trình sẽ bị hủy.

body.addEventListener('dragover', function() {
    clearTimeout(body_dragleave_timeout);
    show_dropzone();
}, false);

body.addEventListener('dragleave', function() {
    clearTimeout(body_dragleave_timeout);
    body_dragleave_timeout = setTimeout(show_upload_form, 100);
}, false);

dropzone.addEventListener('dragover', function(event) {
    event.preventDefault();
    dropzone.addClass("hover");
}, false);

dropzone.addEventListener('dragleave', function(event) {
    dropzone.removeClass("hover");
}, false);

Đây là những gì tôi đã làm quá, nhưng nó vẫn còn sơ sài.
mở

1

" Dragleave " Sự kiện này được kích hoạt khi con trỏ chuột ra khỏi khu vực kéo của container mục tiêu.

Mà làm cho rất nhiều ý nghĩa như trong nhiều trường hợp chỉ phụ huynh có thể droppable và không phải là hậu duệ. Tôi nghĩ event.stopPropogation () nên đã xử lý trường hợp này nhưng có vẻ như nó không thực hiện được mẹo.

Ở trên đã đề cập một số giải pháp dường như có hiệu quả đối với hầu hết các trường hợp, nhưng không thành công trong trường hợp những đứa trẻ không hỗ trợ các sự kiện dragenter / dragleave , chẳng hạn như iframe .

1 cách giải quyết là kiểm tra event.relatedTarget và xác minh xem nó có nằm trong container không, sau đó bỏ qua sự kiện dragleave như tôi đã làm ở đây:

function isAncestor(node, target) {
    if (node === target) return false;
    while(node.parentNode) {
        if (node.parentNode === target)
            return true;
        node=node.parentNode;
    }
    return false;
}

var container = document.getElementById("dropbox");
container.addEventListener("dragenter", function() {
    container.classList.add("dragging");
});

container.addEventListener("dragleave", function(e) {
    if (!isAncestor(e.relatedTarget, container))
        container.classList.remove("dragging");
});

Bạn có thể tìm thấy một fiddle làm việc ở đây !


1

Đã giải quyết ..!

Khai báo bất kỳ mảng nào cho ex:

targetCollection : any[] 

dragenter: function(e) {
    this.targetCollection.push(e.target); // For each dragEnter we are adding the target to targetCollection 
    $(this).addClass('red');
},

dragleave: function() {
    this.targetCollection.pop(); // For every dragLeave we will pop the previous target from targetCollection
    if(this.targetCollection.length == 0) // When the collection will get empty we will remove class red
    $(this).removeClass('red');
}

Không cần phải lo lắng về các yếu tố trẻ em.


1

Tôi đã đấu tranh rất nhiều với điều này, ngay cả sau khi đọc qua tất cả các câu trả lời này, và nghĩ rằng tôi có thể chia sẻ giải pháp của mình với bạn, bởi vì tôi nghĩ rằng nó có thể là một trong những cách tiếp cận đơn giản hơn, mặc dù hơi khác. dragleaveSuy nghĩ của tôi chỉ đơn giản là bỏ hoàn toàn người nghe sự kiện và mã hóa hành vi kéo theo với mỗi sự kiện trình kéo mới được kích hoạt, trong khi đảm bảo rằng các sự kiện trình kéo sẽ không bị bắn một cách không cần thiết.

Trong ví dụ của tôi dưới đây, tôi có một bảng, nơi tôi muốn có thể trao đổi nội dung hàng của bảng với nhau thông qua API kéo và thả. Vào dragenter, một lớp CSS sẽ được thêm vào thành phần hàng mà bạn hiện đang kéo phần tử của mình, để tô sáng nó và trên đó dragleave, lớp này sẽ bị xóa.

Thí dụ:

Bảng HTML rất cơ bản:

<table>
  <tr>
    <td draggable="true" class="table-cell">Hello</td>
  </tr>
  <tr>
    <td draggable="true" clas="table-cell">There</td>
  </tr>
</table>

Và chức năng DragEnter xử lý sự kiện, thêm vào mỗi ô trong bảng (ngoài dragstart, dragover, drop, và dragendxử lý, mà không phải là cụ thể cho câu hỏi này, vì vậy không được sao chép ở đây):

/*##############################################################################
##                              Dragenter Handler                             ##
##############################################################################*/

// When dragging over the text node of a table cell (the text in a table cell),
// while previously being over the table cell element, the dragleave event gets
// fired, which stops the highlighting of the currently dragged cell. To avoid
// this problem and any coding around to fight it, everything has been
// programmed with the dragenter event handler only; no more dragleave needed

// For the dragenter event, e.target corresponds to the element into which the
// drag enters. This fact has been used to program the code as follows:

var previousRow = null;

function handleDragEnter(e) {
  // Assure that dragenter code is only executed when entering an element (and
  // for example not when entering a text node)
  if (e.target.nodeType === 1) {
    // Get the currently entered row
    let currentRow = this.closest('tr');
    // Check if the currently entered row is different from the row entered via
    // the last drag
    if (previousRow !== null) {
      if (currentRow !== previousRow) {
        // If so, remove the class responsible for highlighting it via CSS from
        // it
        previousRow.className = "";
      }
    }
    // Each time an HTML element is entered, add the class responsible for
    // highlighting it via CSS onto its containing row (or onto itself, if row)
    currentRow.className = "ready-for-drop";
    // To know which row has been the last one entered when this function will
    // be called again, assign the previousRow variable of the global scope onto
    // the currentRow from this function run
    previousRow = currentRow;
  }
}

Những bình luận rất cơ bản còn lại trong mã, sao cho mã này cũng phù hợp với người mới bắt đầu. Hy vọng rằng cái này sẽ giúp đc bạn! Lưu ý rằng tất nhiên bạn sẽ cần thêm tất cả các trình lắng nghe sự kiện mà tôi đã đề cập ở trên vào mỗi ô của bảng để hoạt động.


0

Đây là một cách tiếp cận khác dựa trên thời gian của các sự kiện.

Sự dragenterkiện được gửi từ phần tử con có thể được bắt bởi phần tử cha và nó luôn xảy ra trước dragleave. Thời gian giữa hai sự kiện này thực sự ngắn, ngắn hơn bất kỳ hành động chuột nào của con người. Vì vậy, ý tưởng là ghi nhớ thời gian dragenterxảy ra và lọc dragleavecác sự kiện xảy ra "không quá nhanh" sau ...

Ví dụ ngắn này hoạt động trên Chrome và Firefox:

var node = document.getElementById('someNodeId'),
    on   = function(elem, evt, fn) { elem.addEventListener(evt, fn, false) },
    time = 0;

on(node, 'dragenter', function(e) {
    e.preventDefault();
    time = (new Date).getTime();
    // Drag start
})

on(node, 'dragleave', function(e) {
    e.preventDefault();
    if ((new Date).getTime() - time > 5) {
         // Drag end
    }
})

0

pimvdb ..

Tại sao bạn không thử sử dụng drop thay vì dragleave . Nó làm việc cho tôi. Hy vọng điều này sẽ giải quyết vấn đề của bạn.

Vui lòng kiểm tra jsFiddle: http://jsfiddle.net/HU6Mk/118/

$('#drop').bind({
                 dragenter: function() {
                     $(this).addClass('red');
                 },

                 drop: function() {
                     $(this).removeClass('red');
                 }
                });

$('#drag').bind({
                 dragstart: function(e) {
                     e.allowedEffect = "copy";
                     e.setData("text/plain", "test");
                 }
                });

2
Nếu người dùng thay đổi ý định và kéo tập tin ra khỏi trình duyệt, điều này sẽ vẫn có màu đỏ.
ZachB

0

Bạn có thể sử dụng thời gian chờ với transitioningcờ và lắng nghe phần tử trên cùng. dragenter/ dragleavetừ các sự kiện con sẽ nổi bong bóng lên container.

dragenterphần tử con kích hoạt trước dragleavecontainer, chúng tôi sẽ đặt cờ hiển thị là chuyển tiếp trong 1ms ... dragleavengười nghe sẽ kiểm tra cờ trước khi hết 1ms.

Cờ sẽ chỉ đúng trong khi chuyển đổi sang các phần tử con và sẽ không đúng khi chuyển sang phần tử cha (của vùng chứa)

var $el = $('#drop-container'),
    transitioning = false;

$el.on('dragenter', function(e) {

  // temporarily set the transitioning flag for 1 ms
  transitioning = true;
  setTimeout(function() {
    transitioning = false;
  }, 1);

  $el.toggleClass('dragging', true);

  e.preventDefault();
  e.stopPropagation();
});

// dragleave fires immediately after dragenter, before 1ms timeout
$el.on('dragleave', function(e) {

  // check for transitioning flag to determine if were transitioning to a child element
  // if not transitioning, we are leaving the container element
  if (transitioning === false) {
    $el.toggleClass('dragging', false);
  }

  e.preventDefault();
  e.stopPropagation();
});

// to allow drop event listener to work
$el.on('dragover', function(e) {
  e.preventDefault();
  e.stopPropagation();
});

$el.on('drop', function(e) {
  alert("drop!");
});

jsfiddle: http://jsfiddle.net/ilovett/U7mJj/


0

Bạn cần xóa các sự kiện con trỏ cho tất cả các đối tượng con của mục tiêu kéo.

function disableChildPointerEvents(targetObj) {
        var cList = parentObj.childNodes
        for (i = 0; i < cList.length; ++i) {
            try{
                cList[i].style.pointerEvents = 'none'
                if (cList[i].hasChildNodes()) disableChildPointerEvents(cList[i])
            } catch (err) {
                //
            }
        }
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.