문제



위와 같은 디자인을 구현하던 중 문제가 발생했습니다.
일단 검색창에 focus
이벤트가 발생하면 옵션창이 뜨고, blur
이벤트가 발생하면 다시 사라지도록 하였습니다.
또한 옵션에 click
이벤트가 발생하면 선택된 옵션을 데이터 리스트에 추가하도록 하였습니다.
하지만 생각과는 달리 옵션의 onClick
에 등록한 이벤트 핸들러가 아예 실행되지 않는 걸 알 수 있었습니다.
해결 방법
onClick
대신onMouseDown
에 이벤트 핸들러를 등록한다.onMouseDown={(e) => { e.preventDefault() }}
를 추가하고,onClick
은 그대로 둔다.
여러 블로그를 통해 해결 방법을 찾을 수 있었지만, 명확한 설명은 찾을 수 없었습니다.
왜 mousedown
이벤트를 처리해주어야 할까요?
설명
- 클릭 관련 이벤트 순서
- focus
와 blur
이벤트
- 기본 동작과 Event.preventDefault()
해결 방법을 제대로 이해하기 위의 내용을 차례대로 알아보겠습니다.
클릭 관련 이벤트 순서
마우스 클릭 이벤트는 mousedown -> mouseup -> click
순으로 발생합니다.
mousedown
은 마우스를 누른 순간, mouseup
은 마우스를 뗀 순간, click
은 mouseup
다음에 발생합니다.
그렇기 때문에 마우스를 꾹 누를 때, mousedown
이벤트와 mouseup
, click
이벤트 사이에 추가적인 시간이 존재하게 됩니다.
예시 코드를 통해 살펴보겠습니다.
"use client";
import styled from "styled-components";
export default function Test() {
return (
<Box
onMouseDown={() => {
console.log("onMouseDown!");
}}
onMouseUp={() => {
console.log("onMouseUp!");
}}
onClick={() => {
console.log("onClick!");
}}
/>
);
}
const Box = styled.div`
width: 100px;
height: 50px;
background-color: black;
`;

실제로 mousedown -> mouseup -> click
순으로 출력이 되는 걸 확인할 수 있습니다.
focus, blur 이벤트
focus
이벤트는 요소가 포커스를 받을 때,
blur
이벤트는 포커스를 잃을 때 발생합니다.
다시 말해, 엘리먼트를 클릭하거나 tab 키를 통해 엘리먼트로 이동하면 해당 엘리먼트에 focus
이벤트가 발생합니다.
포커스 되어있던 엘리먼트가 포커스를 잃는 순간 해당 엘리먼트에 blur
이벤트가 발생합니다.
기본 동작과 Event.preventDefault()
<a>
의 click
이벤트, <submit>
의 click
이벤트, <checkbox>
의 click
이벤트 등은 특수한 기본 동작을 가지고 있습니다.
특정 링크로 이동하던지 값을 전송하고 새로고침 하던지, 체크박스를 on, off 하는 동작 등 입니다.
Event.preventDefault()
는 이러한 기본 동작을 실행하지 않도록 지정합니다.
click
이벤트는 태그마다 기본 동작이 다르지만
mousedown
이벤트의 기본 동작은 해당 엘리먼트로 포커스하는 동작을 포함하고 있습니다.
(target is a focusable area that is click focusable, https://w3c.github.io/uievents/#handle-native-mouse-down-id)
예시를 통해 살펴보겠습니다.
"use client";
import styled from "styled-components";
export default function Home() {
return (
<StyledWrawpper>
<Input />
<Input
onMouseDown={(e) => {
e.preventDefault();
}}
onClick={() => {
console.log("클릭되었지만 포커스는 옮겨지지 않음!!");
}}
/>
<div
onMouseDown={(e) => {
e.preventDefault();
}}
>
hello
</div>
<a
href="https://123.com"
onMouseDown={(e) => {
e.preventDefault();
}}
onClick={(e) => {
e.preventDefault();
}}
>
hello
</a>
<input
type="checkbox"
onMouseDown={(e) => {
e.preventDefault();
}}
/>
</StyledWrawpper>
);
}
const StyledWrawpper = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
`;
const Input = styled.input`
&:focus {
border: 1px solid red;
outline: none;
}
border: 1px solid black;
border-radius: 4px;
`;
포커스를 받으면 빨개지는 input
, div
, a
, input checkbox
있습니다.
맨 위의 input
을 제외한 나머지 요소들의 mousedown
이벤트를 preventDefault()
처리해줍니다.
포커스가 첫 번째 인풋에서 움직이지 않는 모습을 볼 수 있습니다.
onMouseDown
을 제거하면 차이를 확인할 수 있습니다 (<a>
의 클릭은 막아두었습니다)
정리
1. 마우스 클릭 이벤트는 mousedown -> mouseup -> click
순으로 진행된다.
2. mousedown
이벤트가 발생하면서 focus
가 이동하고, blur
이벤트가 발생한다.
3. onBlur
에 등록된 이벤트 핸들러가 실행되며 옵션창이 닫힌다.
4. 옵션의 onClick
에 등록된 이벤트 핸들러가 실행이 안 된다.
해결 방법
onClick
대신onMouseDown
에 이벤트 핸들러를 등록한다.onMouseDown={(e) => { e.preventDefault() }}
를 추가하고,onClick
은 그대로 둔다.
1번은 onMouseDown
에 이벤트 핸들러를 추가함으로써, 포커스가 바뀌어 blur
이벤트가 발생하면서도 원하는 작업이 가능합니다. 제 경우엔 옵션창이 사라지지만 데이터들은 올바르게 리스트에 들어가게 됩니다.
2번은 mousedown
이벤트를 막음으로써 옵션으로 포커스가 이동되지 않으므로 blur
이벤트가 발생하지 않고 옵션창이 사라지지 않습니다. 이후 onClick
에 등록한 이벤트 핸들러에 의해 데이터가 리스트에 들어가고, 결과적으로 옵션창도 그대로 남아있게 됩니다.
!! 따라서 blur
와 click
이벤트를 동시에 다룰 땐 포커스와 마우스 클릭 이벤트 간의 순서를 잘 생각해 만드는 것이 중요할 것 같습니다.
https://w3c.github.io/uievents/#events-mouseevent-event-order
https://w3c.github.io/uievents/#handle-native-mouse-down-id