Skip to Content
WebHTML모던 HTML03. dialog로 만드는 프로젝트 상세 모달

이번 편의 결과물: projects.html 프로젝트 카드의 “자세히” 버튼을 누르면 dialog#project-dialog가 열려 프로젝트 상세 정보를 보여줍니다 · 다루는 개념: show()showModal()의 차이, ::backdrop, top layer, ESC로 닫히는 기본 동작

이 편에서 만드는 파일

web-practice/portfolio/ ├── projects.html ~ (dialog#project-dialog 마크업 추가, 카드마다 "자세히" 버튼 추가) └── js/ └── main.js ~ (프로젝트 데이터, dialog 여는 로직 작성)

개념 정리

show()showModal()

<dialog>는 두 가지 방식으로 열립니다. 차이는 배경 상호작용 차단 여부입니다.

메서드배경 클릭·포커스시각 효과용도
show()막지 않음(non-modal)::backdrop 없음알림 패널처럼 배경과 공존하는 UI
showModal()막음(modal)::backdrop 생김사용자가 반드시 처리해야 하는 모달

프로젝트 상세 정보는 사용자가 “닫기”를 눌러야 원래 화면으로 돌아가는 것이 자연스러우므로 이 편에서는 showModal()만 씁니다. close()는 두 방식 모두에서 닫을 때 씁니다.

::backdrop과 top layer

showModal()로 연 dialog는 브라우저가 별도로 관리하는 top layer(문서 렌더링 트리 바깥의 최상단 레이어)에 올라갑니다. z-index 값을 조작하지 않아도 항상 다른 모든 요소 위에 그려지는 이유입니다. 이때 dialog와 나머지 화면 사이에 ::backdrop 의사 요소가 자동으로 생깁니다. 이 편에서는 최소한의 어둡기만 넣습니다.

/* portfolio/css/styles.css (css_fundamentals 과목에서 정식 배치, 지금은 개념 확인용) */ dialog#project-dialog::backdrop { background: rgb(0 0 0 / 0.5); }

ESC로 닫히는 기본 동작

showModal()로 연 dialog는 별도 코드 없이 Esc 키로 닫힙니다. 브라우저가 자동으로 cancel 이벤트를 발생시키고 dialog를 닫습니다. 이 동작을 막으려면 cancel 이벤트에서 preventDefault()를 호출해야 하는데, 이 편에서는 기본 동작을 그대로 둡니다. 04편에서 “닫기” 버튼과 포커스 복귀를 다룰 때 이 기본 동작과 함께 씁니다.

실습

1. dialog 마크업 추가하기

projects.htmlproject-grid 아래, </main> 앞에 재사용할 dialog 하나를 둡니다. 카드마다 dialog를 따로 만들지 않고 하나의 dialog 내용을 그때그때 채우는 방식을 씁니다.

<!-- portfolio/projects.html --> <dialog id="project-dialog" class="project-dialog"> <h2 class="dialog-title"></h2> <p class="dialog-background"></p> <p class="dialog-role"></p> <p class="dialog-learned"></p> </dialog>

2. 카드마다 “자세히” 버튼 추가하기

6개 프로젝트 카드 각각에 data-project-id를 붙인 버튼을 넣습니다. 아래는 첫 번째 카드입니다. 나머지 5개도 같은 구조로 idbookshelf·notes·a11y·tokens·todo-cli로 바꿔 반복합니다.

<!-- portfolio/projects.html의 project-grid 안 --> <div class="project-grid"> <article class="project-card"> <img src="images/project-01.svg" alt="오늘의 날씨 프로젝트 화면" width="1200" height="800" /> <h3>오늘의 날씨</h3> <p>위치 기반 날씨를 카드로 보여주는 웹앱입니다. 오프라인에서도 마지막 데이터를 보여줍니다.</p> <ul class="tags"> <li>JavaScript</li> <li>PWA</li> </ul> <button type="button" class="btn-detail" data-project-id="weather">자세히</button> </article> <!-- bookshelf, notes, a11y, tokens, todo-cli 카드도 같은 구조로 반복 --> </div>

main.js가 없어도 페이지는 깨지지 않지만, 지금은 버튼을 눌러도 아무 일도 일어나지 않는 상태입니다. 3단계에서 채웁니다.

3. main.js에 프로젝트 데이터와 여는 로직 작성하기

copy.md의 6개 프로젝트 설명을 3~4문장으로 늘려 데이터 객체를 만듭니다. 버튼을 누르면 id로 데이터를 찾아 dialog 내용을 채우고 showModal()을 호출합니다.

// portfolio/js/main.js const projectDialog = document.querySelector('#project-dialog'); const projectDetails = { weather: { title: '오늘의 날씨', background: '위치 기반 날씨 정보를 빠르게 확인할 수 있는 화면이 필요해서 시작한 프로젝트입니다.', role: 'fetch로 외부 날씨 API를 호출하고, Service Worker로 마지막 응답을 캐시했습니다.', learned: '네트워크가 끊겨도 마지막 데이터를 보여주는 오프라인 우선 설계를 배웠습니다.', }, bookshelf: { title: '독서 기록 bookshelf', background: '읽은 책과 별점을 기록할 곳이 마땅치 않아 직접 만든 React 앱입니다.', role: '컴포넌트를 상태별로 나누고, Context로 테마를 전역에서 관리했습니다.', learned: 'props와 state를 분리하는 기준, 리스트 렌더링 최적화를 배웠습니다.', }, notes: { title: '메모 앱 notes', background: '서버 없이 브라우저 저장소만으로 동작하는 메모 앱을 만들어 보고 싶었습니다.', role: 'IndexedDB로 메모를 저장하고, 검색은 Worker 스레드로 분리했습니다.', learned: '메인 스레드를 막지 않고 무거운 검색을 처리하는 방법을 배웠습니다.', }, a11y: { title: '접근성 체크리스트', background: 'WCAG 항목을 하나씩 점검할 수 있는 정적 사이트가 필요했습니다.', role: '시맨틱 요소와 ARIA 속성만으로 체크리스트 UI를 구성했습니다.', learned: '시맨틱 마크업이 접근성의 기본 토대라는 것을 실감했습니다.', }, tokens: { title: '디자인 토큰 미리보기', background: 'CSS 변수로 만든 색·간격 토큰을 한눈에 비교하고 싶어 만들었습니다.', role: 'CSS 커스텀 속성을 값별로 나열하고, 배경색을 그대로 미리보기로 썼습니다.', learned: '디자인 토큰을 코드로 관리하면 일관성을 지키기 쉽다는 것을 배웠습니다.', }, 'todo-cli': { title: 'todo-cli', background: '터미널에서 바로 쓸 수 있는 할 일 관리 도구가 필요해 만들었습니다.', role: 'Node.js로 파일 저장소를 구현하고, 명령어별로 모듈을 나눴습니다.', learned: '파일 I/O를 비동기로 다루는 법과 CLI 인자 파싱을 배웠습니다.', }, }; document.querySelectorAll('[data-project-id]').forEach((button) => { button.addEventListener('click', () => { const detail = projectDetails[button.dataset.projectId]; projectDialog.querySelector('.dialog-title').textContent = detail.title; projectDialog.querySelector('.dialog-background').textContent = detail.background; projectDialog.querySelector('.dialog-role').textContent = detail.role; projectDialog.querySelector('.dialog-learned').textContent = detail.learned; projectDialog.showModal(); }); });

4. 실행하고 확인하기

VS Code의 Live Server 등으로 projects.html을 엽니다. main.js를 아직 script 태그로 연결하지 않았다면 </body> 앞에 다음 한 줄을 추가합니다.

<script type="module" src="js/main.js"></script>
$ (VS Code Live Server 확장으로 projects.html 열기)

확인

  • 첫 번째 카드의 “자세히”를 누르면 화면 가운데 dialog가 뜨고, 배경이 반투명하게 어두워진다(::backdrop).
  • dialog 안 제목·본문 3문단이 클릭한 카드에 맞는 내용으로 채워진다.
  • Esc 키를 누르면 별도 코드 없이 dialog가 닫힌다.
  • 다른 카드를 눌러도 같은 dialog가 내용만 바뀌어 다시 열린다.

직접 해보기

show()로 바꿔서 열어 보고 무엇이 달라지는지 관찰해 보세요. showModal()show()로 바꾼 뒤 배경 카드를 클릭하거나 Tab 키로 포커스를 옮겨 보면 어떤 차이가 있나요?

답 확인

show()로 열면 ::backdrop이 생기지 않아 배경이 어두워지지 않고, 배경의 다른 버튼이나 링크를 그대로 클릭·포커스할 수 있습니다. showModal()은 top layer에 올리면서 배경 상호작용을 차단하지만, show()는 dialog를 문서 흐름 위에 겹쳐 보여줄 뿐 배경을 막지 않습니다.

자주 하는 실수

증상원인고치는 법
버튼을 눌러도 dialog가 안 열린다main.jsscript 태그로 연결되지 않았다</body>script type="module" src="js/main.js" 확인
dialog는 열리는데 배경이 어두워지지 않는다show()를 썼거나 ::backdrop CSS가 없다showModal()인지 확인하고, ::backdrop 스타일이 css_fundamentals 편에서 아직 없다면 정상이다
두 번째 카드를 누르면 내용이 안 바뀐다data-project-id 값과 projectDetails 키가 다르다버튼의 data-project-id와 객체 키 철자를 문자 그대로 비교한다

확인 문제

문제 14지선다
showModal()과 show()의 핵심 차이는?
문제 24지선다
top layer의 특징으로 옳은 것은?
문제 34지선다
showModal()로 연 dialog를 Esc로 닫을 때 필요한 추가 코드는?
문제 44지선다
이 실습에서 dialog를 6개가 아니라 1개만 만든 이유는?

참고 자료

Last updated on