Tạo một phần tử có thể kéo

Thêm thao tác kéo vào một phần tử nổi hiện có, chẳng hạn như bảng điều khiển, bảng màu, thanh công cụ, trình kiểm tra hoặc hộp thoại. Thao tác kéo không xác định cách phần tử được hiển thị, đóng, xếp chồng hoặc lấy tiêu điểm.

Sử dụng tên người dùng riêng

Bắt đầu kéo từ một ô điều khiển chuyên dụng khi phần tử chứa văn bản, nút, dữ liệu đầu vào hoặc đường liên kết có thể chọn. Điều này giúp nội dung thông thường của tệp vẫn có thể sử dụng được. Đặt tay điều khiển ở trạng thái có thể làm tâm điểm cho thao tác di chuyển bằng bàn phím và đặt tên thành phần hỗ trợ tiếp cận cho tay điều khiển đó, trong đó đề cập đến thao tác tương tác bằng phím mũi tên.

Không bôi role="application" lên tay cầm. Nó thay đổi không cần thiết hoạt động tương tác của công nghệ hỗ trợ.

Áp dụng cursor: grab/grabbing, touch-action: none và cả user-select: none và -webkit-user-select: none cho tay điều khiển (không phải toàn bộ phần tử có thể kéo) để văn bản bên ngoài tay điều khiển vẫn có thể chọn được và cử chỉ chạm vẫn giữ nguyên hành vi thông thường:

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

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

Giữ nguyên vị trí và khả năng tiếp cận của con trỏ

Sử dụng mô hình định vị tương đối theo khung nhìn, chẳng hạn như position: fixed khi giới hạn chuyển động trong khung nhìn.

  • Chỉ bắt đầu kéo cho nút con trỏ chính (event.button === 0).
  • Ghi lại độ lệch con trỏ trên pointerdown và sử dụng độ lệch đó cho mỗi lần di chuyển để phần tử không bị nhảy khi được kéo ra khỏi góc.
  • Tiếp tục kéo sau khi con trỏ rời khỏi tay điều khiển. Mặc định là setPointerCapture() và xoá trạng thái kéo trên lostpointercapture. Nếu tay điều khiển có thể bị xoá hoặc kết xuất lại giữa chừng, hãy sử dụng trình nghe pointermove, pointerup và pointercancel ở cấp tài liệu, vì thao tác chụp sẽ bị mất trong trường hợp đó.
  • Nếu muốn, hãy đợi khoảng 4px di chuyển trước khi chuyển sang trạng thái kéo, để thao tác nhấp vào tay cầm không kích hoạt kiểu kéo.

Khi chụp con trỏ, trình nghe chuyển động có thể vẫn nằm trên tay cầm:

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`;
}

Giới hạn mọi vị trí của con trỏ và bàn phím thông qua cùng một trình trợ giúp để phần tử luôn có thể truy cập được. Giới hạn lại vị trí hiện tại sau khi thay đổi khung nhìn hoặc kích thước phần tử. Đối với các phần tử có bố cục nặng, hãy cân nhắc việc dịch trong khi kéo để tránh bố cục theo khung hình, sau đó áp dụng vị trí cuối cùng cho left và top khi thả.

Cung cấp thao tác di chuyển bằng bàn phím

Khi tay điều khiển có tiêu điểm, hãy di chuyển phần tử bằng các phím mũi tên và gọi preventDefault() cho các phím đó. Sử dụng cùng một trình trợ giúp định vị được kẹp làm chuyển động của con trỏ. Mức tăng 15px là điểm bắt đầu hợp lý; hãy điều chỉnh mức tăng này cho phù hợp với kích thước và nhu cầu về độ chính xác của thành phần.

Ghi chú dành riêng cho hộp thoại

<dialog> hoặc [popover] có thể sử dụng cơ chế này với tiêu đề làm tay điều khiển. Đặt margin: 0 trước khi chỉ định left và top, vì lề mặc định của trình duyệt can thiệp vào việc định vị thủ công. Để biết hành vi mở, đóng, lấy tiêu điểm và đóng hộp thoại cũng như cửa sổ bật lên, hãy xem bài viết Điều khiển hộp thoại và cửa sổ bật lên theo cách khai báo và Điều khiển nền tảng đóng hộp thoại.

Chiến lược dự phòng

Khi thao tác kéo chỉ định vị lại một thành phần, hãy coi đó là cải tiến tăng dần: không có JavaScript, hãy giữ cho phần tử, nội dung và các thành phần điều khiển của phần tử đó hiển thị, dễ đọc và hoạt động ở vị trí mặc định hợp lý. Không hiển thị tay cầm kéo không hoạt động: nếu tay cầm là một thành phần điều khiển tay cầm độc lập, hãy ẩn tay cầm theo mặc định và hiển thị tay cầm khi JavaScript khởi chạy; nếu tiêu đề đóng vai trò là tay cầm, hãy đính kèm các chức năng kéo của tiêu đề khi JavaScript khởi chạy.

Thêm -webkit-user-select: none trước user-select: none để lựa chọn văn bản vẫn bị chặn trong khi kéo trong Safari.