HTML5 dragleave ใช้งานเมื่อทำการวางตัวองค์ประกอบลูก


300

ปัญหาที่ฉันมีคือdragleaveเหตุการณ์ขององค์ประกอบนั้นถูกไล่ออกเมื่อทำการวางตัวองค์ประกอบย่อยขององค์ประกอบนั้น นอกจากนี้dragenterจะไม่ถูกไล่ออกเมื่อโฮเวอร์กลับองค์ประกอบหลักอีกครั้ง

ฉันทำไวโอลินง่าย: 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>

ด้วย JavaScript ต่อไปนี้:

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

สิ่งที่ควรทำคือการแจ้งผู้ใช้ด้วยการทำให้divสีแดงหล่นเมื่อลากสิ่งที่มี วิธีนี้ใช้ได้ผล แต่ถ้าคุณลากเข้าสู่pเด็กการdragleaveถูกไล่ออกและdivจะไม่เป็นสีแดงอีกต่อไป การย้ายกลับไปยังการดร็อปdivไม่ทำให้แดงอีกเลย มีความจำเป็นที่จะต้องย้ายออกจากการตกdivและลากกลับเข้าไปอีกครั้งเพื่อทำให้เป็นสีแดง

เป็นไปได้หรือไม่ที่จะป้องกันไม่ให้dragleaveยิงเมื่อลากเข้าสู่องค์ประกอบย่อย

2017 Update: TL; DR, ค้นหา CSS pointer-events: none;ตามที่อธิบายไว้ในคำตอบของ @ HD ด้านล่างซึ่งใช้งานได้ในเบราว์เซอร์สมัยใหม่และ IE11


ข้อผิดพลาด pimvdb รายงานยังคงมีอยู่ใน Webkit ณ พฤษภาคม 2012 ฉันได้ตอบโต้ด้วยการเพิ่มคลาสใน dragover ซึ่งไม่ได้อยู่ใกล้กับที่ดีเพราะมันมักจะเกิดไฟไหม้ แต่ดูเหมือนจะแก้ไขปัญหาเล็กน้อย
ajm

2
@ajm: ขอบคุณที่ใช้งานได้ในระดับหนึ่ง อย่างไรก็ตามใน Chrome มีแฟลชเมื่อเข้าหรือออกองค์ประกอบลูกอาจสันนิษฐานได้ว่า dragleaveยังคงมีการยิงในกรณีนี้
pimvdb

ผมได้เปิดข้อผิดพลาด jQuery UI upvotesยินดีต้อนรับเพื่อให้พวกเขาสามารถตัดสินใจที่จะนำทรัพยากรที่มัน
fguillen

@fguillen: ฉันขอโทษ แต่สิ่งนี้ไม่เกี่ยวข้องกับ jQuery UI ในความเป็นจริง jQuery ไม่จำเป็นต้องใช้เพื่อกระตุ้นข้อผิดพลาด ฉันได้ยื่นข้อบกพร่องของ WebKit แล้ว แต่ตอนนี้ยังไม่มีการอัปเดต
pimvdb

2
@pimvdb ทำไมคุณไม่ยอมรับคำตอบของ HD?
TylerH

คำตอบ:


337

คุณเพียงแค่ต้องรักษาตัวนับอ้างอิงเพิ่มขึ้นเมื่อคุณได้รับ dragenter ลดลงเมื่อคุณได้รับ dragleave เมื่อตัวนับเป็น 0 - ลบคลาส

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

หมายเหตุ: ในเหตุการณ์ดรอปรีเซ็ตตัวนับเป็นศูนย์และล้างคลาสที่เพิ่ม

คุณสามารถเรียกใช้ที่นี่


8
OMG นี่เป็นทางออกที่ชัดเจนที่สุดและมีเพียงหนึ่งเสียงเท่านั้น ... มาที่คนคุณทำได้ดีกว่า ฉันกำลังคิดเกี่ยวกับสิ่งนั้น แต่หลังจากเห็นระดับความซับซ้อนของคำตอบสองสามข้อแรกฉันเกือบจะทิ้งมันไป คุณมีข้อบกพร่องหรือไม่?
Arthur Corenzan

11
สิ่งนี้ไม่ทำงานเมื่อขอบขององค์ประกอบถูกลากแตะที่ขอบขององค์ประกอบที่ลากได้อื่น ตัวอย่างเช่นรายการเรียงลำดับ; การลากองค์ประกอบลงด้านล่างรายการที่ลากได้ถัดไปจะไม่ลดตัวนับกลับเป็น 0 แต่จะติดค้างที่ 1 แต่ถ้าฉันลากไปด้านข้างออกจากองค์ประกอบที่ลากได้อื่น ๆ ก็ใช้งานได้ ฉันไปกับpointer-events: noneเด็ก ๆ เมื่อฉันเริ่มการลากฉันจะเพิ่มคลาสที่มีคุณสมบัตินี้เมื่อลากไปฉันจะลบคลาส ทำงานได้ดีบน Safari และ Chrome แต่ไม่ใช่ใน Firefox
Arthur Corenzan

13
สิ่งนี้ใช้ได้สำหรับฉันในเบราว์เซอร์ทั้งหมด แต่ Firefox ฉันมีโซลูชันใหม่ที่ใช้งานได้ทุกที่ในกรณีของฉัน ในวันแรกdragenterที่ฉันบันทึกในตัวแปรใหม่event.currentTarget dragEnterTargetตราบใดที่dragEnterTargetมีการตั้งค่าฉันไม่สนใจdragenterกิจกรรมเพิ่มเติมเพราะพวกเขามาจากเด็ก ๆ ในทุกเหตุการณ์ที่เกิดขึ้นฉันจะตรวจสอบdragleave dragEnterTarget === event.targetหากสิ่งนี้เป็นเท็จเหตุการณ์จะถูกเพิกเฉยเนื่องจากถูกไล่ออกจากเด็ก ในกรณีนี้คือการตั้งค่าฉันจริงไปdragEnterTarget undefined
Pipo

3
ทางออกที่ดี ฉันต้องการรีเซ็ตตัวนับเป็น 0 ในฟังก์ชั่นที่จัดการยอมรับการปล่อยแม้ว่ามิฉะนั้น drags ที่ตามมาจะไม่ทำงานตามที่คาดไว้
เลียมจอห์นสตัน

2
@ Woody ฉันไม่เห็นความคิดเห็นของเขาในรายการความคิดเห็นจำนวนมากที่นี่ แต่ใช่นั่นคือการแก้ไข ทำไมไม่รวมมันเข้ากับคำตอบของคุณ?
BT

93

เป็นไปได้หรือไม่ที่จะป้องกันไม่ให้ dragleave ยิงเมื่อทำการลากเข้าสู่ส่วนย่อย

ใช่.

#drop * {pointer-events: none;}

CSS นั้นดูเหมือนจะเพียงพอสำหรับ Chrome

ในขณะที่ใช้กับ Firefox # หยดไม่ควรมีโหนดข้อความโดยตรง (มีปัญหาแปลก ๆที่องค์ประกอบ "ปล่อยให้มันเป็นของตัวเอง" ) ดังนั้นฉันขอแนะนำให้ปล่อยไว้ด้วยองค์ประกอบเดียวเท่านั้น (เช่นใช้ div ภายใน # วางทุกอย่างไว้ข้างใน)

นี่เป็น jsfiddleแก้คำถามเดิม (หัก) ตัวอย่างเช่น

ฉันได้สร้างเวอร์ชันที่เรียบง่ายแยกจากตัวอย่าง @ Theodore Brown แต่ใช้ CSS เท่านั้น

เบราว์เซอร์บางตัวมีการใช้งาน CSS นี้ แต่: http://caniuse.com/pointer-events

การได้เห็นซอร์สโค้ดของ Facebook ฉันสามารถพบสิ่งนี้ได้pointer-events: none;หลายครั้ง อย่างน้อยมันก็ง่ายมากและแก้ปัญหาสำหรับสภาพแวดล้อมจำนวนมาก


คุณสมบัติตัวชี้เหตุการณ์เป็นทางออกที่ถูกต้องในอนาคต แต่น่าเสียดายที่มันไม่ทำงานใน IE8-IE10 ซึ่งยังคงใช้กันอย่างแพร่หลาย นอกจากนี้ฉันควรชี้ให้เห็นว่า jsFiddle ปัจจุบันของคุณไม่ทำงานแม้แต่ใน IE11 เนื่องจากมันไม่ได้เพิ่มฟังเหตุการณ์ที่จำเป็นและการป้องกันพฤติกรรมเริ่มต้น
Theodore Brown

2
การใช้ตัวชี้เหตุการณ์เป็นคำตอบที่ดีฉันพยายามดิ้นรนเล็กน้อยก่อนที่จะค้นพบด้วยตัวเอง
floribon

ตัวเลือกที่ยอดเยี่ยมสำหรับพวกเราโชคดีที่ไม่ต้องสนับสนุนเบราว์เซอร์รุ่นเก่า
Luke Hansford

7
เกิดอะไรขึ้นถ้าลูกของคุณเป็นปุ่ม? oO
David

9
ใช้งานได้เฉพาะในกรณีที่คุณไม่มีองค์ประกอบตัวควบคุมอื่น (แก้ไขลบ) ภายในพื้นที่เนื่องจากโซลูชันนี้จะบล็อกองค์ประกอบเหล่านั้นด้วย
ZoltánSüle

45

ที่นี่โซลูชัน Cross-Browser ที่ง่ายที่สุด (จริงจัง):

jsfiddle <- ลองลากไฟล์บางไฟล์เข้าไปในกล่อง

คุณสามารถทำอะไรเช่นนั้น:

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

ในสองสามคำคุณสร้าง "รูปแบบ" ภายใน dropzone โดยสืบทอดความกว้างและความสูงตำแหน่งที่แน่นอนซึ่งจะแสดงเมื่อ dragover เริ่มขึ้น
ดังนั้นหลังจากแสดงมาสก์นั้นแล้วคุณสามารถทำเคล็ดลับได้โดยแนบ dragleave และวางกิจกรรมอื่น ๆ

หลังจากออกหรือทิ้งคุณเพียงซ่อนหน้ากากอีกครั้ง
ง่ายและไม่มีภาวะแทรกซ้อน

(ข้อสังเกต: คำแนะนำของเกร็กเพทติท - คุณต้องแน่ใจว่าหน้ากากเลื่อนไปมาทั้งกล่องรวมถึงเส้นขอบด้วย)


ไม่แน่ใจว่าทำไม แต่ไม่ทำงานอย่างสม่ำเสมอกับ Chrome บางครั้งออกจากพื้นที่ทำให้หน้ากากมองเห็นได้
Greg Pettit

2
ที่จริงมันเป็นชายแดน มีหน้ากากที่คาบเกี่ยวกันโดยไม่มีขอบของมันและมันก็ใช้ได้ดี
Greg Pettit

1
หมายเหตุ jsfiddle ของคุณมีบั๊กอยู่ใน drag_drop คุณควรลบคลาส hover ที่ "#box" ไม่ใช่ "# box-a"
เอนโทรปีที่

นี่เป็นทางออกที่ดี แต่อย่างใดก็ไม่ได้ผลสำหรับฉัน ในที่สุดฉันก็พบวิธีแก้ปัญหา สำหรับบรรดาของคุณที่กำลังมองหาอย่างอื่น คุณสามารถลองนี้: github.com/bingjie2680/jquery-draghover
bingjie2680

ไม่ฉันไม่ต้องการสูญเสียการควบคุมองค์ประกอบลูก ฉันได้สร้างปลั๊กอิน jQuery ง่าย ๆ ที่เกี่ยวข้องกับปัญหาในการทำงานให้เรา ตรวจสอบคำตอบของฉัน
Luca Fagioli

39

เวลาผ่านไปค่อนข้างนานหลังจากถามคำถามนี้และมีวิธีแก้ปัญหามากมาย (รวมถึงแฮ็กที่น่าเกลียด)

ฉันจัดการเพื่อแก้ไขปัญหาเดียวกันฉันเพิ่งขอบคุณคำตอบในคำตอบนี้และคิดว่าอาจเป็นประโยชน์กับคนที่มาถึงหน้านี้ ความคิดทั้งหมดคือการจัดเก็บevenet.targetในondrageenterทุกครั้งที่มีการเรียกองค์ประกอบใด ๆ ของผู้ปกครองหรือเด็ก จากนั้นในondragleaveการตรวจสอบถ้าเป้าหมายปัจจุบัน ( event.target) ondragenterเท่ากับวัตถุที่คุณเก็บไว้ใน

กรณีเดียวที่ทั้งสองนี้จับคู่คือเมื่อคุณลากออกจากหน้าต่างเบราว์เซอร์

ด้วยเหตุผลที่ว่านี้ทำงานได้ดีคือเมื่อใบเมาส์องค์ประกอบ (พูดel1) และเข้าองค์ประกอบอื่น (พูดel2) คนแรกที่ถูกเรียกแล้วel2.ondragenter el1.ondragleaveเฉพาะเมื่อลากจะออก / เข้าหน้าต่างเบราว์เซอร์event.targetจะมี''ทั้งในและel2.ondragenter el1.ondragleave

นี่คือตัวอย่างการทำงานของฉัน ฉันได้ทำการทดสอบกับ IE9 +, Chrome, Firefox และ 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;
    };
})();

และนี่คือหน้า html ที่เรียบง่าย:

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

ด้วยสไตล์ที่เหมาะสมสิ่งที่ฉันทำคือทำให้ div ภายใน (# file-drop-area) ใหญ่ขึ้นทุกครั้งที่มีการลากไฟล์ไปที่หน้าจอเพื่อให้ผู้ใช้สามารถวางไฟล์ลงในตำแหน่งที่เหมาะสมได้อย่างง่ายดาย


4
นี่เป็นทางออกที่ดีที่สุดดีกว่าตัวนับ (โดยเฉพาะถ้าคุณมอบหมายกิจกรรม) และทำงานได้กับเด็ก ๆ ที่สามารถลากได้เช่นกัน
Fred

3
สิ่งนี้ไม่ได้ช่วยแก้ปัญหาเหตุการณ์ dragenter ที่ซ้ำกัน
BT

สิ่งนี้ใช้ได้กับ "เด็ก ๆ " ที่เป็นองค์ประกอบหลอก css หรือคลาสหลอกหรือไม่? ฉันไปไม่ได้ แต่บางทีฉันทำผิด
Josh Bleecher Snyder

1
เมื่อวันที่มีนาคม 2563 ฉันโชคดีกับวิธีนี้เช่นกัน หมายเหตุ: linters บางคนอาจจะบ่นเกี่ยวกับการใช้มากกว่า== ===คุณกำลังเปรียบเทียบการอ้างอิงวัตถุเป้าหมายเหตุการณ์จึง===ทำงานได้ดีเช่นกัน
Alex

33

วิธี "ถูกต้อง" เพื่อแก้ไขปัญหานี้คือการปิดใช้งานเหตุการณ์ตัวชี้บนองค์ประกอบย่อยของเป้าหมายการปล่อย (ตามคำตอบของ @ HD) นี่คือ jsFiddle ที่ฉันสร้างขึ้นซึ่งแสดงให้เห็นถึงเทคนิคนี้ แต่น่าเสียดายที่นี้ไม่ได้ทำงานใน Internet Explorer รุ่นก่อนที่จะ IE11 เนื่องจากพวกเขาไม่สนับสนุนเหตุการณ์ชี้

โชคดีที่ผมสามารถที่จะเกิดขึ้นกับการแก้ปัญหาที่ไม่ทำงานในรุ่นเก่าของ IE โดยทั่วไปจะเกี่ยวข้องกับการระบุและละเว้นdragleaveเหตุการณ์ที่เกิดขึ้นเมื่อลากไปที่องค์ประกอบลูก เนื่องจากdragenterเหตุการณ์ถูกใช้บนโหนดชายน์ก่อนdragleaveเหตุการณ์บนพาเรนต์ผู้ฟังเหตุการณ์ที่แยกต่างหากสามารถเพิ่มไปยังโหนดชายด์แต่ละโหนดซึ่งเพิ่มหรือลบคลาส ดังนั้นdragleaveผู้ฟังเหตุการณ์ของเป้าหมายการปล่อยสามารถเพิกเฉยต่อการโทรที่เกิดขึ้นเมื่อมีคลาสนี้อยู่ นี่คือjsFiddle ที่แสดงวิธีแก้ปัญหานี้ มันได้รับการทดสอบและทำงานใน Chrome, Firefox และ IE8 +

ปรับปรุง:

ฉันสร้างjsFiddle ซึ่งแสดงโซลูชันที่รวมกันโดยใช้การตรวจจับคุณลักษณะซึ่งมีการใช้ตัวชี้เหตุการณ์หากรองรับ (ปัจจุบันคือ Chrome, Firefox และ IE11) และเบราว์เซอร์กลับไปเพิ่มเหตุการณ์ให้กับโหนดลูกหากไม่รองรับเหตุการณ์ตัวชี้ (IE8 -10)


คำตอบนี้แสดงวิธีแก้ปัญหาสำหรับการยิงที่ไม่พึงประสงค์ แต่ไม่สนใจคำถาม "เป็นไปได้ไหมที่จะป้องกันไม่ให้ dragleave ทำการยิงเมื่อลากไปที่องค์ประกอบเด็ก?" อย่างสิ้นเชิง
HD

พฤติกรรมอาจแปลกเมื่อลากไฟล์จากนอกเบราว์เซอร์ ใน Firefox ฉันได้รับ "กำลังเข้าสู่เด็ก -> กำลังเข้าสู่ผู้ปกครอง -> ออกจากเด็ก -> กำลังเข้าร่วมเด็ก -> กำลังออกจากเด็ก" โดยไม่ต้องออกจากผู้ปกครองซึ่งเหลือไว้กับคลาส "เกิน" IE เก่าจะต้องมีการแทนที่ attachEvent สำหรับ addEventListener
HD

การแก้ปัญหานั้นขึ้นอยู่กับการเดือดปุด ๆ อย่างยิ่ง "เท็จ" ใน addEventListener ทั้งหมดควรเน้นเป็นสิ่งจำเป็น (แม้ว่าจะเป็นพฤติกรรมเริ่มต้น) เนื่องจากหลายคนอาจไม่รู้เรื่องนั้น
HD

ลากไปมาได้หนึ่งครั้งจะเพิ่มเอฟเฟกต์ไปยัง dropzone ที่ไม่ปรากฏขึ้นเมื่อใช้ dropzone สำหรับวัตถุที่ลากได้อื่น ๆ ที่ไม่ได้เรียกใช้เหตุการณ์ลากสตาร์ต บางทีการใช้ทุกอย่างเป็น dropzone สำหรับเอฟเฟกต์ของการลากในขณะที่เก็บ dropzone ที่แท้จริงกับตัวจัดการอื่น ๆ ก็จะทำเช่นนั้น
HD

1
@HD ฉันได้อัปเดตคำตอบของฉันพร้อมข้อมูลเกี่ยวกับการใช้คุณสมบัติตัวชี้เหตุการณ์ CSS เพื่อป้องกันไม่ให้dragleaveเหตุการณ์เกิดขึ้นใน Chrome, Firefox และ IE11 + ฉันได้ปรับปรุงวิธีแก้ปัญหาอื่นของฉันเพื่อรองรับ IE8 และ IE9 นอกเหนือจาก IE10 เป็นความตั้งใจที่จะเพิ่มเอฟเฟกต์ dropzone เฉพาะเมื่อลากลิงก์ "ลากฉัน" ผู้อื่นสามารถเปลี่ยนพฤติกรรมนี้ได้ตามต้องการเพื่อรองรับกรณีการใช้งาน
Theodore Brown

14

ปัญหาคือdragleaveเหตุการณ์ถูกไล่ออกเมื่อเมาส์ไปข้างหน้าองค์ประกอบลูก

ฉันได้ลองใช้วิธีการต่าง ๆ ในการตรวจสอบเพื่อดูว่าe.targetองค์ประกอบนั้นเหมือนกับthisองค์ประกอบหรือไม่ แต่ไม่สามารถปรับปรุงได้

วิธีที่ฉันแก้ไขปัญหานี้คือการแฮ็ก แต่ใช้งานได้ 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
ขอบคุณ! ฉันไม่สามารถทำงานใน Chrome ได้ คุณสามารถให้ซอทำแฮ็คของคุณได้หรือไม่?
pimvdb

3
ฉันกำลังคิดจะทำมันโดยตรวจสอบ coords ด้วย คุณทำงานให้ฉันได้มากที่สุดขอบคุณ :) ฉันต้องทำการปรับเปลี่ยนบางอย่าง:if (e.x >= (rect.left + rect.width) || e.x <= rect.left || e.y >= (rect.top + rect.height) || e.y <= rect.top)
Christof

มันจะไม่ทำงานใน Chrome เพราะเหตุการณ์ไม่ได้และe.x e.y
Hengjie

ฉันชอบวิธีนี้ ไม่ได้ทำงานใน Firefox ก่อน แต่ถ้าคุณแทนที่ ex ด้วย e.clientX และ ey ด้วย e.clientY มันใช้งานได้ ยังใช้งานได้ใน Chrome
Nicole Stutz

ไม่ได้ทำงานกับโครเมี่ยมสำหรับฉันไม่ได้ทำในสิ่งที่ChrisหรือDaniel Stutsแนะนำ
Timo Huovinen

14

หากคุณใช้ HTML5 คุณจะได้รับ clientRect ของผู้ปกครอง:

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

จากนั้นใน parent.dragleave ():

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

นี่คือ jsfiddle


2
คำตอบที่ยอดเยี่ยม
broc.seib

ขอบคุณเพื่อนฉันชอบจาวาสคริปต์ธรรมดา
ทิมแกร์ฮาร์ด

เมื่อใช้กับองค์ประกอบที่มีการborder-radiusย้ายตัวชี้ใกล้มุมจริง ๆจะปล่อยให้องค์ประกอบ แต่รหัสนี้จะยังคงคิดว่าเราอยู่ข้างใน (เราได้ออกจากองค์ประกอบ แต่เรายังคงอยู่ในรูปสี่เหลี่ยมผืนผ้าขอบเขต) จากนั้นdragleaveตัวจัดการเหตุการณ์จะไม่ถูกเรียกเลย
Jay Dadhania

11

วิธีการแก้ปัญหาที่ง่ายมากคือการใช้คุณสมบัติpointer-events CSS เพียงแค่ตั้งค่าให้เป็นnoneเมื่อdragstartในองค์ประกอบเด็กทุกคน องค์ประกอบเหล่านี้จะไม่เรียกเหตุการณ์ที่เกี่ยวข้องกับเมาส์อีกต่อไปดังนั้นพวกเขาจะไม่จับเมาส์เหนือพวกมันและจะไม่เรียกใช้dragleaveกับผู้ปกครอง

อย่าลืมที่จะตั้งค่าคุณสมบัตินี้กลับไปautoเมื่อเสร็จสิ้นการลาก;)


11

วิธีแก้ปัญหาที่ค่อนข้างง่ายนี้ใช้งานได้ดีสำหรับฉันโดยสมมติว่ากิจกรรมของคุณเชื่อมต่อกับองค์ประกอบลากแต่ละรายการ

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

มันเยี่ยมมาก! ขอบคุณที่แชร์ @kenneth คุณช่วยชีวิตฉันไว้! คำตอบอื่น ๆ จำนวนมากนำไปใช้เฉพาะเมื่อคุณมีเขตที่ปล่อยได้เพียงจุดเดียว
antoni

สำหรับฉันมันใช้งานได้ใน Chrome และ Firefox แต่ id ไม่ทำงานใน Edge โปรดดู: jsfiddle.net/iwiss/t9pv24jo
iwis


7

คุณสามารถแก้ไขได้ใน Firefox ด้วยแรงบันดาลใจเล็กน้อยจากซอร์สโค้ด 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');
        }
    }

}

น่าเสียดายที่มันใช้งานไม่ได้ใน Chrome เพราะrelatedTargetดูเหมือนว่าไม่มีอยู่ในdragleaveเหตุการณ์และฉันคิดว่าคุณกำลังทำงานใน Chrome เพราะตัวอย่างของคุณใช้งานไม่ได้ใน Firefox นี่คือรุ่นที่มีโค้ดด้านบนติดตั้ง


ขอบคุณมาก แต่จริง ๆ แล้วมันเป็น Chrome ฉันพยายามที่จะแก้ปัญหานี้ใน
pimvdb

5
@pimvdb ฉันเห็นว่าคุณได้บันทึกข้อผิดพลาดแล้วฉันจะทิ้งการอ้างอิงไว้ที่นี่ในกรณีที่คนอื่นเจอคำตอบนี้
robertc

ฉันทำไปแล้ว แต่ฉันลืมเพิ่มลิงค์ที่นี่ ขอบคุณที่ทำเช่นนั้น
pimvdb

ข้อผิดพลาดของ Chrome ได้รับการแก้ไขในเวลาเฉลี่ย
phk

7

และนี่จะเป็นทางออกสำหรับ 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!');
                    }
                })

มันเข้าสู่«ถ้า (typeof event.clientX === 'undefined') »?
Aldekein

1
ทำงานได้ดี แต่อาจมีหน้าต่างอื่นผ่านเบราว์เซอร์ดังนั้นการหาตำแหน่งเมาส์และเปรียบเทียบกับพื้นที่หน้าจอสี่เหลี่ยมนั้นไม่เพียงพอ
HD

ฉันเห็นด้วยกับ @HD อีกทั้งจะทำให้เกิดปัญหาเมื่อองค์ประกอบมีขนาดใหญ่border-radiusตามที่ฉันได้อธิบายไว้ในความคิดเห็นของฉันในคำตอบของ @ azlar ด้านบน
Jay Dadhania

7

นี่คือวิธีแก้ไขปัญหาอื่นโดยใช้เอกสารองค์ประกอบจาก PointPoint:

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

หวังว่างานนี้ที่นี่เป็นไวโอลิน


3
สิ่งนี้ไม่ได้ผลสำหรับฉันออกจากกล่อง ฉันต้องการใช้event.clientX, event.clientYแทนเนื่องจากมันสัมพันธ์กับวิวพอร์ตไม่ใช่หน้าเว็บ ฉันหวังว่าจะช่วยให้วิญญาณที่หลงหายไปที่นั่นอีก
Jon Abrams

7

วิธีง่ายๆคือการเพิ่มกฎแบบ CSS ไปยังส่วนเด็กที่จะป้องกันไม่ให้ทริกเกอร์ของpointer-events: none ondragleaveดูตัวอย่าง:

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>


ฉันมีปัญหาเดียวกันและวิธีแก้ปัญหาของคุณใช้งานได้ดี เคล็ดลับสำหรับคนอื่นนี่คือกรณีของฉัน: ถ้าองค์ประกอบลูกคุณพยายามละเว้นเหตุการณ์ลากคือโคลนขององค์ประกอบที่ถูกลาก (พยายามที่จะบรรลุตัวอย่างภาพ) คุณสามารถใช้สิ่งนี้ภายในdragstartเมื่อสร้างโคลน :dragged = event.target;clone = dragged.cloneNode();clone.style.pointerEvents = 'none';
Scaramouche

4

ไม่แน่ใจว่าจะข้ามเบราว์เซอร์นี้หรือไม่ แต่ฉันทดสอบใน Chrome แล้วมันแก้ปัญหาได้ไหม:

ฉันต้องการลากและวางไฟล์ไว้ทั่วทั้งหน้า แต่ dragleave ของฉันถูกไล่ออกเมื่อฉันลากองค์ประกอบลูก การแก้ไขของฉันคือดูที่ x และ y ของเมาส์:

ฉันมี div ที่ซ้อนทับทั้งหน้าของฉันเมื่อโหลดหน้าฉันซ่อนมัน

เมื่อคุณลากไปบนเอกสารที่ฉันแสดงมันและเมื่อคุณวางบนแม่มันจัดการมันและเมื่อคุณออกจากแม่ฉันตรวจสอบ x และ 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
แฮ็ก แต่ฉลาด ฉันชอบ. ทำงานให้ฉัน
คัพเค้ก

1
โอ้ฉันหวังว่าคำตอบนี้จะได้รับคะแนนเสียงมากขึ้น! ขอบคุณ
Kirby

4

ฉันได้เขียนห้องสมุดเล็ก ๆ ชื่อว่าDragsterเพื่อจัดการปัญหานี้ทำงานได้ทุกที่ยกเว้นไม่ทำอะไรเลยใน IE (ซึ่งไม่สนับสนุน DOM Event Constructors แต่มันค่อนข้างง่ายที่จะเขียนสิ่งที่คล้ายกันโดยใช้เหตุการณ์ที่กำหนดเองของ jQuery)


มีประโยชน์มาก (อย่างน้อยสำหรับฉันที่ฉันสนใจเฉพาะ Chrome เท่านั้น)
M Katz

4

ฉันมีปัญหาเดียวกันและพยายามใช้โซลูชัน pk7s มันใช้งานได้ แต่มันสามารถทำได้ดีขึ้นเล็กน้อยโดยไม่มีองค์ประกอบ dom พิเศษ

โดยพื้นฐานแล้วความคิดนั้นเหมือนกัน - เพิ่มการซ้อนทับที่ไม่สามารถมองเห็นได้เหนือพื้นที่ที่ปล่อยทิ้งได้ ให้ทำเช่นนี้โดยไม่ต้องมีองค์ประกอบเสริม นี่คือส่วนที่มีองค์ประกอบหลอก CSS มาให้เล่น

จาวาสคริ

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

หลังจากกฎนี้จะสร้างภาพซ้อนทับที่ครอบคลุมอย่างสมบูรณ์สำหรับพื้นที่ที่ปล่อยได้

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

นี่คือวิธีการแก้ปัญหาอย่างเต็มรูปแบบ: http://jsfiddle.net/F6GDq/8/

ฉันหวังว่ามันจะช่วยให้ทุกคนที่มีปัญหาเดียวกัน


1
บางครั้งไม่ได้ทำงานในโครเมี่ยม (ไม่ได้จับ dragleave ต้อง)
Timo Huovinen

4

เพียงตรวจสอบว่าองค์ประกอบที่ลากไปนั้นเป็นเด็กหรือไม่ถ้าเป็นเช่นนั้นอย่าลบคลาสสไตล์ 'dragover' ของคุณ ค่อนข้างง่ายและใช้งานได้สำหรับฉัน:

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

ดูเหมือนทางออกที่ง่ายที่สุดสำหรับฉันและมันแก้ไขปัญหาของฉันได้ ขอบคุณ!
Francesco Marchetti-Stasi

3

ฉันพบปัญหาเดียวกันและนี่คือทางออกของฉัน - ซึ่งฉันคิดว่าง่ายกว่านั้น ฉันไม่แน่ใจว่ามันเป็น crossbrowser (อาจขึ้นอยู่กับคำสั่งที่เดือดดาล)

ฉันจะใช้ jQuery เพื่อความเรียบง่าย แต่วิธีแก้ปัญหาควรมีกรอบอิสระ

เหตุการณ์ฟองสบู่กับผู้ปกครองด้วยวิธีใดวิธีหนึ่งดังนี้:

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

เราแนบกิจกรรม

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

และนั่นคือเกี่ยวกับมัน :) มันใช้งานได้เพราะถึงแม้จะป้อนไฟเด็กก่อนที่จะปล่อยให้ผู้ปกครองเราล่าช้าเล็กน้อยกลับคำสั่งดังนั้นชั้นจะถูกลบออกก่อนแล้วนำกลับมาใช้ใหม่หลังจาก milisecond


มีเพียงสิ่งเดียวเท่านั้นที่ข้อมูลนี้ป้องกันไม่ให้คลาส 'hovered' ถูกลบออกโดยใช้รอบการทำเครื่องหมายถัดไปอีกครั้ง (ทำให้เหตุการณ์ 'dragleave' ไร้ประโยชน์)
null

มันไม่ไร้ประโยชน์ หากคุณปล่อยให้ผู้ปกครองมันจะทำงานตามที่คาดไว้ พลังของโซลูชันนี้คือความเรียบง่ายไม่เหมาะหรือดีที่สุด ทางออกที่ดีกว่าคือการทำเครื่องหมายการป้อนบนเด็กในการตรวจสอบแบบออนไลน์หากเราเพิ่งป้อนเด็กและหากไม่ให้เหตุการณ์เกิด มันจะต้องมีการทดสอบความต้องการยามพิเศษสำหรับลูกหลาน ฯลฯ
Marcin Raczkowski

3

ฉันเขียนโมดูลลากแล้วปล่อยที่เรียกว่าหยดแบบหยดซึ่งแก้ไขพฤติกรรมที่แปลกประหลาดนี้และอื่น ๆ หากคุณกำลังมองหาโมดูลลากและวางระดับต่ำที่ดีคุณสามารถใช้เป็นพื้นฐานสำหรับสิ่งใดก็ได้ (อัปโหลดไฟล์ลากและวางในแอปลากและจากแหล่งภายนอก) คุณควรตรวจสอบสิ่งนี้ โมดูลออก:

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

นี่คือวิธีที่คุณจะทำในสิ่งที่คุณกำลังพยายามทำในแบบหยด:

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

ในการทำเช่นนี้โดยไม่ต้องใช้ห้องสมุดเทคนิคการนับเป็นสิ่งที่ฉันใช้ในการวางแบบหยดโดยที่คำตอบที่ได้รับคะแนนสูงสุดพลาดขั้นตอนสำคัญที่จะทำให้ทุกสิ่งแตกต่างไปจากทุกอย่างยกเว้นการดรอปครั้งแรก นี่คือวิธีการทำอย่างถูกต้อง:

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

ทางเลือกในการแก้ปัญหาการทำงานง่ายขึ้นเล็กน้อย

//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) {

สิ่งนี้ดูเหมือนจะไม่ทำงานใน Chrome เสมอไป ในบางครั้งฉันจะได้รับclientXสูงกว่า 0 เมื่อเมาส์อยู่นอกกล่อง ได้รับองค์ประกอบของฉันคือposition:absolute
Hengjie

มันเกิดขึ้นตลอดเวลาหรือบางครั้งเท่านั้น? เพราะหากเมาส์เคลื่อนเร็วเกินไป (นอกหน้าต่าง) คุณอาจได้รับค่าที่ผิด
Profet

มันเกิดขึ้น 90% ของเวลา มีบางกรณีที่ไม่ค่อยเกิดขึ้น (1 จาก 10 ครั้ง) ที่ฉันสามารถทำได้ถึง 0 ฉันจะลองเลื่อนเมาส์อีกครั้งช้าลง แต่ฉันไม่สามารถพูดได้ว่าฉันกำลังเคลื่อนที่อย่างรวดเร็ว (บางทีคุณอาจเรียกความเร็วปกติ)
Hengjie

2

หลังจากใช้เวลาหลายชั่วโมงฉันก็ได้รับคำแนะนำนั้นทำงานตรงตามที่ตั้งใจไว้ ฉันต้องการให้คิวเฉพาะเมื่อไฟล์ถูกลากไปและการลากเอกสาร dragleave ทำให้เกิดอาการสั่นไหวในเบราว์เซอร์ 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
});

1

ฉันมีปัญหาที่คล้ายกัน - รหัสของฉันสำหรับการซ่อน dropzone ในเหตุการณ์ dragleave สำหรับร่างกายถูกไล่ออกอย่างต่อเนื่องเมื่อโฮเวอร์องค์ประกอบของเด็กที่ทำให้ Dropzone สั่นไหวใน Google Chrome

ฉันสามารถแก้ปัญหานี้ได้โดยการตั้งเวลาฟังก์ชั่นสำหรับซ่อน dropzone แทนที่จะเรียกมันทันที จากนั้นหากมีการปล่อย dragover หรือ dragleave อื่นการเรียกใช้ฟังก์ชันตามกำหนดการจะถูกยกเลิก

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

นี่คือสิ่งที่ฉันทำลงเอยด้วย แต่ก็ยังคงร่าง
mpen

1

" dragleave " เหตุการณ์ถูกยิงเมื่อชี้เมาส์ออกจากพื้นที่การลากของภาชนะเป้าหมาย

ซึ่งจะทำให้ความรู้สึกมากในหลายกรณีมีเพียงผู้ปกครองอาจจะdroppableและไม่ใช่ลูกหลาน ฉันคิดว่าevent.stopPropogation ()ควรจัดการกรณีนี้ แต่ดูเหมือนว่ามันไม่ได้ทำเคล็ดลับ

ดังกล่าวข้างต้นแก้ปัญหาบางอย่างดูเหมือนจะทำงานให้กับส่วนของคดี แต่ล้มเหลวในกรณีของเด็กเหล่านั้นซึ่งไม่สนับสนุนDragEnter / dragleaveเหตุการณ์เช่นiframe

1 วิธีแก้ปัญหาคือการตรวจสอบเหตุการณ์ที่เกี่ยวข้องเป้าหมายและตรวจสอบว่ามันอยู่ในภาชนะแล้วละเว้นเหตุการณ์dragleaveที่ฉันได้ทำที่นี่:

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

คุณสามารถหาซอทำงานได้ที่นี่ !


1

แก้ไขแล้ว .. !

ประกาศอาร์เรย์ใด ๆ เช่น:

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

ไม่จำเป็นต้องกังวลเกี่ยวกับองค์ประกอบของเด็ก


1

ฉันต่อสู้กับสิ่งนี้เป็นอย่างมากแม้หลังจากอ่านคำตอบทั้งหมดเหล่านี้แล้วและคิดว่าฉันอาจแบ่งปันวิธีแก้ปัญหาของคุณกับคุณเพราะฉันคิดว่ามันอาจเป็นหนึ่งในวิธีที่ง่ายกว่า ความคิดของฉันเป็นเพียงการละเว้นdragleaveผู้ฟังเหตุการณ์อย่างสมบูรณ์และเขียนโค้ดพฤติกรรม dragleave ด้วยเหตุการณ์ dragenter ใหม่แต่ละเหตุการณ์ถูกเรียกใช้ในขณะที่ตรวจสอบให้แน่ใจว่าเหตุการณ์ Dragenter จะไม่ถูกเรียกใช้โดยไม่จำเป็น

ในตัวอย่างของฉันด้านล่างฉันมีตารางที่ฉันต้องการแลกเปลี่ยนเนื้อหาแถวของตารางด้วยกันผ่าน API การลากและวาง ในdragenterชั้น CSS จะต้องเพิ่มองค์ประกอบแถวที่คุณกำลังลากองค์ประกอบของคุณเพื่อเน้นมันและในdragleaveชั้นนี้จะถูกลบออก

ตัวอย่าง:

ตาราง HTML พื้นฐานมาก:

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

และฟังก์ชันตัวจัดการเหตุการณ์ dragenter ที่เพิ่มลงในแต่ละเซลล์ของตาราง (นอกเหนือจากdragstart, dragoverและdrop, dragendตัวจัดการซึ่งไม่เจาะจงสำหรับคำถามนี้ดังนั้นจึงไม่คัดลอกมาที่นี่):

/*##############################################################################
##                              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;
  }
}

ความคิดเห็นพื้นฐานเหลือไว้ในโค้ดเช่นนี้เหมาะสำหรับผู้เริ่มต้นด้วย หวังว่านี่จะช่วยคุณได้! โปรดทราบว่าคุณจะต้องเพิ่มผู้ฟังเหตุการณ์ทั้งหมดที่ฉันกล่าวถึงข้างต้นลงในแต่ละเซลล์ตารางเพื่อให้สามารถใช้งานได้


0

นี่คือวิธีการอื่นตามเวลาของเหตุการณ์

เหตุการณ์ส่งมาจากองค์ประกอบที่เด็กสามารถบันทึกโดยองค์ประกอบหลักและมันก็เกิดขึ้นก่อนdragenter dragleaveเวลาระหว่างเหตุการณ์สองเหตุการณ์นี้สั้นมากสั้นกว่าการกระทำของเมาส์มนุษย์ที่เป็นไปได้ ดังนั้นความคิดคือการจดจำเวลาที่dragenterเกิดขึ้นและกรองdragleaveเหตุการณ์ที่เกิดขึ้น "ไม่เร็วเกินไป" หลังจาก ...

ตัวอย่างสั้น ๆ นี้ใช้ได้กับ Chrome และ 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 ..

ทำไมคุณไม่ลองใช้ลดลงแทนdragleave มันใช้งานได้สำหรับฉัน หวังว่านี่จะช่วยแก้ปัญหาของคุณ

โปรดตรวจสอบ 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
หากผู้ใช้เปลี่ยนใจและลากไฟล์ออกจากเบราว์เซอร์จะยังคงเป็นสีแดง
ZachB

0

คุณสามารถใช้การหมดเวลากับการtransitioningตั้งค่าสถานะและฟังบนองค์ประกอบด้านบน dragenter/ dragleaveจากเหตุการณ์เด็กจะเกิดฟองขึ้นที่คอนเทนเนอร์

เนื่องจากdragenterองค์ประกอบลูกยิงก่อนหน้าdragleaveคอนเทนเนอร์เราจะตั้งค่าสถานะการแสดงเป็นการเปลี่ยนเป็นเวลา 1 มิลลิวินาที ... dragleaveผู้ฟังจะตรวจสอบการตั้งค่าสถานะก่อนที่จะหมดเวลา 1 มิลลิวินาที

การตั้งค่าสถานะจะเป็นจริงเฉพาะในช่วงการเปลี่ยนภาพเป็นองค์ประกอบลูกและจะไม่เป็นจริงเมื่อเปลี่ยนไปใช้องค์ประกอบหลัก (ของคอนเทนเนอร์)

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

คุณต้องลบกิจกรรมตัวชี้สำหรับวัตถุลูกทั้งหมดของเป้าหมายการลาก

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) {
                //
            }
        }
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.