레이어 팝업 샘플
화면을 열면 바로 뜨는 안내 팝업입니다. 지금 이 화면에도 3개가 떠 있습니다 —
WEB-INF/jsp/common/layerPopup.jsp 조각을 <jsp:include> 한 결과입니다.
🚨 될 수 있으면 쓰지 마세요
레이어 팝업은 사용자가 요청하지 않았는데 화면을 가리고 뜨는 UI 입니다.
웹에서 가장 평이 나쁜 패턴이고, 접근성과 모바일에서 특히 불리합니다
(포커스가 튀고, 좁은 화면에서는 본문을 통째로 덮습니다).
전 국민이 쓰는 공공 포털이므로, 관리자가 등록해 전체 사용자에게 알리는 공지(메인 팝업존)에만 쓰는 것을 원칙으로 합니다.
먼저 이 대안들을 검토하세요.
전 국민이 쓰는 공공 포털이므로, 관리자가 등록해 전체 사용자에게 알리는 공지(메인 팝업존)에만 쓰는 것을 원칙으로 합니다.
먼저 이 대안들을 검토하세요.
- 화면 안 안내문 — 자리만 차지하고 아무것도 가리지 않습니다. 대부분 이걸로 충분합니다.
- 사용자가 버튼을 눌러 여는 모달 (모달 팝업 샘플) — 사용자가 원해서 연 것이라 문제가 없습니다.
- 게시판 공지 상단 고정
💡 한 줄 요약 — 화면 JSP 에 include 한 줄, 컨트롤러에서 모델에 목록 한 줄.
목록이 비어 있으면 아무것도 그리지 않습니다 (CSS/JS 도 로드하지 않습니다). 여러 개 동시 노출 · 계단식 배치 · 드래그 · ESC 닫기 · 화면 밖으로 안 나가기 · 반응형 · 웹접근성은 이미 되어 있습니다.
목록이 비어 있으면 아무것도 그리지 않습니다 (CSS/JS 도 로드하지 않습니다). 여러 개 동시 노출 · 계단식 배치 · 드래그 · ESC 닫기 · 화면 밖으로 안 나가기 · 반응형 · 웹접근성은 이미 되어 있습니다.
1. 지금 화면에서 확인해 보기
떠 있는 팝업을 닫았다면 아래 버튼으로 다시 여세요. '오늘 하루 그만보기' 를 눌렀다면
쿠키를 지워야 다시 뜹니다 — 그 버튼이 실제로 어떻게 동작하는지 확인하는 용도입니다.
제목줄을 잡고 드래그해 보세요. 겹친 팝업은 클릭하면 맨 앞으로 나옵니다. ESC 는 맨 앞 팝업 하나만 닫습니다. 창 크기를 780px 아래로 줄이면 세로로만 쌓이는 카드 뭉치로 바뀝니다.
2. 붙이는 법 — 두 곳만 고치면 됩니다
이 화면이 실제로 하고 있는 일 그대로입니다.
① 화면 JSP (위치는 본문 최상위가 안전합니다 — 아래 주의사항 참고)
<jsp:include page="/WEB-INF/jsp/common/layerPopup.jsp"/>
② 컨트롤러
List<Map<String, Object>> pops = new ArrayList<Map<String, Object>>();
Map<String, Object> p = new HashMap<String, Object>();
p.put("mainPupId", "N001"); // 필수 — '오늘 하루 그만보기' 쿠키 이름에 쓰인다
p.put("title", "서비스 점검 안내");
p.put("imgYn", "N"); // 'Y' = 이미지형, 그 외 = HTML형
p.put("htmlCnts", "<p>내용</p>");
p.put("pupWidthSize", "480"); // 가로 px (없으면 500)
p.put("pupHeightSize", "320"); // 세로 px 상한 (HTML형만)
pops.add(p);
model.addAttribute("layerPopupData", pops);
⚠ VO 말고
조각은
List<Map<String,Object>> 를 쓰세요.조각은
mainPupId / title / imgYn / atchFileId / htmlCnts / linkUrl / pupWidthSize / pupHeightSize / pupDesc 를 EL 로 읽습니다.
VO 에 이 getter 가 하나라도 없으면 PropertyNotFoundException 으로 화면이 통째로 500 이 됩니다
(빈 값으로 넘어가지 않습니다). Map 은 없는 키가 null 이라 안전하고, 쓰지 않는 필드를 안 채워도 됩니다.
3. 항목 필드
| 필드 | 필수 | 설명 |
|---|---|---|
mainPupId | ✔ | 팝업 식별자. '오늘 하루 그만보기' 쿠키 이름에 쓰인다 ※ 이름이 main~ 인 것은 메인 팝업 테이블에서 온 이름입니다. 공통으로 옮기면서도 그대로 뒀습니다 — 바꾸면 DAO·SQL 까지 번집니다 |
imgYn | ✔ | 'Y' = 이미지형, 그 외 = HTML형 |
title | 제목. 드래그 손잡이이자 접근성 이름이라 되도록 넣으세요. 없으면 제목줄을 안 그리고 드래그도 안 됩니다 | |
htmlCnts | HTML형 | 본문 HTML. 이스케이프하지 않고 그대로 출력합니다(관리자 입력 전제) |
atchFileId | 이미지형 | 첨부파일 ID |
linkUrl | 이미지 클릭 시 새 창으로 이동. 비었거나 http:// 만 있으면 링크를 안 겁니다 | |
pupWidthSize | 가로 px. 없으면 500, 최소 360 은 CSS 가 보장 | |
pupHeightSize | 세로 px 상한. HTML형에만 적용 ※ 가로는 '실제 폭', 세로는 '상한' 입니다 — 내용이 짧은데 세로를 고정하면 본문과 버튼 사이에 큰 빈 공간이 생깁니다 | |
pupDesc | 이미지 대체텍스트 후보(title 이 없을 때) |
4. include 파라미터 3가지
이 화면은 아래처럼 세 가지를 다 넘기고 있습니다.
<jsp:include page="/WEB-INF/jsp/common/layerPopup.jsp">
<jsp:param name="cookiePrefix" value="smpLayerPop_"/>
<jsp:param name="todayBtn" value="Y"/>
<jsp:param name="imgUrlPrefix" value="/pub2026/images/main/"/>
</jsp:include>
| 파라미터 | 기본값 | 설명 |
|---|---|---|
cookiePrefix |
layerPop_ |
🚨 화면마다 다르게 주세요.
쿠키는 접두사 + 팝업ID 이고 path=/ 라 사이트 전역입니다.
화면마다 팝업 ID 체계가 다르므로(메인은 팝업 채번, 다른 화면은 게시글 ID 등)
번호가 겹치는 순간 한 화면에서 끈 팝업이 다른 화면에서도 꺼집니다.※ 메인은 기본값을 그대로 씁니다 — 바꾸면 이미 '그만보기' 를 누른 사용자의 쿠키가 초기화됩니다 |
todayBtn |
Y |
N 이면 '오늘 하루 그만보기' 버튼을 안 그립니다.
약관 변경 고지처럼 매번 보여야 하는 안내에만 쓰세요.
버튼만 빠지고 '닫기' 는 그대로라, 그 팝업은 새로고침하면 다시 뜹니다(그게 목적입니다).⚠ 남용 금지 — 끌 수 없는 팝업은 사용자에게 가장 나쁜 형태입니다. |
imgUrlPrefix |
/common/getImageFile.do?fd= |
이미지형 팝업의 파일 서비스 경로. 뒤에 첨부파일ID + &fs=0 이 붙습니다.SDC( TN_SBCS_FILE_DTL) 자료면 기본값으로는 이미지가 404 입니다
→ /common/getSdcImageFile.do?fd= 를 넘기세요.※ 한 화면의 팝업이 두 DB 에 섞여 있으면 이 방식으로는 안 됩니다. 그때는 컨트롤러가 완성된 URL 을 항목에 담는 구조로 바꿔야 합니다(메인 배너 팝업존의 PopupZone.imgUrl 이 그 예)※ 이 샘플은 DB 첨부파일이 없어 퍼블 정적 이미지 폴더를 가리켜 두었습니다. 실제 화면에서는 건드릴 일이 거의 없습니다 |
5. 이미 되어 있는 것 — 따로 안 해도 됩니다
- 여러 개 동시 노출 — 계단식으로 겹쳐 배치. 클릭하면 그 팝업이 맨 앞으로
- 계단 간격 자동 — 앞 팝업의 제목줄 높이를 실측해 그만큼 내립니다(제목이 두 줄이어도 X 버튼이 안 가립니다). 동시에 맨 앞 팝업 몫을 먼저 떼는 예산이 있어, 개수가 많아도 맨 앞 본문이 사라지지 않습니다
- 드래그 이동 — 제목줄을 잡고 이동. 터치 기기도 됩니다(jQuery UI 는 마우스만 듣습니다). jQuery UI draggable 이 없는 빌드에서는 조용히 넘어갑니다
- ESC 로 닫기 — 맨 앞 팝업부터 하나씩. 모달이 열려 있으면 모달에 양보합니다
- 화면 밖으로 안 나감 — 팝업마다 자기 위치 기준으로 높이 상한을 잡아 하단 버튼이 항상 눌립니다. 창 크기를 바꾸면 다시 계산합니다
- 헤더를 안 가림 —
#wrap-header높이를 실측해 그 아래에서 시작합니다. 헤더가 없는 레이아웃이면 기본값(top:200px)을 씁니다 - 반응형 — 780px 이하에서는 가로 계단을 없애고 세로로만 쌓습니다(카드 뭉치)
- 웹접근성 —
role="dialog"+aria-modal="false"+ 제목 연결, X 버튼 이름 부여
6. 붙일 때 흔히 막히는 곳
| 증상 | 원인 / 조치 |
|---|---|
| 아무것도 안 뜬다 | 모델이 비었거나 이름이 다름 — layerPopupData (메인은 mainPopupData) |
| 화면이 500 으로 죽는다 | VO 에 위 getter 가 없음 → List<Map> 으로 바꾸거나 getter 추가 |
| 이미지가 깨진다 | imgUrlPrefix 가 SDC 인데 MDIS 기본값을 쓰고 있음, 또는 파일 실물 없음 |
| 다른 화면에서 껐는데 여기도 꺼짐 | cookiePrefix 를 안 갈랐음 |
| 계속 안 뜬다 | 이전에 '오늘 하루 그만보기' 를 누름. 브라우저 쿠키에서 접두사+ID 삭제 |
| 팝업이 화면이 아니라 어떤 박스 안에 갇힌다 | include 를 transform/filter/perspective 가 걸린 요소 안에 넣었음. 그 요소가 position:fixed 의 새 기준이 됩니다 → 본문 최상위로 옮기세요 |
| 같은 팝업이 두 벌 뜬다 | 레이아웃과 화면이 둘 다 include 함. 조각에 가드가 있어 보통은 막히지만, include 를 한 곳으로 정리하세요 |
| 드래그가 안 된다 | title 이 비어 손잡이(제목줄)가 없음. 또는 780px 이하 좁은 화면(의도된 동작) |
