[UI component] accordion

·2025년 4월 3일
0

[Study] Toy project

목록 보기
2/4

두번째 스터디 과제는 아코디언! ! ! 레 쓰 고


1. 아코디언(Accordion)의 정의 및 특징

  • 세로로 쌓여있는 아이템 리스트
  • 레이블(아이콘) 클릭 시 내용 펼침/숨김 기능



2. 아코디언의 유형

2-1. 메뉴 아코디언

  • 2depth 이상의 메뉴/카테고리를 보여줄 때

2-2. 콘텐츠 아코디언

  • 내용이 긴 상세설명을 숨김/펼침으로 보여줄 때

2-3. 컨테이너 아코디언

  • 컨테이너 안의 특정 요소들을 숨겼다가 조건에 부합할 경우에만 보여줄 때, 컨테이너 박스의 크기 자체가 작아졌다가(숨김) 커지는 형태(펼침)로 보임



3. 아코디언의 구성 요소

3-1. 제목/메뉴이름

  • 숨김 상태에서도 항상 보여줄 제목이나 메뉴 이름

3-2. 아이콘(레이블)

  • 패널이 열려있는지 여부를 아이콘으로 표시

3-3. 패널

  • 제목(메뉴이름)과 관련된 상세 내용 영역



4. 아코디언 사용 규칙

4-1. 아이콘 배치는 일관성 있게

  • 제목 앞에 아이콘 배치 시 트리 메뉴로 인식할 수 있으므로 우측 끝에 아이콘 배치를 권장

4-2. 스크롤 제공 가능

  • 패널이 펼쳐진 경우 세로 영역이 제한될 시 스크롤 발생 가능

4-3. 아코디언 아이콘 선택 시 콘텐츠 영역이 아래로 확장

  • 컨테이너 아코디언 제공 시 컨테이너 확장은 아래쪽으로



5. 배치 가능 영역

  • 메뉴, 콘텐츠, 퀵 메뉴 영역에 배치 가능



6. 아코디언 스타일 가이드

6-1. 크기

  • 정보량과 환경에 따라 높이 40/56 2가지로 구분











번외 : 작업하면서 추가로 알게 된 것들


1. 메뉴 아코디언과 트리 메뉴의 차이

내용을 작성하며 참고한 lithium design에서는 트리 메뉴와의 혼동을 방지하기 위해 아코디언에서 아이콘을 우측에 사용한다고 하기에 트리 메뉴가 뭐길래 혼동할 수 있다는 거지? 하는 궁금증이 생겨 알아보았다.
챗GPT의 답변에 의하면 트리 메뉴와 아코디언 메뉴의 차이점은 다음과 같다고 했다.

🎯 차이점 정리

트리 메뉴아코디언 메뉴
계층 구조부모-자식 관계 (트리 구조)독립적인 패널 (평면 구조)
동작 방식여러 개의 항목을 동시에 펼칠 수 있음보통 하나의 패널만 열림
아이콘 위치제목 앞 (📁 Products)제목 오른쪽 끝 (🔽 Products)
사용 사례파일 탐색기, 카테고리 메뉴FAQ, 모바일 메뉴

그런데 lithium design에서 아코디언 메뉴 예시로 제시한 사진이 트리 메뉴와 굉장히 유사하게 생겨서 이 설명을 보고도 아이콘의 위치를 제외하고 대체 뭐가 다른 건지 잘 와닿지 않았다.
lithium design의 아코디언 메뉴 예시

이 경우 트리 메뉴와 비슷한 형태로 구성되었으나 그럼에도 아래와 같은 차이점이 분명히 있다고 했다.

1. 1단계 그룹만 독립적으로 열리고 닫힘

  • 하위 메뉴에서는 추가 하위 계층을 여닫을 수 있는 기능이 없음
  • 트리 메뉴라면 하위 메뉴도 하위 항목을 가질 수 있음
  • 예시 사진의 아코디언 메뉴에서는 하위 메뉴가 단순 리스트처럼 배치되어 아코디언에 더 가까움

2. 동시에 여러 그룹이 열릴 수 있는가?

  • 아코디언 메뉴는 일반적으로 하나의 그룹만 열리고 나머지는 자동으로 닫힘
  • 트리 메뉴는 여러 그룹을 동시에 열어둘 수 있음

3. 메뉴 아이콘의 위치

  • 우측 끝에 배치된 아이콘 -> 보편적인 아코디언 스타일
  • 트리 메뉴의 경우 제목 앞쪽(왼쪽 끝)에 배치하는 경우가 많음

결론적으로 예시 사진은 트리 구조처럼 보일 수 있으나 하위 메뉴가 더 확장되지 않기에 아코디언 메뉴라는 얘기였다. 한끗차이같은데 굉장히 미묘하구만..!




2. forEach()와 map() 비교

1-1. 공통점

  • 두 메서드 모두 배열의 요소를 순회

1-2. 차이점

  • 목적과 반환값이 다름
  • forEach()
    • 반환값이 없음
    • 반환값이 없으므로 출력 시 값은 undefined
    • 주로 콘솔 출력, DOM조작, API호출 등에 사용됨
  • map()
    • 각 요소를 변환해 새로운 배열을 반환
    • 배열 데이터를 변형해 새로운 배열이 필요할 때 사용
    • 원본 배열을 변경하지 않고 새로운 배열을 생성함

📌 forEach() vs map() 비교 정리

기능forEach()map()
반환값undefined (아무것도 반환 안 함)새로운 배열 반환
원본 배열변경되지 않음 (O)변경되지 않음 (O)
주 사용 목적반복하며 부수 효과 수행 (출력, DOM 조작 등)배열 변환 및 새로운 배열 생성
사용 예시console.log(), DOM 조작, API 호출배열.map(변환 함수)

🚀 정리:

✔️ 배열을 변형해서 새로운 배열이 필요하면map()
✔️ 배열을 그냥 순회하면서 무언가 실행할 때는forEach()




3. onclick VS addEventListner

onclick이나 addEventListner나 결과적으로는 같은 기능을 하지 않나? 뭐가 다르지? 라는 의문이 들어 알아봤다.
결론은 생각한 것처럼 비슷한 기능을 하지만 중요한 차이점은 addEventListner를 사용해야 이벤트를 여러 개 적용할 수 있다는 점!!
onclick 이벤트 핸들러 사용 시 새로운 onClick 이벤트를 사용한다면 기존 이벤트를 덮어쓰고 새로운 이벤트가 적용되는 반면, addEventListner를 사용할 경우 여러 이벤트를 추가해도 누적되어 모든 이벤트가 동작한다고 한다!

여러개의 addEventListner 사용 예시
https://mondaymonday2.tistory.com/233

이외의 차이점은
브라우저 호환성

  • onclick : 모든 브라우저에서 호환
  • addEventListner : IE 6,7,8 버전에서는 호환 X, 오래된 브라우저 지원 시 onclick 사용 권장

버블링/캡쳐링

  • addEventListner의 경우 이벤트 발생 시 버블링으로/캡쳐링으로 작동될지 지정할 수 있다. 세번째 파라미터가 true면 캡쳐링, false면 버블링 사용

  • 이러한 옵션 지정으로 더 세밀한 제어가 가능

    clickEvent.addEventListner('click', 이벤트 리스너, 버블링/캡처링)

    이라고 하는데..버블링이랑 캡처링은 또 뭐지...!^.ㅠ

onclick vs addEventListner 참고문서
https://beforb.tistory.com/37




4. 버블링 / 캡처링

그래서 또 알아봤습니다..버블링과 캡처링..니..먼데

4-1. 버블링

  • 자식 요소의 이벤트 발생 시 가장 최상단의 조상으로까지 차례대로 이벤트가 발생

4-2. 캡처링

  • 버블링과 반대로 이벤트 발생 시 해당 요소의 조상으로부터 이벤트 발생 요소(자식/자손) 순으로 이벤트가 발생
    default는 버블링이고, focus와 같은 몇몇 이벤트를 제외하면 대부분 버블링됨.

이걸 보고 나서야 아, 그래서 여태 e.preventDefault()를 썼던 거구나..! 라고 이제야 이해하게 되었습니다..^^ 오늘도 하나 배워간다!! 더 자세히 적기에는 본래 주제에서 많이 벗어날 듯 해 참고한 자료만 첨부하기로 하겠다.
e.preventDefault() 외에도 이벤트 전파를 방지하는 다른 메소드에 대해 설명되어 있는데, 이것도 굉장히 새롭고 흥미로웠다!!

참고문서
1. https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-%EB%B2%84%EB%B8%94%EB%A7%81-%EC%BA%A1%EC%B3%90%EB%A7%81#%EB%B2%84%EB%B8%94%EB%A7%81__%EC%BA%A1%EC%B2%98%EB%A7%81_%EC%9B%90%EB%A6%AC
2. https://ko.javascript.info/bubbling-and-capturing


++추가

  • 코드를 작성하면서 이벤트 발생에 관해 또 의문이 계속 생겨서 문제 해결에 가장 도움이 되었던 질문과 그에 대한 챗GPT의 답변을 정리했다.

📌 질문에 대한 답변

질문답변설명
closest()는 이벤트 발생 지점(자신) 포함 부모 방향으로만 검색하는 게 맞아?✅ O형제나 자식 요소는 검색하지 않고, 부모 요소 중에서만 가장 가까운 요소를 찾는다.
검색할 요소는 태그명, 클래스명, 아이디 모두 가능해?✅ Oclosest("a"), closest(".menu-item"), closest("#myId") 모두 사용 가능하다.
n번째 메뉴 글씨+아이콘을 클릭하면 해당 영역이 <a> 태그인데, <li> 영역과도 겹치지 않아?✅ O<a> 태그는 <li> 내부에 있지만, 브라우저가 <a>를 위쪽 레이어로 렌더링하기 때문에 시각적으로 겹쳐 보일 수 있다.
그런데 왜 클릭 이벤트 발생 위치는 <a> 태그가 되는 거야?✅ O클릭 이벤트는 가장 깊은(안쪽) 요소부터 시작하므로, <a> 내부의 <span>을 클릭하면 event.target<span>이 되고, 텍스트 부분을 클릭하면 event.target<a>가 된다.
<li>보다 <a>가 나중에 렌더링되었기 때문인가?❌ X브라우저의 렌더링 순서는 관련 없음. 이벤트 발생 시 항상 가장 깊은 요소부터 감지되기 때문.
부모와 자식 영역이 겹치는 경우, 이벤트 발생 시 event.target은 무조건 자식 요소가 되는 거야?✅ O이벤트는 항상 가장 안쪽 요소부터 시작되므로 부모보다 자식 요소가 event.target이 된다.

이 중 내가 잘못 이해했던 X 항목에 대한 추가 설명

  • 이벤트 전파 방식이 버블링이든 캡처링이든 부모,자식 요소가 겹치는 영역에서 이벤트가 발생할 경우 이벤트 타겟은 무조건 가장 깊은 곳에 위치한 요소(가장 안쪽의 자식요소)이다.




5. closest()

이것두 처음본다.. 사용법은 아래와 같다.

타겟요소.closest('검색할 요소')

타겟을 포함해 부모 요소 방향으로 가장 가까운 검색할 요소를 찾는 메소드라고 한다.(형제,자식방향으로는 X)

  • 검색할 요소는 태그이름, 클래스명, id로 모두 가능
  • 반환값은 해당하는 요소, 해당 요소가 없는 경우 null 반환
  • 특정 요소의 존재유무를 확인하는 경우 아래와 같이 활용 가능함
if (aTag.closest(".menu-item")) {
  console.log("menu-item 클래스를 가진 부모 요소가 있음!");
} else {
  console.log("해당하는 부모 요소가 없음!");
}




6. querySelectorAll

❗️ querySelectorAll은 배열(nodelist)을 리턴하고 개인적으로 선택하려면 forEach구문을 사용해야한다. 또한 nodeList를 Array로 만들려면 Array.from()을 사용해야 한다. 하지만 배열은 addEventListner를 추가할 수 없다.

참고문서
https://velog.io/@itssweetrain/JavaScript-event-delegation




7. e.preventDefault()

습관처럼 사용해 왔던 건데도 개념을 잘 이해하지 못하고 있다는 생각에 정리함

  • 이벤트는 발생하지만 기본 동작만 방지하는 것(이벤트가 일어나지 않는 게 아님!!)
  • 일회성으로 조건에 부합하는 이벤트 발생 시에만 기본 동작이 제거되고, 해당하지 않는 경우에는 기본 동작을 실행함

🚀 📌 정리: preventDefault()는 언제 사용하면 좋을까?

상황기본 동작preventDefault() 사용 시
<a> 태그 클릭페이지 이동이동 방지
<form> 제출새로고침 발생새로고침 방지
<input type="checkbox"> 클릭체크 상태 변경체크 안 됨
키보드 단축키 (Ctrl + S)페이지 저장저장 기능 방지
파일 드래그 앤 드롭파일 열림파일 열림 방지




8. classList 메서드

지난 popup component 작업을 하면서 add, remove 정도는 이전에 사용한 적이 있다는 걸 금방 떠올렸었는데, 이외의 다른 메서드들은 아예 처음 사용해보는 것도 있었다. 꽤 유용하게 사용할 수 있을 것 같아 정리함

1. remove()

  • 지정한 클래스 값을 제거
  • 존재하지 않는 클래스를 제거해도 에러 발생 X

2. add()

  • 지정한 클래스 값을 추가
  • 추가하는 클래스가 이미 존재하는 경우 무시
// remove(), add() 로 여러 클래스를 한번에 처리 가능하다.
targetElement.classList.add("class1", "class2", "class3");
targetElement.classList.remove("class1", "class2", "class3");

3. item(index)

  • 인덱스를 이용해 클래스 값을 반환
console.log(targetElement.classList.item(0))

4. toggle()

  • 인수가 하나일 때

    • 클래스가 있으면 제거 후 false 반환, 없으면 추가 후 true를 반환함(최종적으로 클래스가 존재하면 true)
    targetEelemt.classList.toggle('className');
  • 인수가 두 개일 때

    • 두번째 인수가 true면 클래스 값을 추가, false면 제거
    targetElement.classList.toggle('className', 조건);

5. contains()

  • 클래스 값이 해당 요소의 클래스에 존재하면 true, 없으면 false 반환
targetElement.classList.contains('className');
// className이 targetElement의 클래스이름에 존재하면 true/없으면 false

6.replace(old, new)

  • 기존 클래스를 새로운 클래스로 교체
// <div class="class1 old"></div>

targetElement.classList.replace("old", "new");
// <div class="class1 new"></div>

참고문서
https://seokzin.tistory.com/entry/Javascript-classList-%EB%A9%94%EC%84%9C%EB%93%9C-%EC%A2%85%EB%A5%98-remove-add-item-toggle-contains-replace

0개의 댓글