Adicione a ação de arrastar a um elemento flutuante, como um painel, uma paleta, uma barra de ferramentas, um inspetor ou uma caixa de diálogo. Arrastar não determina como o elemento é exibido, dispensado, empilhado ou focado.
Usar um identificador dedicado
Comece a arrastar de uma alça dedicada quando o elemento tiver texto, botões, entradas ou links selecionáveis. Isso mantém o conteúdo normal utilizável. Torne o identificador focalizável para o movimento do teclado e dê a ele um nome acessível que mencione a interação com as teclas de seta.
Não aplique role="application" ao identificador. Isso muda desnecessariamente a interação com a tecnologia adaptativa.
Aplique cursor: grab/grabbing, touch-action: none e user-select: none e -webkit-user-select: none à alça, não ao elemento arrastável inteiro, para que o texto fora dela permaneça selecionável e os gestos de toque mantenham o comportamento normal:
.drag-handle {
cursor: grab;
touch-action: none;
-webkit-user-select: none;
user-select: none;
}
.drag-handle.dragging {
cursor: grabbing;
}
Preservar a posição e o alcance do ponteiro
Use um modelo de posicionamento relativo à janela de visualização, como position: fixed, ao fixar o movimento na janela de visualização.
- Inicie uma ação de arrastar apenas para o botão do ponteiro principal (
event.button === 0). - Registre o deslocamento do ponteiro em
pointerdowne use-o em cada movimento para que o elemento não pule quando for arrastado para longe do canto. - Mantenha o arrasto ativo depois que o ponteiro sair da alça. Defina como padrão
setPointerCapture()e limpe o estado de arrastar emlostpointercapture. Se o identificador puder ser removido ou renderizado novamente durante o arrasto, use os listenerspointermove,pointerupepointercancelno nível do documento, já que a captura é perdida nesse caso. - Se quiser, aguarde cerca de 4 px de movimento antes de entrar no estado de arrastar, para que um clique na alça não acione o estilo de arrastar.
Com a captura de ponteiro, os listeners de movimento podem permanecer no identificador:
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`;
}
Fixe todas as posições do ponteiro e do teclado usando o mesmo auxiliar para que o elemento permaneça acessível. Fixar novamente a posição atual após mudanças na janela de visualização ou no tamanho do elemento. Para elementos com muitos layouts, considere fazer a translação durante o arrasto para evitar o layout por frame e, em seguida, confirme a posição final em left e top na liberação.
Oferecer movimento pelo teclado
Quando a alça estiver em foco, mova o elemento com as teclas de seta e chame preventDefault() para essas teclas. Use o mesmo assistente de posicionamento fixo que o movimento do ponteiro. Um incremento de 15px é um ponto de partida razoável. Adapte-o ao tamanho e às necessidades de precisão do componente.
Observação específica da caixa de diálogo
Um <dialog> ou [popover] pode usar esse mecanismo com o cabeçalho como identificador. Defina margin: 0 antes de atribuir left e top, porque as margens padrão do navegador interferem no posicionamento manual. Para saber mais sobre o comportamento de abertura, fechamento, foco e dispensa de caixas de diálogo e pop-overs, consulte Controle declarativo de caixas de diálogo e pop-overs e Caixa de diálogo de dispensa de controles da plataforma.
Estratégias de fallback
Quando o arrasto apenas reposiciona um componente, trate-o como aprimoramento progressivo: sem JavaScript, mantenha o elemento, o conteúdo e os controles visíveis, legíveis e funcionais em uma posição padrão sensata. Não exponha uma alça de arrastar não funcional: se a alça for um controle de fixação independente, oculte-a por padrão e revele-a quando o JavaScript for inicializado. Se um cabeçalho funcionar como a alça, anexe as funcionalidades de arrastar quando o JavaScript for inicializado.
Inclua -webkit-user-select: none antes de user-select: none para que a seleção de texto permaneça suprimida durante o arrasto no Safari.