Membuat Elemen yang Dapat Ditarik

Menambahkan penarikan ke elemen mengambang yang ada seperti panel, palet, toolbar, inspector, atau dialog. Menarik tidak menentukan cara elemen ditampilkan, ditutup, ditumpuk, atau difokuskan.

Menggunakan nama sebutan channel khusus

Mulai tarik dari tuas khusus saat elemen berisi teks, tombol, input, atau link yang dapat dipilih. Hal ini membuat konten normalnya dapat digunakan. Buat tuas dapat difokuskan untuk gerakan keyboard dan beri label aksesibilitas yang menyebutkan interaksi tombol panah.

Jangan oleskan role="application" ke gagang. Hal ini akan mengubah interaksi teknologi pendukung tanpa alasan yang jelas.

Terapkan cursor: grab/grabbing, touch-action: none, serta user-select: none dan -webkit-user-select: none ke tuas—bukan seluruh elemen yang dapat ditarik—sehingga teks di luarnya tetap dapat dipilih dan gestur sentuh mempertahankan perilaku biasanya:

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

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

Mempertahankan posisi dan jangkauan pointer

Gunakan model pemosisian yang relatif terhadap area pandang seperti position: fixed saat membatasi gerakan ke area pandang.

  • Mulai operasi tarik hanya untuk tombol pointer utama (event.button === 0).
  • Catat offset pointer pada pointerdown dan gunakan pada setiap gerakan agar elemen tidak melompat saat diambil dari sudutnya.
  • Biarkan tindakan tarik tetap aktif setelah pointer meninggalkan tuas. Default ke setPointerCapture() dan hapus status penyeretan di lostpointercapture. Jika tuas dapat dihapus atau dirender ulang di tengah proses tarik, gunakan pemroses pointermove, pointerup, dan pointercancel tingkat dokumen, karena pengambilan akan hilang dalam kasus tersebut.
  • Jika perlu, tunggu gerakan sekitar 4 px sebelum memasuki status menarik, sehingga klik pada tuas tidak memicu gaya tarik.

Dengan penangkapan pointer, pemroses gerakan dapat tetap berada di tuas:

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

Batasi setiap posisi pointer dan keyboard melalui helper yang sama agar elemen tetap dapat dijangkau. Menjepit ulang posisi saat ini setelah perubahan ukuran elemen atau area pandang. Untuk elemen yang memerlukan banyak tata letak, pertimbangkan untuk menerjemahkan selama penyeretan untuk menghindari tata letak per frame, lalu tetapkan posisi akhir ke left dan top saat dilepaskan.

Menyediakan gerakan keyboard

Saat tuas memiliki fokus, pindahkan elemen dengan tombol panah dan panggil preventDefault() untuk tombol tersebut. Gunakan helper pemosisian yang dibatasi yang sama dengan gerakan pointer. Penambahan 15 px adalah titik awal yang wajar; sesuaikan dengan ukuran komponen dan kebutuhan presisi.

Catatan khusus dialog

<dialog> atau [popover] dapat menggunakan mekanisme ini dengan header-nya sebagai handle. Tetapkan margin: 0 sebelum menetapkan left dan top, karena margin default browser mengganggu penempatan manual. Untuk perilaku pembukaan, penutupan, fokus, dan penutupan dialog dan popover, lihat Kontrol Dialog dan Popover Deklaratif serta Kontrol Platform Menutup Dialog.

Strategi penggantian

Jika menarik hanya memosisikan ulang komponen, perlakukan sebagai progressive enhancement: tanpa JavaScript, pastikan elemen, konten, dan kontrolnya tetap terlihat, dapat dibaca, dan berfungsi dalam posisi default yang wajar. Jangan mengekspos tuas tarik yang tidak berfungsi: jika tuas adalah kontrol pegangan mandiri, sembunyikan secara default dan tampilkan saat JavaScript melakukan inisialisasi; jika header berfungsi ganda sebagai tuas, lampirkan affordance tariknya saat JavaScript melakukan inisialisasi.

Sertakan -webkit-user-select: none sebelum user-select: none agar pemilihan teks tetap ditiadakan selama penyeretan di Safari.