โพสต์นี้อธิบายพื้นฐานของการลากและวาง
สร้างเนื้อหาที่ลากได้
ในเบราว์เซอร์ส่วนใหญ่ คุณจะลากการเลือกข้อความ รูปภาพ และลิงก์ได้ เช่น หากลากลิงก์ในหน้าเว็บ คุณจะเห็นช่องเล็กๆ ที่มีชื่อและ URL ซึ่งคุณสามารถวางในแถบที่อยู่หรือเดสก์ท็อปเพื่อสร้างทางลัดหรือไปยังลิงก์ได้ หากต้องการให้เนื้อหาประเภทอื่นๆ ลากได้ คุณต้องใช้ HTML5 Drag and Drop API
หากต้องการให้ลากวัตถุได้ ให้ตั้งค่า draggable=true
ในองค์ประกอบนั้น คุณสามารถลากเกือบทุกอย่างได้ ซึ่งรวมถึงรูปภาพ ไฟล์ ลิงก์ หรือมาร์กอัปใดๆ ในหน้า
ตัวอย่างต่อไปนี้สร้างอินเทอร์เฟซเพื่อจัดเรียงคอลัมน์ที่จัดวางด้วย CSS Grid ใหม่ มาร์กอัปพื้นฐานสำหรับคอลัมน์จะมีลักษณะดังนี้ โดยตั้งค่าแอตทริบิวต์ draggable
ของแต่ละคอลัมน์เป็น true
<div class="container">
<div draggable="true" class="box">A</div>
<div draggable="true" class="box">B</div>
<div draggable="true" class="box">C</div>
</div>
CSS สำหรับองค์ประกอบคอนเทนเนอร์และกล่องมีดังนี้ CSS ที่เกี่ยวข้องกับฟีเจอร์การลากมีเพียงพร็อพเพอร์ตี้ cursor: move
เท่านั้น โค้ดที่เหลือจะควบคุมเลย์เอาต์และการจัดสไตล์ขององค์ประกอบคอนเทนเนอร์และช่อง
.container {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
}
.box {
border: 3px solid #666;
background-color: #ddd;
border-radius: .5em;
padding: 10px;
cursor: move;
}
เมื่อถึงจุดนี้ คุณจะลากรายการได้ แต่จะไม่มีอะไรเกิดขึ้น หากต้องการเพิ่มลักษณะการทำงาน คุณต้องใช้ JavaScript API
รอรับเหตุการณ์การลาก
หากต้องการตรวจสอบกระบวนการลาก คุณสามารถฟังเหตุการณ์ต่อไปนี้
หากต้องการจัดการขั้นตอนการลาก คุณต้องมีองค์ประกอบแหล่งที่มาบางประเภท (จุดเริ่มต้นของการลาก) เพย์โหลดข้อมูล (สิ่งที่ลาก) และเป้าหมาย (พื้นที่ที่จะรับการวาง) องค์ประกอบแหล่งที่มาอาจเป็นองค์ประกอบเกือบทุกประเภท เป้าหมายคือโซนวางหรือชุดโซนวางที่ยอมรับข้อมูลที่ผู้ใช้พยายามวาง องค์ประกอบบางรายการไม่สามารถกำหนดเป้าหมายได้ เช่น เป้าหมายต้องไม่ใช่รูปภาพ
เริ่มและสิ้นสุดลำดับการลาก
หลังจากกำหนดแอตทริบิวต์ draggable="true"
ในเนื้อหาแล้ว ให้แนบตัวแฮนเดิลเหตุการณ์ dragstart
เพื่อเริ่มลำดับการลากสำหรับแต่ละคอลัมน์
โค้ดนี้จะตั้งค่าความทึบของคอลัมน์เป็น 40% เมื่อผู้ใช้เริ่มลาก แล้วเปลี่ยนกลับไปเป็น 100% เมื่อเหตุการณ์การลากสิ้นสุด
function handleDragStart(e) {
this.style.opacity = '0.4';
}
function handleDragEnd(e) {
this.style.opacity = '1';
}
let items = document.querySelectorAll('.container .box');
items.forEach(function (item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragend', handleDragEnd);
});
ผลลัพธ์จะแสดงในตัวอย่าง Glitch ต่อไปนี้ ลากรายการแล้วความทึบแสงของรายการจะเปลี่ยนไป เนื่องจากองค์ประกอบแหล่งที่มามีเหตุการณ์ dragstart
การตั้งค่า this.style.opacity
เป็น 40% จะให้ฟีดแบ็กภาพแก่ผู้ใช้ว่าองค์ประกอบนั้นคือการเลือกปัจจุบันที่กำลังย้าย เมื่อวางรายการ องค์ประกอบแหล่งที่มาจะกลับไปมีความทึบแสง 100% แม้ว่าคุณจะยังไม่ได้กำหนดลักษณะการทำงานในการวางก็ตาม
เพิ่มตัวช่วยด้านภาพเพิ่มเติม
ใช้ตัวแฮนเดิลเหตุการณ์ dragenter
, dragover
และ dragleave
เพื่อช่วยผู้ใช้ให้เข้าใจวิธีโต้ตอบกับอินเทอร์เฟซ ในตัวอย่างนี้ คอลัมน์จะเป็นเป้าหมายการวางนอกเหนือจากการลากได้ ช่วยให้ผู้ใช้เข้าใจสิ่งนี้โดยทำให้เส้นขอบเป็นเส้นประเมื่อผู้ใช้ลากรายการไปไว้เหนือคอลัมน์ เช่น ใน CSS คุณอาจสร้างคลาส over
สำหรับองค์ประกอบที่เป็นเป้าหมายการลากวาง
.box.over {
border: 3px dotted #666;
}
จากนั้นใน JavaScript ให้ตั้งค่าตัวแฮนเดิลเหตุการณ์ เพิ่มคลาส over
เมื่อมีการลากคอลัมน์มาวาง และนำคลาสออกเมื่อองค์ประกอบที่ลากออก ในตัวแฮนเดิล dragend
เราจะนําคลาสออกเมื่อการลากสิ้นสุดลงด้วย
document.addEventListener('DOMContentLoaded', (event) => {
function handleDragStart(e) {
this.style.opacity = '0.4';
}
function handleDragEnd(e) {
this.style.opacity = '1';
items.forEach(function (item) {
item.classList.remove('over');
});
}
function handleDragOver(e) {
e.preventDefault();
return false;
}
function handleDragEnter(e) {
this.classList.add('over');
}
function handleDragLeave(e) {
this.classList.remove('over');
}
let items = document.querySelectorAll('.container .box');
items.forEach(function(item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragover', handleDragOver);
item.addEventListener('dragenter', handleDragEnter);
item.addEventListener('dragleave', handleDragLeave);
item.addEventListener('dragend', handleDragEnd);
item.addEventListener('drop', handleDrop);
});
});
โค้ดนี้มีข้อควรทราบ 2-3 ข้อดังนี้
การดำเนินการเริ่มต้นสำหรับเหตุการณ์
dragover
คือการตั้งค่าพร็อพเพอร์ตี้dataTransfer.dropEffect
เป็น"none"
พร็อพเพอร์ตี้dropEffect
จะกล่าวถึงในหน้านี้ต่อ ในระหว่างนี้ ให้ทราบว่าการดำเนินการนี้ป้องกันไม่ให้เหตุการณ์drop
ทำงาน หากต้องการลบล้างลักษณะการทำงานนี้ ให้โทรหาe.preventDefault()
แนวทางปฏิบัติแนะนำอีกอย่างหนึ่งคือให้แสดงผลfalse
ในตัวแฮนเดิลเดียวกันนั้นตัวแฮนเดิลเหตุการณ์
dragenter
ใช้เพื่อเปิด/ปิดคลาสover
แทนdragover
หากคุณใช้dragover
เหตุการณ์จะทริกเกอร์ซ้ำๆ ขณะที่ผู้ใช้ถือรายการที่ลากไว้เหนือคอลัมน์อยู่ ซึ่งทําให้คลาส CSS เปิด/ปิดซ้ำๆ ซึ่งทำให้เบราว์เซอร์ต้องทำงานแสดงผลที่ไม่จำเป็นเป็นจำนวนมาก ซึ่งอาจส่งผลต่อประสบการณ์ของผู้ใช้ เราขอแนะนําอย่างยิ่งให้ลดการวาดภาพใหม่ และหากจําเป็นต้องใช้dragover
ให้พิจารณาควบคุมหรือยกเลิกการหน่วงเวลา Listener เหตุการณ์
วางสินค้าให้เสร็จสิ้น
หากต้องการประมวลผลรายการที่วาง ให้เพิ่ม Listener เหตุการณ์สําหรับเหตุการณ์ drop
ในตัวแฮนเดิล drop
คุณจะต้องป้องกันลักษณะการทํางานเริ่มต้นของเบราว์เซอร์สําหรับการหยุดทำงาน ซึ่งโดยปกติแล้วคือการเปลี่ยนเส้นทางที่น่ารําคาญ โดยโทรไปที่ e.stopPropagation()
function handleDrop(e) {
e.stopPropagation(); // stops the browser from redirecting.
return false;
}
อย่าลืมลงทะเบียนตัวแฮนเดิลใหม่ควบคู่ไปกับตัวแฮนเดิลอื่นๆ ดังนี้
let items = document.querySelectorAll('.container .box');
items.forEach(function(item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragover', handleDragOver);
item.addEventListener('dragenter', handleDragEnter);
item.addEventListener('dragleave', handleDragLeave);
item.addEventListener('dragend', handleDragEnd);
item.addEventListener('drop', handleDrop);
});
หากคุณเรียกใช้โค้ด ณ จุดนี้ รายการจะไม่วางไปยังตำแหน่งใหม่ หากต้องการดำเนินการดังกล่าว ให้ใช้ออบเจ็กต์ DataTransfer
พร็อพเพอร์ตี้ dataTransfer
จะเก็บข้อมูลที่ส่งในการดําเนินการลาก dataTransfer
มีการตั้งค่าในเหตุการณ์ dragstart
และอ่านหรือจัดการในเหตุการณ์การปล่อย การเรียกใช้ e.dataTransfer.setData(mimeType, dataPayload)
ช่วยให้คุณตั้งค่าประเภท MIME และเพย์โหลดข้อมูลของออบเจ็กต์ได้
ในตัวอย่างนี้ เราจะอนุญาตให้ผู้ใช้จัดเรียงลำดับของคอลัมน์ใหม่ โดยก่อนอื่นคุณต้องจัดเก็บ HTML ขององค์ประกอบต้นทางเมื่อการลากเริ่มขึ้น ดังนี้
function handleDragStart(e) {
this.style.opacity = '0.4';
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
ในเหตุการณ์ drop
คุณจะประมวลผลการวางคอลัมน์โดยตั้งค่า HTML ของคอลัมน์ต้นทางเป็น HTML ของคอลัมน์เป้าหมายที่คุณวางข้อมูล ซึ่งรวมถึงการตรวจสอบว่าผู้ใช้ไม่ได้วางกลับไปยังคอลัมน์เดียวกับที่ลากมา
function handleDrop(e) {
e.stopPropagation();
if (dragSrcEl !== this) {
dragSrcEl.innerHTML = this.innerHTML;
this.innerHTML = e.dataTransfer.getData('text/html');
}
return false;
}
คุณดูผลลัพธ์ได้ในเดโมต่อไปนี้ คุณต้องใช้เบราว์เซอร์ในเดสก์ท็อปจึงจะใช้ฟีเจอร์นี้ได้ อุปกรณ์เคลื่อนที่ไม่รองรับ Drag and Drop API ลากและวางคอลัมน์ A ไว้ด้านบนของคอลัมน์ B แล้วสังเกตการเปลี่ยนแปลงตำแหน่งของคอลัมน์
คุณสมบัติการลากเพิ่มเติม
ออบเจ็กต์ dataTransfer
จะแสดงพร็อพเพอร์ตี้เพื่อให้ความคิดเห็นแบบภาพแก่ผู้ใช้ในระหว่างกระบวนการลาก และควบคุมวิธีที่แต่ละเป้าหมายการปล่อยตอบสนองต่อประเภทข้อมูลหนึ่งๆ
dataTransfer.effectAllowed
จำกัด "ประเภทการลาก" ที่ผู้ใช้จะทำกับองค์ประกอบได้ ซึ่งใช้ในรูปแบบการประมวลผลแบบลากและวางเพื่อเริ่มต้นdropEffect
ระหว่างเหตุการณ์dragenter
และdragover
พร็อพเพอร์ตี้นี้มีค่าได้ดังนี้none
,copy
,copyLink
,copyMove
,link
,linkMove
,move
,all
และuninitialized
dataTransfer.dropEffect
ควบคุมความคิดเห็นที่ผู้ใช้ได้รับระหว่างเหตุการณ์dragenter
และdragover
เมื่อผู้ใช้วางเคอร์เซอร์เหนือองค์ประกอบเป้าหมาย เคอร์เซอร์ของเบราว์เซอร์จะระบุประเภทการดำเนินการที่จะเกิดขึ้น เช่น การคัดลอกหรือการย้าย ผลลัพธ์อาจเป็นค่าใดค่าหนึ่งต่อไปนี้none
,copy
,link
,move
e.dataTransfer.setDragImage(imgElement, x, y)
หมายความว่าคุณสามารถตั้งค่าไอคอนการลากแทนการใช้การตอบกลับ "ภาพผี" เริ่มต้นของเบราว์เซอร์
อัปโหลดไฟล์
ตัวอย่างง่ายๆ นี้ใช้คอลัมน์เป็นทั้งแหล่งที่มาของการลากและเป้าหมายการลาก กรณีนี้อาจเกิดขึ้นใน UI ที่ขอให้ผู้ใช้จัดเรียงรายการใหม่ ในบางสถานการณ์ เป้าหมายการลากและแหล่งที่มาอาจเป็นองค์ประกอบประเภทต่างๆ เช่น ในอินเทอร์เฟซที่ผู้ใช้ต้องเลือกรูปภาพ 1 รูปเป็นรูปภาพหลักของผลิตภัณฑ์โดยการลากรูปภาพที่เลือกไปยังเป้าหมาย
การลากและวางมักใช้เพื่อให้ผู้ใช้ลากรายการจากเดสก์ท็อปไปยังแอปพลิเคชันได้ ความแตกต่างหลักอยู่ที่ตัวแฮนเดิล drop
ข้อมูลของไฟล์จะอยู่ในพร็อพเพอร์ตี้ dataTransfer.files
แทนที่จะใช้ dataTransfer.getData()
เพื่อเข้าถึงไฟล์
function handleDrop(e) {
e.stopPropagation(); // Stops some browsers from redirecting.
e.preventDefault();
var files = e.dataTransfer.files;
for (var i = 0, f; (f = files[i]); i++) {
// Read the File objects in this FileList.
}
}
ดูข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ในการลากและวางที่กำหนดเอง