Frontend/js

closest() 로 selector에서 가장 가까운 parent Element 선택하기

Yaerhee 2022. 6. 7. 18:00

사용할 method/ function: .closest()

사용 목적: 특정 태그에 active 클래스가 추가될 경우, 해당 태그에 인접한 parent Element 에도 active class를 붙이고자 함


사이드 바를 헤더에 붙여서 디자인을 일관화 하던 작업 중,

하위의 메뉴를 선택해도 상단 메뉴가 색이 변하지 않는 현상을 발견하였다.

수정한 사이드 바 화면. 기존 자바스크립트를 갈아엎어서(...) active 를 붙이는 조건도 사라졌다.

타이틀에 active 클래스를 추가하던 스크립트를 정리하다 보니 발생한 현상이었다.

클릭해도 색상이 변하지 않는 사태 발생... (위의 아이콘이 포함된 타이틀 색상이 변해야 한다)

그런데, 기존에는 event 발생 시 해당 클래스를 직접 selector로 선택했었는데,

지금은 하위 메뉴 중에 active인 (위의 이미지와 같이 선택되어 있어서 색상이 바뀌어있는) 메뉴를 인식해서

위의 타이틀 배경 색상도 바꿔야 하는 상황으로 케이스가 바뀌었다.

 

<li className="nav-list"> // 2. 이 클래스에서 .active 가 붙어야 한다. (...)
	<div className="submenu submenu-title">
		진단 수행/ 결과 조회
 		<div className="sidebar-icon">
			<FontAwesomeIcon icon={faSearch} />
        </div>
    </div>
    <ul className="collapse">
    	<li className="nav-item">
        	<NavLink to="/execute/plan" className="link first-item">
            	신규 진단 계획 실행 // 1. 예를 들어 여기서 클릭을 하게 되면
            </NavLink>
        </li>
     	<li className="nav-item">
          	<NavLink to="/execute/list" className="link no-border-bottom">
               	진단 실행 리스트
            </NavLink>
        </li>
    </ul>
</li>

(주석의 1, 2 번 순서대로 봐 주시면 됩니다.

하위 태그가 직접 맞닿아 있지 않아 조금 껄끄러운 상황입죠...)

 

선택된 클래스(.link.active) 에 따른 타이틀 색상 변경(li.nav-list)이 필요한 케이스라,

.closest() 가 적합하다 판단되어 코드에 적용해 보았다.

 

먼저 하위 메뉴 중 하나가 선택되면 active 클래스가 추가되므로 .active querySelector를 활용하였는데,

로고 링크도 active 상태라, 1의 index인 active 하위 메뉴만 가져오도록 .item(1) 처리를 하였다.

logo 와 active item이 동시애 존재하여, item(1)을 붙여 로고는 걸러냄

// 클릭으로 인해 active class 가 추가된 아이템 검색
const activeClass = document.querySelectorAll('.active').item(1)

 

이후 클릭한 하위메뉴에서 가장 가까운 리스트 태그 & 클래스 이름이 nav-list 인 Element를 .closest() 로 선택하도록 했다.

activeClass.closest('li.nav-list') 로 console.log() 출력한 태그. 원하던 태그가 선택되어 출력된다

의도된 클래스를 select 하게 되었으니, 해당 className 에 active 를 append 하게끔 코드를 작성했다.

React.useEffect(() => {
    // 클릭으로 인해 active class 가 추가된 아이템 검색
    const activeClass = document.querySelectorAll('.active').item(1)
    // active class 에서 제일 가까운 조상 li (nav-list class) 검색
    if (activeClass) {
      const activeNavList = activeClass.closest('li.nav-list')
      activeNavList.className = activeNavList.className + ' active'
    }
  })

cf) if (activeClass) 가 있는 이유는, 프로젝트 내부 메뉴 중 사이드 바에 없는 링크도 존재하기 때문입니다.
(제 프로젝트 상의 개인 설정입니다 ㅎㅎ)

ex - 설정 페이지가 있을 때, 세부 설정 버튼을 눌러 부메뉴의 부메뉴로 들어감

이럴때는 .active 로 title을 감지할 수 없기 때문에 조건문에서 거르도록 설정했습니다.

 

.closest() 적용 후 정상적으로 active 클래스가 추가된 li.nav-list 태그

직접 맞닿아 있는 부모-자식 태그가 아니였지만, .closest()를 활용하여 active 클래스를 추가할 수 있었다.

타이틀이 원하는 색상으로 변경되었다. 


추가

아래와 같이, 작성자가 활용한 내용 외의 컨디션도 여러 가지가 존재하므로

필요에 따라 일반 class / tag / parent 조건을 곁들여서 검색하시는 것도 유용할 것 같습니다.

const el = document.getElementById('div-03');

// ID가 "div-02"인 가장 가까운 조상
console.log(el.closest('#div-02')); // <div id="div-02">

// div 안에 놓인 div인 가장 가까운 조상
console.log(el.closest('div div')); // <div id="div-03">

// div면서 article을 부모로 둔 가장 가까운 조상
console.log(el.closest("article > div")); // <div id="div-01">

// div가 아닌 가장 가까운 조상
console.log(el.closest(":not(div)")); // <article>

출처

https://developer.mozilla.org/ko/docs/web/api/element/closest

 

Element.closest() - Web API | MDN

Element의 closest() 메서드는 주어진 CSS 선택자와 일치하는 요소를 찾을 때까지, 자기 자신을 포함해 위쪽(부모 방향, 문서 루트까지)으로 문서 트리를 순회합니다.

developer.mozilla.org