Agrega la función de arrastrar a un elemento flotante existente, como un panel, una paleta, una barra de herramientas, un inspector o un diálogo. Arrastrar no determina cómo se muestra, descarta, apila o enfoca el elemento.
Usa un identificador exclusivo
Comienza a arrastrar desde un controlador dedicado cuando el elemento contenga texto, botones, entradas o vínculos seleccionables. Esto mantiene su contenido normal utilizable. Haz que el controlador sea enfocable para el movimiento con el teclado y asígnale un nombre de accesibilidad que mencione la interacción con las teclas de flecha.
No apliques role="application" al mango. Cambia innecesariamente la interacción con la tecnología de accesibilidad.
Aplica cursor: grab/grabbing, touch-action: none y user-select: none y -webkit-user-select: none al controlador, no a todo el elemento arrastrable, para que se pueda seleccionar el texto que está fuera de él y los gestos táctiles conserven su comportamiento habitual:
.drag-handle {
cursor: grab;
touch-action: none;
-webkit-user-select: none;
user-select: none;
}
.drag-handle.dragging {
cursor: grabbing;
}
Preserva la posición y la accesibilidad del puntero
Usa un modelo de posicionamiento relativo al viewport, como position: fixed, cuando limites el movimiento al viewport.
- Inicia un arrastre solo para el botón principal del puntero (
event.button === 0). - Registra el desplazamiento del puntero en
pointerdowny úsalo en cada movimiento para que el elemento no salte cuando se lo tome desde su esquina. - Mantén el arrastre activo después de que el puntero salga del controlador. Se establece de forma predeterminada en
setPointerCapture()y se borra el estado de arrastre enlostpointercapture. Si el controlador se puede quitar o volver a renderizar a mitad del arrastre, usa los objetos de escuchapointermove,pointerupypointercancela nivel del documento, ya que la captura se pierde en ese caso. - De manera opcional, espera aproximadamente 4 px de movimiento antes de ingresar al estado de arrastre, de modo que un clic en el controlador no active el estilo de arrastre.
Con la captura del puntero, los objetos de escucha de movimiento pueden permanecer en el controlador:
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`;
}
Fija cada posición del puntero y del teclado con el mismo ayudante para que se pueda acceder al elemento. Vuelve a fijar su posición actual después de los cambios en el viewport o en el tamaño del elemento. En el caso de los elementos con un diseño pesado, considera traducirlos durante el arrastre para evitar el diseño por fotograma y, luego, confirmar la posición final en left y top cuando se suelte.
Proporciona movimiento con el teclado
Cuando el controlador tenga el enfoque, mueve el elemento con las teclas de flecha y llama a preventDefault() para esas teclas. Usa el mismo ayudante de posicionamiento restringido que el movimiento del puntero. Un incremento de 15 px es un punto de partida razonable; adáptalo al tamaño del componente y a las necesidades de precisión.
Nota específica del diálogo
Un <dialog> o [popover] puede usar este mecanismo con su encabezado como identificador. Establece margin: 0 antes de asignar left y top, ya que los márgenes predeterminados del navegador interfieren con el posicionamiento manual. Para conocer el comportamiento de apertura, cierre, enfoque y descarte de diálogos y ventanas emergentes, consulta Control declarativo de diálogos y ventanas emergentes y Los controles de la plataforma descartan el diálogo.
Estrategias de resguardo
Cuando arrastrar solo cambia la posición de un componente, trátalo como una mejora progresiva: sin JavaScript, mantén el elemento, su contenido y sus controles visibles, legibles y funcionales en una posición predeterminada razonable. No expongas un controlador de arrastre que no funcione: si el controlador es un control de agarre independiente, ocúltalo de forma predeterminada y muéstralo cuando se inicialice JavaScript; si un encabezado funciona como controlador, adjunta sus affordances de arrastre cuando se inicialice JavaScript.
Incluye -webkit-user-select: none antes de user-select: none para que la selección de texto permanezca inhabilitada mientras se arrastra en Safari.