유지보수 중이던 화면은 항목 순서를 sortable로 변경할 수 있고,
각 항목마다 checkbox 옵션도 함께 있는 구조였습니다.
이 화면은 웹과 앱에서 함께 사용하고 있었는데,
웹 브라우저에서는 별다른 문제가 없었지만 앱에서는 checkbox가 정상적으로 클릭되지 않는 문제가 발생했습니다.
항목 순서를 변경하는 기능 자체는 정상적으로 동작했지만,
같은 영역 안에 있는 체크박스만 유독 클릭이 불안정했던 것입니다.
처음에는 단순한 체크박스 이벤트 문제처럼 보였습니다.
하지만 원인을 확인해보니, 실제 문제는 jQuery UI Sortable이 드래그 시작을 감지하는 방식에 있었습니다.
이번 글에서는 이 문제가 왜 발생했는지, 그리고 가장 깔끔하게 해결한 방법이 무엇이었는지 예제와 함께 정리해보겠습니다.
문제 상황
유지보수 중이던 화면은 대략 아래와 같은 형태였습니다.
- 리스트 항목의 순서를 드래그해서 변경할 수 있어야 하고
- 각 항목에는 checkbox 옵션이 함께 들어 있는 구조였습니다.
예를 들면 아래와 같은 코드입니다.
<ul class="sortable-list">
<li>
<input type="checkbox" id="item1">
<label for="item1">항목 1</label>
</li>
<li>
<input type="checkbox" id="item2">
<label for="item2">항목 2</label>
</li>
<li>
<input type="checkbox" id="item3">
<label for="item3">항목 3</label>
</li>
</ul>
그리고 여기에 jQuery UI Sortable을 아래처럼 적용한 상태였습니다.
$('.sortable-list').sortable();
겉보기에는 특별한 문제가 없어 보이지만,
실제로 화면에서 체크박스를 클릭해보면 체크가 바로 되지 않거나 클릭이 불안정하게 동작하는 현상이 발생했습니다.
즉, 사용자는 체크박스를 클릭하려고 했는데 정작 화면에서는 클릭보다 드래그 동작이 먼저 감지되는 것처럼 보였습니다.
원인
문제의 원인은 jQuery UI Sortable이 정렬 대상 전체 영역에서
mousedown 이벤트를 감지해 드래그 시작 여부를 판단하기 때문입니다.
현재 코드에는 cancel 옵션이 따로 지정되어 있지 않았고, 기본값에 의존하고 있는 상태였습니다.
기본적으로 Sortable의 cancel 옵션은 아래 요소들을 제외 대상으로 처리합니다.
input, textarea, button, select, option
이렇게 보면 checkbox도 input이기 때문에 문제없이 동작해야 할 것처럼 보입니다.
하지만 실제 화면에서는 DOM 구조나 이벤트 전파 방식, 감싸고 있는 태그 구성 등에 따라
기본값만으로는 충분히 안정적으로 동작하지 않는 경우가 있습니다.
결국 Sortable이 .sortable-list 전체에서 드래그 시작을 감지하려고 하면서, 체크박스 클릭까지 간섭하게 된 것입니다.
정리하면 이번 문제는 다음과 같았습니다.
순서 변경을 위해 리스트 전체가 드래그 가능 영역처럼 동작하고 있었고
그 안에 있는 checkbox 클릭까지 드래그 감지가 개입하면서
체크박스 클릭 이벤트가 기대한 대로 전달되지 않았습니다.
왜 cancel 옵션만으로는 아쉬운가
이 문제를 보면 가장 먼저 cancel 옵션을 떠올릴 수 있습니다.
물론 cancel은 특정 요소에서 정렬 동작을 막는 데 도움이 됩니다.
하지만 이 방식은 어디까지나 “예외 대상을 늘리는 방식”에 가깝습니다.
구조가 단순할 때는 괜찮지만, 유지보수 과정에서 화면이 점점 복잡해지면 다음과 같은 문제가 생길 수 있습니다.
- 클릭 가능한 요소가 많아질수록 예외 처리도 계속 늘어남
- 어느 부분에서 드래그가 시작되는지 직관적이지 않음
- 이후 버튼, 링크, 추가 옵션 등이 붙으면 다시 충돌할 가능성이 있음
즉, cancel은 증상을 완화하는 데는 도움이 될 수 있지만, 드래그 시작 지점을 명확하게 설계하는 방식은 아닙니다.
가장 깔끔한 해결 방법
가장 깔끔한 해결 방법은 handle 옵션을 사용하는 것이었습니다.
즉, 드래그를 시작할 수 있는 지점을 특정 요소로 한정하는 방식입니다.
리스트 전체에서 드래그가 시작되도록 두는 것이 아니라,
드래그용 아이콘이나 특정 영역에서만 정렬이 시작되도록 바꾸면 됩니다.
이렇게 하면 역할이 명확하게 분리됩니다.
- 체크박스는 클릭 전용
- 드래그 핸들은 정렬 전용
이 방식이 가장 직관적이고, 유지보수 측면에서도 안정적입니다.
해결 예제
예를 들어 아래처럼 드래그 전용 핸들을 추가합니다.
<ul class="sortable-list">
<li>
<span class="drag-handle">☰</span>
<input type="checkbox" id="item1">
<label for="item1">항목 1</label>
</li>
<li>
<span class="drag-handle">☰</span>
<input type="checkbox" id="item2">
<label for="item2">항목 2</label>
</li>
<li>
<span class="drag-handle">☰</span>
<input type="checkbox" id="item3">
<label for="item3">항목 3</label>
</li>
</ul>
그리고 Sortable을 아래처럼 변경합니다.
$('.sortable-list').sortable({
handle: '.drag-handle'
});
이제는 drag-handle을 잡았을 때만 정렬이 시작되고,
체크박스를 클릭할 때는 드래그 감지가 개입하지 않게 됩니다.
결론
이번 이슈는 화면에서, sortable로 순서를 변경하면서
checkbox 옵션도 함께 사용하는 구조에서 발생했습니다.
특히 같은 화면임에도 웹에서는 문제가 없었지만 앱에서만 checkbox 클릭 문제가 발생했다는 점이 특징이었습니다.
원인을 확인해보니 jQuery UI Sortable이 리스트 전체에서 mousedown을 감지하며 드래그를 시작하려고 했고,
그 과정에서 체크박스 클릭과 충돌이 발생하고 있었습니다.
그리고 cancel 옵션을 기본값에만 의존하는 방식은 이런 환경 차이까지 안정적으로 막아주기에는 부족했습니다.
결국 가장 깔끔한 해결 방법은 다음과 같았습니다.
handle 옵션으로 드래그 시작 지점을 특정 요소로 제한한다.
이 방식은 클릭 요소와 드래그 요소의 역할을 명확하게 분리할 수 있어서,
실행 환경이 달라도 더 안정적으로 동작하도록 만들 수 있습니다.
'Programmer > JAVASCRIPT' 카테고리의 다른 글
| 유지보수 힘든 JSP를 Claude Code로 구조화해본 기록 (0) | 2026.03.18 |
|---|---|
| 회사 개발환경에서 AI 코드 도구를 써보려 했던 이야기 (0) | 2026.03.09 |
| jQuery Ajax async:false 사용 시 렌더링이 멈추는 이유와 해결 방법 (0) | 2026.03.05 |
| 티스토리 ‘내 블로그 수익 예측하기’를 참고해 직접 만들어본 간단 계산기 (0) | 2026.01.23 |
| GPT로 간트차트 라이브러리 만들다가 포기한 이유 (그리고 배운 점) (0) | 2026.01.14 |