สร้างองค์ประกอบที่ลากได้

ฟีเจอร์บนเว็บ: Pointer Events touch-action user-select

เพิ่มการลากไปยังองค์ประกอบแบบลอยที่มีอยู่ เช่น แผง จานสี แถบเครื่องมือ ตัวตรวจสอบ หรือกล่องโต้ตอบ การลากไม่ได้กำหนดวิธีแสดง ปิด ซ้อน หรือโฟกัสองค์ประกอบ

ใช้แฮนเดิลเฉพาะ

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

อย่าใช้ role="application" กับแฮนเดิล ซึ่งจะเปลี่ยนการโต้ตอบของเทคโนโลยีความช่วยเหลือพิเศษโดยไม่จำเป็น

ใช้ cursor: grab/grabbing, touch-action: none และทั้ง user-select: none และ -webkit-user-select: none กับแฮนเดิล ไม่ใช่องค์ประกอบที่ลากได้ทั้งหมด เพื่อให้ข้อความภายนอกยังคงเลือกได้ และท่าทางสัมผัสยังคงทำงานตามปกติ ดังนี้

.drag-handle {
  cursor: grab;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

.drag-handle.dragging {
  cursor: grabbing;
}

คงตำแหน่งตัวชี้และความสามารถในการเข้าถึง

ใช้รูปแบบการวางตำแหน่งที่สัมพันธ์กับวิวพอร์ต เช่น position: fixed เมื่อจำกัดการเคลื่อนไหวไว้ที่วิวพอร์ต

  • เริ่มการลากสำหรับปุ่มตัวชี้หลัก (event.button === 0) เท่านั้น
  • บันทึกออฟเซ็ตของเคอร์เซอร์ใน pointerdown และใช้ในการเคลื่อนไหวทุกครั้งเพื่อไม่ให้องค์ประกอบกระโดดเมื่อจับออกจากมุม
  • คงการลากไว้หลังจากที่ตัวชี้ออกจากแฮนเดิล ค่าเริ่มต้นเป็น setPointerCapture() และล้างสถานะการลากใน lostpointercapture หากอาจมีการนำแฮนเดิลออกหรือแสดงผลอีกครั้งระหว่างการลาก ให้ใช้ Listener pointermove, pointerup และ pointercancel ระดับเอกสารแทน เนื่องจากระบบจะสูญเสียการจับภาพในกรณีดังกล่าว
  • หรือจะรอให้มีการเคลื่อนไหวประมาณ 4 พิกเซลก่อนเข้าสู่สถานะการลาก เพื่อไม่ให้การคลิกแฮนเดิลทริกเกอร์การจัดรูปแบบการลาก

เมื่อใช้การจับตัวชี้ Listener การเคลื่อนไหวจะยังคงอยู่ที่แฮนเดิลได้ ดังนี้

handle.addEventListener('pointerdown', (event) => {
  if (event.button !== 0 || event.target.closest('button, input, select, textarea, a')) return;

  const rect = draggable.getBoundingClientRect();
  offsetX = event.clientX - rect.left;
  offsetY = event.clientY - rect.top;
  handle.setPointerCapture(event.pointerId);
});

handle.addEventListener('pointermove', (event) => {
  if (!handle.hasPointerCapture(event.pointerId)) return;
  handle.classList.add('dragging');
  moveTo(event.clientX - offsetX, event.clientY - offsetY);
});

handle.addEventListener('lostpointercapture', () => {
  handle.classList.remove('dragging');
});

function moveTo(left, top) {
  const maxLeft = Math.max(0, window.innerWidth - draggable.offsetWidth);
  const maxTop = Math.max(0, window.innerHeight - draggable.offsetHeight);

  draggable.style.left = `${Math.max(0, Math.min(left, maxLeft))}px`;
  draggable.style.top = `${Math.max(0, Math.min(top, maxTop))}px`;
}

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

รองรับการเคลื่อนไหวด้วยแป้นพิมพ์

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

หมายเหตุเฉพาะกล่องโต้ตอบ

<dialog> หรือ [popover] สามารถใช้กลไกนี้โดยมีส่วนหัวเป็นแฮนเดิล ตั้งค่า margin: 0 ก่อนกำหนด left และ top เนื่องจากระยะขอบเริ่มต้นของเบราว์เซอร์จะรบกวนการกำหนดตำแหน่งด้วยตนเอง ดูพฤติกรรมการเปิด ปิด โฟกัส และปิดกล่องโต้ตอบและป๊อปโอเวอร์ได้ที่การควบคุมกล่องโต้ตอบและป๊อปโอเวอร์แบบประกาศและกล่องโต้ตอบปิดการควบคุมแพลตฟอร์ม

กลยุทธ์สำรอง

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

ใส่ -webkit-user-select: none ก่อน user-select: none เพื่อให้การเลือกข้อความยังคงถูกระงับระหว่างการลากใน Safari