Notice
Recent Posts
Recent Comments
Link
«   2026/06   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

Developer N

[jQuery] 마우스를 드래그하여 checkbox 다중 선택하기 본문

STUDY/FRONT

[jQuery] 마우스를 드래그하여 checkbox 다중 선택하기

nnh 2024. 8. 22. 14:37
728x90

세로로 이어진 체크박스에 마우스를 드래그했을 경우 체크박스의 상태가 바뀌게 해달라는 수정 요청이 들어왔다.
해당 기능을 추가하면서 생긴 이슈 및 해결방법에 대해 정리해보고자 한다.

 


문제 해결

우선 마우스 드래그를 통해 한번에 이벤트를 발생시키기 위해서는
mousedown, mousemove, mouseup 이벤트가 필요했다.

$('ul.item-list').on('mousedown', 'li', function() {
	let checked = false;

    if($(this).find('input:checkbox').is(':checked'); {
    	checked = false;
    } else {
    	checked = true;
    }
});



제일 먼저 선택하려는 checkbox 요소위에서 마우스를 눌렀을 경우 해당 요소의 check 상태에 따라 바꿔줄 check 상태를 checked 변수에 담아주었다.

그다음 mousedown 이벤트 내에 마우스를 움직일때 발생하는 mousemove 이벤트를 추가한다.

 

$('ul.item-list').on('mousemove', 'li', function() {
    if(checked) {
    	$(this).find('input:checkbox').prop('checked', true);
    } else {
    	$(this).find('input:checkbox').prop('checked', false);
    }
});



마우스를 누른 다음 움직였을 때 checked 변수값에 따라 checkbox의 상태를 변경해준다.

mousedown을 했을 경우 바로 해당 요소의 check 상태를 바꾸지 않은 이유는 마우스를 누른 요소내에서 마우스를 계속 움직여줄 경우 checkbox의 상태가 고정되지않고 바뀌었기 때문이다.
다만, 이렇게 했을 경우 처음 마우스를 누른 요소의 상태에 따라 이후 마우스에 걸리는 요소의 상태값도 결정된다.
(다른 방법이 없는지는 좀더 찾아보아야 함.)

mousemove를 통해 변경하고자 하는 checkbox의 상태를 변경해주었으니, 이제 마우스를 떼야한다.
이때 필요한 이벤트가 mouseup이다.

mousemove 이벤트와 마찬가지로 mousedown 이벤트 함수 내에 mouseup 이벤트를 추가해준다.

 

$('ul.item-list').on('mouseup', function() {
    $('ul.item-list').off('mousemove', 'li');
    $('ul.item-list').off('mouseup');
});



마우스를 뗄 경우 생성한 mousemove 이벤트를 제거해주어야 한다.
그러지 않을 경우 마우스를 떼어내도 mousemove 이벤트가 진행되어 마우스가 checkbox가 있는 li 내에서 움직일 경우 checkbox의 상태가 변하게 된다.
mouseup 이벤트 또한 한번만 실행되면 되기 때문에 실행된 이후 제거해준다.

 

 

 

 

 

이슈

mousedown - mousemove - mouseup 이벤트를 통해 드래그 이벤트를 구현했다.
하지만 막상 실행해보니 문제가 한가지 발생했다.

 

문제발생 1.

드래그해서 체크하려는 checkbox는 ul > li 내에 존재했다.
따라서 마우스 이벤트를 li에 걸어주었는데 문제는 마우스가 눌린 상태에서 li 요소를 벗어났을 경우 발생했다.
마우스가 li 위에 있지 않다보니 mouseup 이벤트가 진행되지 않은 것이다.
따라서 li 요소 밖으로 마우스가 나갔다가 다시 들어왔을 경우 mousemove 이벤트가 계속 진행되었다.

이 문제를 해결하기 위해 mousedown 이벤트 함수 내에 mouseleave 이벤트를 추가해주었다.

$('ul.item-list').on('mouseleave', function() {
    $('ul.item-list').off('mousemove', 'li');
    $('ul.item-list').off('mouseup');
    $('ul.item-list').off('mouseleave');
});



mouseup 때와 똑같이 mousemove, mouseup 이벤트를 제거해주고, 마찬가지로 한번만 실행되면 되는 mouseleave 이벤트도 제거해주었다.

 


문제발생2.

문제1을 해결하고 드래그 기능을 사용해보니 새로운 문제가 발생했다.
mousedown, mousemove, mouseup 이벤트가 진행된 요소가 같을 경우 즉, 한 요소 내에서 마우스를 눌렀다가 움직이고 뗐을 경우 checkbox의 상태가 변했다가 다시 원상태로 되돌아왔다.


이 문제를 이해하기 위해서는 mouse 이벤트가 호출되는 순서를 알아야 한다.
마우스 클릭시 호출되는 이벤트의 순서는 mousedown - mouseup - click 이다.
따라서, 같은 요소 내에서 mousedown과 mouseup 이벤트가 발생하면 mouseup 이벤트 다음으로 click 이벤트가 호출되었다.

이로 인해 mousedown 이후 mousemove를 통해 checkbox의 상태가 false->true로 변했으나,
mouseup 이후 click 이벤트가 발생했고, checkbox의 상태가 true -> false로 다시 원상복귀되고 말았다.

이를 위한 해결방법은 드래그중일 경우 click 이벤트를 막는 것이었다.

우선 드래그 중인 상태를 구분하기 위한 변수에 기본값으로 false를 지정한다.
그리고 기존에 설정된 click 이벤트의 콜백 함수 내에서 드래그 상태를 표시하는 변수값이 false일 경우 return false;를 반환하도록 수정한다.

$('ul.item-list').on('click', 'li input:checkbox', function() {
    if(isDragging) {
    	return false;
    }

    // 이하 기존 클릭 이벤트 로직
});



이렇게 되면 드래그 상태를 나타내는 isDragging 값이 false일 경우에만 기존의 이벤트 로직을 따르게 된다.

이제 드래그를 할 경우 isDragging값을 true로 바꿔주고, 드래그가 완료되었을 때 false로 되돌려주기만 하면 된다.
isDragging 값을 true로 바꾸는 것은 mousemove 이벤트가 실행될때 변경해주면 된다.

문제는 mouseup을 통해 드래그를 끝낸 뒤 드래그 상태값을 false로 초기화해주는 경우다.
mouseup 내에서 드래그 상태값을 false로 변경해줄 경우 이후에 발생하는 click 이벤트가 그대로 진행된다.

따라서 mouseup 이벤트가 진행되고 click 이벤트까지 if(isDragging) 조건에 걸려 return false로 넘어간 다음(여기까지는 드래그 상태값이 true여야함)
드래그 상태값을 false로 변경해주어야 한다.

해결방법은 setTimeout을 이용하는 것이다.

mouseup 이벤트 호출시 실행되는 콜백함수에 아래 함수를 추가해준다.

 

setTimeout(() => {
  isDragging = false;
})

 


위 함수는 mouseup 이벤트가 실행될 때 콜백함수에서 실행되는 함수지만, setTimeout을 통해 비동기처리했기 때문에 별다른 시간을 지정해주지 않아도 즉시 실행되는 것이 아닌 태스크큐에 저장된다.

각각 이벤트 핸들러에 할당되어 있는 실행함수들도 비동기로 처리되는데, 위의 setTimeout으로 실행되는 함수는 테스크큐에 click 이벤트의 실행함수보다 뒷순위로 저장된다.

따라서 우리가 원하는 대로 mousedown - mouseup - click 이후 setTimeout 함수가 실행되어 드래그 상태값이 false로 초기화된다.

 

+) mouseleave 이벤트 호출시 실행되는 콜백함수에도 setTimeout 함수를 추가해주어야 한다.

 

 

 

 

 

전체 소스

let isDragging = false; // 드래그 상태값

// mousedown 이벤트
$('ul.item-list').on('mousedown', 'li', function() {
	let checked = false;

        if($(this).find('input:checkbox').is(':checked'); {
            checked = false;
        } else {
            checked = true;
        }
	
	// mousemove 이벤트
	$('ul.item-list').on('mousemove', 'li', function() {
		isDragging = true; // 드래그 시작
		if(checked) {
			$(this).find('input:checkbox').prop('checked', true);
		} else {
			$(this).find('input:checkbox').prop('checked', false);
		}
	});
	
	// mouseup 이벤트
	$('ul.item-list').on('mouseup', function() {
		setTimeout(() => {
		  isDragging = false; // 드래그 상태값 초기화
		})
		$('ul.item-list').off('mousemove', 'li');
		$('ul.item-list').off('mouseup');
	});
	
	// mouseleave 이벤트
	$('ul.item-list').on('mouseleave', function() {
    		setTimeout(() => {
		  isDragging = false; // 드래그 상태값 초기화
		})
		$('ul.item-list').off('mousemove', 'li');
		$('ul.item-list').off('mouseup');
		$('ul.item-list').off('mouseleave');
	});
});

// 클릭 이벤트
$('ul.item-list').on('click', 'li input:checkbox', function() {
    if(isDragging) {
		// 드래그중일 경우 클릭 이벤트 비활성
    	return false;
    }

    // 이하 기존 클릭 이벤트 로직
});

 

 

 

 


결론

이번 요청사항을 해결하면서 알게된 사실을 간단하게 정리해보자면 아래와 같다.

1. 마우스 클릭 이벤트의 호출 순서

2. 드래그 이벤트 구현 방법

3. setTimeout을 활용하여 이벤트의 호출 순서 제어

 

기능 구현 중 두번째 발생한 문제를 해결하기 위해 꽤 오래 시간을 할애했는데, 이로 인해 setTimeout의 다른 활용법과 마우스 이벤트에 대해 구체적으로 알수 있어서 유의미한 시간이었다.

 

 

 

 


추가 요청사항

 

mousemove 이벤트에서 체크박스가 체크될 때 특정 함수를 실행해야하는 케이스가 있었다.
이 함수는 해당 체크박스 내에서 계속 마우스가 움직여도 한번만 실행이 되어야 했다.
따라서 처음 체크박스가 체크될 때 함수를 실행하고 해당 체크박스의 정보를 기억해 다음 같은 요소 내에서 마우스가 움직였을 때는 함수가 진행되지 않도록 조건절을 추가해주었다.

 

기존 소스를 조금만 변경하여 해당 기능을 구현했다.

소스는 아래와 같다.

 

//mousemove 이벤트 핸들러 밖에 함수를 실행한 요소를 저장할 변수 생성
let nowItem = null;

$('ul.item-list').on('mousemove', 'li', function() {
	isDragging = true;
	
	/* nowItem이 null이 아니고(이미 드래그중인 상황)
	마우스가 움직이고 있는 요소의 id와 nowItem값이 같을 경우(nowItem에 id값을 저장하여 비교)
	mousemove 이벤트 종료(체크박스 상태변경은 물론 함수호출 안함) */
	if(nowItem != null && nowItem == $(this).find('input:checkbox').prop('id')) {
		return false;
	}
	
	if(checked && !(this).find('input:checkbox').prop('checked')) {
		/* 체크박스 상태를 체크로 변경해야하고 현재 요소의 체크박스가 체크해제 상태라면
        체크로 변경 및 함수 호출, nowItem에 체크박스 id값 할당 */
		$(this).find('input:checkbox').prop('checked', true);
		nowItem = $(this).find('input:checkbox').prop('id');
		
		getItemData(); // 체크할 경우 원하는 함수 호출
	} else if(!checked && (this).find('input:checkbox').prop('checked')) {
		/* 체크박스 상태를 체크해제로 변경해야하고 현재 요소의 체크박스가 체크상태라면 
        체크해제로 변경 및 함수 호출, nowItem에 체크박스 id값 할당*/
		$(this).find('input:checkbox').prop('checked', false);
		nowItem = $(this).find('input:checkbox').prop('id');
		
		removeItemData(); // 체크 해제할 경우 원하는 함수 호출
	}
});

 

 

이 소스를 포함한 전체 소스는 아래와 같다.

 

let isDragging = false; // 드래그 상태값

// mousedown 이벤트
$('ul.item-list').on('mousedown', 'li', function() {
	let checked = false;
    	let nowItem = null;

        if($(this).find('input:checkbox').is(':checked'); {
            checked = false;
        } else {
            checked = true;
        }
	
	// mousemove 이벤트
	$('ul.item-list').on('mousemove', 'li', function() {
            isDragging = true;

            if(nowItem != null && nowItem == $(this).find('input:checkbox').prop('id')) {
                return false;
            }

            if(checked && !(this).find('input:checkbox').prop('checked')) {
                $(this).find('input:checkbox').prop('checked', true);
                nowItem = $(this).find('input:checkbox').prop('id');

                getItemData();
            } else if(!checked && (this).find('input:checkbox').prop('checked')) {
                $(this).find('input:checkbox').prop('checked', false);
                nowItem = $(this).find('input:checkbox').prop('id');

                removeItemData();
            }
    	});
	
	// mouseup 이벤트
	$('ul.item-list').on('mouseup', function() {
		setTimeout(() => {
		  isDragging = false; // 드래그 상태값 초기화
		})
		$('ul.item-list').off('mousemove', 'li');
		$('ul.item-list').off('mouseup');
	});
	
	// mouseleave 이벤트
	$('ul.item-list').on('mouseleave', function() {
    		setTimeout(() => {
		  isDragging = false; // 드래그 상태값 초기화
		})
		$('ul.item-list').off('mousemove', 'li');
		$('ul.item-list').off('mouseup');
		$('ul.item-list').off('mouseleave');
	});
});

// 클릭 이벤트
$('ul.item-list').on('click', 'li input:checkbox', function() {
    if(isDragging) {
    	return false;
    }

    // 이하 기존 클릭 이벤트 로직
});

 

 

 

 


 

참고

https://stickode.tistory.com/754

 

[JavaScript] 마우스 드래그로 일괄 선택하기

안녕하세요. 이번 시간에는 마우스 드래그를 통해 한번에 이벤트를 발생시키는 방법에 대해 알아보겠습니다. 자바스크립트에는 마우스와 관련된 다양한 이벤트를 제공하는데, 아래의 예제에서

stickode.tistory.com

https://sangmin802.github.io/Study/Think/mouse%20and%20click%20issue/

 

🖱 동일 요소의 클릭이벤트, 마우스이벤트 이슈

sangmin802.github.io

 

728x90
Comments