아코디언을 만들려고 쓴 JavaScript를 details의 name 속성으로 지운 과정
문제 발생
FAQ 아코디언에서 "하나를 열면 나머지는 닫힌다"를 구현하려고 <details>에 이벤트를 걸어 서로를 닫고 있었습니다.
const panels = document.querySelectorAll(".faq details");
panels.forEach((panel) => {
panel.addEventListener("toggle", () => {
if (!panel.open) return;
panels.forEach((other) => { if (other !== panel) other.open = false; });
});
});동작은 했지만 서버 렌더링 직후 JavaScript가 붙기 전까지는 여러 개가 열려 있을 수 있었고, 목록이 동적으로 늘면 이벤트를 다시 걸어야 했습니다.
원인 분석
<details>는 열고 닫는 동작 자체는 브라우저가 해주지만, 여러 개 사이의 배타 관계는 표현할 방법이 없었습니다. 그래서 그 관계만을 위해 스크립트가 필요했습니다.
name 속성이 이 관계를 마크업으로 표현합니다. 같은 name 값을 가진 <details>들은 한 그룹이 되고, 그중 하나만 열릴 수 있습니다. 하나를 열면 브라우저가 다른 하나를 닫습니다. 라디오 버튼의 name과 같은 발상입니다.
해결 방안
- 같은 그룹에 같은
name을 줍니다. 위의 스크립트가 통째로 사라집니다.
<details name="faq">
<summary>배송은 얼마나 걸리나요?</summary>
<p>보통 2~3일 걸립니다.</p>
</details>
<details name="faq">
<summary>교환할 수 있나요?</summary>
<p>수령 후 7일 이내에 가능합니다.</p>
</details>- 초기 상태는
open하나만 씁니다. 그룹 안에서 여러 개에open을 달면 문서 순서상 첫 번째만 열린 채로 렌더됩니다. 두 개를 열어두려던 의도라면 그 자체가 배타 그룹과 모순입니다. - 상태 변화는
toggle이벤트로 받습니다. 상태가 바뀐 뒤에 발생하므로, 핸들러 안에서는details.open이 이미 새 값입니다.
details.addEventListener("toggle", () => {
if (details.open) trackOpen(details.querySelector("summary").textContent);
});name의 지원 범위는<details>자체보다 좁습니다.<details>는 2020년 1월부터 널리 쓸 수 있지만name은 나중에 들어온 속성입니다. 미지원 브라우저에서는 배타 동작만 사라지고 각 항목은 정상적으로 열고 닫힙니다 — 기능이 깨지는 게 아니라 하나 덜 하는 쪽으로 무너지므로, 점진적 향상으로 쓰기 좋습니다.- 열린 영역을 애니메이션하려면
::details-content를 봅니다. 예전에는 내용을 별도 요소로 감싸야 했습니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.