드래그 가능한 요소 빌드

패널, 팔레트, 툴바, 검사기, 대화상자와 같은 기존 플로팅 요소에 드래그를 추가합니다. 드래그는 요소가 표시, 닫기, 스택 또는 포커스되는 방식을 결정하지 않습니다.

전용 핸들 사용

요소에 선택 가능한 텍스트, 버튼, 입력 또는 링크가 포함된 경우 전용 핸들에서 드래그를 시작합니다. 이렇게 하면 일반 콘텐츠를 사용할 수 있습니다. 키보드 이동을 위해 핸들에 포커스를 맞출 수 있도록 하고 화살표 키 상호작용을 언급하는 액세스 가능한 이름을 지정합니다.

핸들에 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에서 드래그 상태를 지웁니다. 드래그 중에 핸들이 삭제되거나 다시 렌더링될 수 있는 경우 문서 수준 pointermove, pointerup, pointercancel 리스너를 대신 사용하세요. 이 경우 캡처가 손실되기 때문입니다.
  • 원하는 경우 드래그 상태로 전환하기 전에 약 4픽셀의 움직임을 기다려 핸들을 클릭해도 드래그 스타일이 트리거되지 않도록 합니다.

포인터 캡처를 사용하면 이동 리스너가 핸들에 유지될 수 있습니다.

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()를 호출합니다. 포인터 이동과 동일한 고정 위치 지정 도우미를 사용합니다. 15px 증분은 적절한 시작점입니다. 구성요소의 크기와 정밀도 요구사항에 맞게 조정하세요.

대화상자별 참고사항

<dialog> 또는 [popover]는 헤더를 핸들로 사용하여 이 메커니즘을 사용할 수 있습니다. left 및 top를 할당하기 전에 margin: 0를 설정합니다. 브라우저의 기본 여백이 수동 위치 지정을 방해하기 때문입니다. 대화상자 및 팝오버 열기, 닫기, 포커스, 닫기 동작은 선언적 대화상자 및 팝오버 컨트롤 및 플랫폼 컨트롤 닫기 대화상자를 참고하세요.

대체 전략

드래그가 구성요소의 위치만 변경하는 경우 점진적 개선으로 처리합니다. JavaScript 없이 요소, 콘텐츠, 컨트롤을 합리적인 기본 위치에서 표시하고, 읽을 수 있고, 작동하도록 유지합니다. 작동하지 않는 드래그 핸들을 노출하지 마세요. 핸들이 독립형 그립 컨트롤인 경우 기본적으로 숨기고 JavaScript가 초기화될 때 표시합니다. 헤더가 핸들 역할을 하는 경우 JavaScript가 초기화될 때 드래그 어포던스를 연결합니다.

Safari에서 드래그하는 동안 텍스트 선택이 계속 표시되지 않도록 user-select: none 앞에 -webkit-user-select: none를 포함합니다.