일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- git
- 깃
- jQuery
- 웹디자인기능사실기
- JavaScript
- 정보처리기사
- 생활코딩
- 타입스크립트
- 슬라이드전환
- HTML
- 비전공자
- web
- github
- 프론트엔드
- CSS
- PROJECT
- 코드공유
- JS
- 코딩독학
- 렛츠기릿자바스크립트
- 연산자
- 실기
- 웹디실기
- 세로메뉴바
- 웹퍼블리셔
- Supabase
- react
- 리액트
- 자바스크립트
- 웹디자인기능사
- Today
- Total
코딩하는라민
[JavaScript/React] 영역 외 클릭 감지로 모달/프로필 박스 닫기 기능 구현 본문
[JavaScript/React] 영역 외 클릭 감지로 모달/프로필 박스 닫기 기능 구현
웹 페이지를 구현하다보면 모달창이나 프로필 박스 혹은 사이드바를 구현해야 하는 경우가 많다.
모달창의 예를 들어보면,
모달창의 바깥 영역을 클릭했을 때 창을 닫을 수 있으면 사용자 경험을 향상시킬 수 있다.
이번 프로젝트의 경우에는 프로필 박스가 페이지가 전환됨에도 닫아지지 않고, 외부 영역을 클릭해도 상태 변화가 없어 닫아지지 않았다.
그래서 찾아본 기능이 영역 외 클릭 감지 기능이다.
👀 기본 레이아웃
HTML
<div id="wrapper">
<div id="userName">ramincode</div>
<div id="profile-box">hello world!</div>
</div>
CSS
#wrapper {
width: 200px;
height: auto;
margin: 0 auto;
position: relative;
}
#username {
font-size: 25px;
font-weight: 600;
text-align: center;
padding: 8px 20px;
border-radius: 8px;
}
#profile-box {
display: none;
position: absolute;
left: 50px;
border: 1px solid #d9d9d9;
border-radius: 8px;
width: fit-content;
padding: 8px 20px;
font-size: 17px;
margin-top: 10px;
background-color: azure;
}
📌 토글 박스
Javascript
영역 외 클릭 감지를 위해서 document 에 클릭 이벤트를 준다.
모달이나 드롭다운 같은 요소가 오픈되어 있을 때 외부 영역을 클릭 시 닫는 패턴으로 사용한다.
DOM 요소 가져오기
profile-box, username 을 getElementById 로 가져온다.
const profileBox = document.getElementById('profile-box');
const usernameElement = document.getElementById('username');
상태 변수 선언
프로필 박스의 open or close 상태를 나타내는 변수를 선언해준다.
초기값은 false 이다.
let isProfileOpen = false;
토글 함수 실행 로직
프로필 박스가 열리면 isProfileOpen 은 true 가 될 것이고, 닫히면 false 이다.
프로필 박스가 열려있으면 닫기 위해 closeProfile 함수를 실행하고,
프로필 박스가 닫혀있으면 열기 위해 openProfile 함수를 실행한다.
function toggleProfile(event) {
event.stopPropagation();
isProfileOpen ? closeProfile() : openProfile();
}
isProfileOpen 이 false 일 경우 프로필 박스를 열기 위해 openProfile 함수를 실행한다.
isProfileOpen 를 true 로 변경하고 profileBox 를 보이게 display: block 속성을 준다.
function openProfile() {
isProfileOpen = true;
profileBox.style.display = "block";
}
isProfileOpen 이 true 일 경우 프로필 박스를 닫기 위해 closeProfile 함수를 실행한다
function closeProfile() {
isProfileOpen = false;
profileBox.style.display = "none";
}
usernameElement 에 클릭 이벤트를 할당해준다.
usernameElement.addEventListener('click', toggleProfile);
👀 여기까지 전체 코드
const profileBox = document.getElementById('profile-box');
const usernameElement = document.getElementById('username');
let isProfileOpen = false;
function toggleProfile(event) {
event.stopPropagation();
isProfileOpen ? closeProfile() : openProfile();
}
function openProfile() {
isProfileOpen = true;
profileBox.style.display = 'block';
}
function closeProfile() {
isProfileOpen = false;
profileBox.style.display = 'none';
}
usernameElement.addEventListener('click', toggleProfile);
📌 영역 외 클릭 감지
바깥 영역을 클릭했을 때, 프로필이 오픈되어 있고, 프로필 박스 바깥에 타겟이 있는 경우 closeProfile 함수를 실행시킨다.
이 로직의 함수를 document 의 클릭 이벤트 리스너에 할당해준다.
function handleClickOutside(event) {
if (isProfileOpen && !profileBox.contains(event.target)) {
closeProfile();
}
}
document.addEventListener('click', handleClickOutside);
contains() 메서드
contains() 메서드는 DOM 요소 내에 다른 요소가 포함되어 있는지 여부를 확인하는 메서드이다.
이 메서드는 부모 요소에 대해 호출되며, 전달된 매개변수가 부모 요소의 자식인지 여부를 검사한다.
const parentElement = document.getElementById('parent');
const childElement = document.getElementById('child');
const isChildInParent = parentElement.contains(childElement);
console.log(isChildInParent); // true
📌 전체 코드
const profileBox = document.getElementById('profile-box');
const usernameElement = document.getElementById('username');
let isProfileOpen = false;
function toggleProfile(event) {
event.stopPropagation();
isProfileOpen ? closeProfile() : openProfile();
}
function openProfile() {
isProfileOpen = true;
profileBox.style.display = 'block';
}
function closeProfile() {
isProfileOpen = false;
profileBox.style.display = 'none';
}
usernameElement.addEventListener('click', toggleProfile);
function handleClickOutside(event) {
if (isProfileOpen && !profileBox.contains(event.target)) {
closeProfile();
}
}
document.addEventListener('click', handleClickOutside);
📌 React 로 영역 외 클릭 시 감지 기능 구현하기
프로필 박스 useState 정의하기
const [isProfileOpen, setIsProfileOpen] = useState(false);
useRef
useRef 는 값을 저장하는 역할을 한다.
프로필 박스 DOM 노드에 대한 참조를 저장한다.
useRef 를 이용하면 클릭된 대상이 프로필 박스인지 아닌지를 판단할 수 있게 되는 것이다.
Javascript
const profileRef = useRef(null)
Typescript
const profileRef = useRef<HTMLDivElement>(null)
이벤트 리스너 설정
마우스를 클릭했을 때 호출 가능한 함수를 정의해준다.
클릭된 대상이 프로필 박스 외부라면 프로필 박스를 닫는다.
Javascript
function handleClickOutside(e) {
const currentProfileRef = profileRef.current
if (currentProfileRef && !currentProfileRef.contains(e.target)) {
setIsProfileOpen(false)
}
}
Typescript
function handleClickOutside(e: MouseEvent): void {
const currentProfileRef = profileRef.current
if (currentProfileRef && !currentProfileRef.contains(e.target as Node)) {
setIsProfileOpen(false)
}
}
useEffect 사용
useEffect 를 이용해 profileRef 의 current 값이 변경될 때마다 해당 로직을 실행하도록 구성해준다.
profileRef.current를 의존성 배열에 넣어 프로필 박스 DOM 노드가 변경될 때만 이벤트 리스너를 재설정하게 한다.
useEffect(() => {
document.addEventListener('click', handleClickOutside)
return () => {
document.removeEventListener("click", handleClickOutside)
}
}, [profileRef.current])
removeEventListener를 사용하여 이전에 추가한 이벤트 리스너를 제거해준다.
클린업 함수로 mousedown 이벤트 리스너를 제거하여 불필요한 메모리 누수와 이벤트 리스너의 중첩을 방지하는 것.
참고 : chat gpt + me
'Core > JavaScript' 카테고리의 다른 글
[Javascript] confirm 후 clipboard api 사용 시 DOMException Error 발생하는 문제점 (18) | 2023.12.19 |
---|---|
[JavaScript] 이벤트와 이벤트 핸들러, 이벤트 객체 event(e) (53) | 2023.12.07 |
[JavaScript/React] 현재 URL 복사하기 & window.location 객체 (42) | 2023.12.04 |
[JavaScript] 클래스와 접근자 프로퍼티(getter함수, setter함수) (0) | 2023.02.21 |
[JavaScript] 클래스(Class)와 constructor (0) | 2023.02.20 |