블로그에 사용할 레이어팝업을 만들면서, 웹 접근성이 충분히 보장되도록 작업을 했고, 코드는 다음과 같다.
See the Pen YzGpLNZ by sel (@selucky) on CodePen.
HTML
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 레이어 열기 버튼 -->
<button type="button" class="open-lp" aria-controls="lp1">open</button>
<!-- 레이어 팝업 -->
<div class="layer-pop" id="lp1" role="dialog" aria-modal="true" aria-labelledby="lp-title">
<div class="layer-pop__inner">
<button type="button" aria-label="닫기" class="layer-pop__close">X</button>
<h2 id="lp-title">레이어 타이틀</h2>
<a href="#">내용</a>
<span tabindex="0">내용</span>
</div>
</div>
role이나 aria- 로 시작하는 속성들에 대해선 WAI-ARIA를 찾아보길 권하며, 마크업에 대한 설명은 딱히 적지 않을 것임. 이 글을 보고 있는 이들 대부분은 많이 알고 있을 것이라 생각하므로(나만 이렇게 생각하나)..
Javascript
Vanilla JS가 아닌 jQuery로 작성하였다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// jQuery 환경에서만 작동할 수 있게 처리
if (typeof jQuery === "undefined") throw new Error("Modal requires jQuery.");
// 레이어 팝업 열기 버튼 클릭 시 팝업 보이기
$(".open-lp").on("click", function() {
var op = $(this);
var lp = $("#" + $(this).attr("aria-controls"));
var lpObj = lp.children(".layer-pop__inner");
var lpObjClose = lp.find(".layer-pop__close");
var lpObjTabbable = lpObj.find("button, input:not([type='hidden']), select, iframe, textarea, [href], [tabindex]:not([tabindex='-1'])");
var lpObjTabbableFirst = lpObjTabbable && lpObjTabbable.first();
var lpObjTabbableLast = lpObjTabbable && lpObjTabbable.last();
var lpOuterObjHidden = $(".skip-links, .masthead, .initial-content, .search-content, .page__footer"); // 레이어 바깥 영역의 요소
var all = $(".masthead, .page__footer").add(lp);
var tabDisable;
var nowScrollPos = $(window).scrollTop();
$("body").css("top", - nowScrollPos).addClass("scroll-off").on("scroll touchmove mousewheel", function(event){
event.preventDefault(); // iOS 레이어 열린 상태에서 body 스크롤되는 문제 방지
});
function lpClose() { // 레이어 닫기 함수
$("body").removeClass("scroll-off").css("top", "").off("scroll touchmove mousewheel");
$(window).scrollTop(nowScrollPos); // 레이어 닫은 후 화면 최상단으로 이동 방지
if (tabDisable === true) lpObj.attr("tabindex", "-1");
all.removeClass("on");
lpOuterObjHidden.removeAttr("aria-hidden");
op.focus(); // 레이어 닫은 후 원래 있던 곳으로 초점 이동
$(document).off("keydown.lp_keydown");
}
$(this).blur();
all.addClass("on");
lpOuterObjHidden.attr("aria-hidden", "true"); // 레이어 바깥 영역을 스크린리더가 읽지 않게
lpObjTabbable.length ? lpObjTabbableFirst.focus().on("keydown", function(event) {
// 레이어 열리자마자 초점 받을 수 있는 첫번째 요소로 초점 이동
if (event.shiftKey && (event.keyCode || event.which) === 9) {
// Shift + Tab키 : 초점 받을 수 있는 첫번째 요소에서 마지막 요소로 초점 이동
event.preventDefault();
lpObjTabbableLast.focus();
}
}) : lpObj.attr("tabindex", "0").focus().on("keydown", function(event){
tabDisable = true;
if ((event.keyCode || event.which) === 9) event.preventDefault();
// Tab키 / Shift + Tab키 : 초점 받을 수 있는 요소가 없을 경우 레이어 밖으로 초점 이동 안되게
});
lpObjTabbableLast.on("keydown", function(event) {
if (!event.shiftKey && (event.keyCode || event.which) === 9) {
// Tab키 : 초점 받을 수 있는 마지막 요소에서 첫번째 요소으로 초점 이동
event.preventDefault();
lpObjTabbableFirst.focus();
}
});
lpObjClose.on("click", lpClose); // 닫기 버튼 클릭 시 레이어 닫기
lp.on("click", function(event){
if (event.target === event.currentTarget) {
// 반투명 배경 클릭 시 레이어 닫기
lpClose();
}
});
$(document).on("keydown.lp_keydown", function(event) {
// Esc키 : 레이어 닫기
var keyType = event.keyCode || event.which;
if (keyType === 27 && lp.hasClass("on")) {
lpClose();
}
});
});
주요 기능
- 레이어가 열리면 레이어 바깥 영역(최상단 header, 메인콘텐츠, 최하단 footer 등)을 스크린리더가 읽지 않게 처리
- 레이어가 열리면 초점을 받을 수 있는 요소 중 첫번째 요소로 초점 이동
- 요소가 없을 경우 타겟 레이어(레이어의 container)로 초점 이동 및 레이어 밖으로 초점 이동이 가지 않게 처리.
- 초점 받을 수 있는 첫번째 요소에서 Shift + Tab키 누르면 마지막 요소로 초점 이동
- 초점 받을 수 있는 마지막 요소에서 Tab키 누르면 첫번째 요소로 초점 이동. 즉 초점 이동이 레이어 내부에서만 돌게 처리.
- 검은 반투명 배경 클릭 시 레이어 닫기
- Esc키 누르면 레이어 닫기
레이어가 열리면 초점을 받을 수 있는 첫번째 요소가 초점을 받는 게 개인적으로 맞다고 본다.
타겟 레이어가 먼저 초점을 받아야 한다고 한때 생각했었는데, 타겟 레이어에 초점이 잡혀서 레이어에 진입했다는 걸 시각적으로 알려줄 수 있다는 것이다. 이렇게 구현했었으나, 초점을 받을 수 있는 첫번째 요소에 초점 이동이 되게 하여 사용자로 하여금 더 수월하게 레이어 내부 콘텐츠를 탐색할 수 있게끔 하는 게 맞다고 생각을 바꿔먹고 다시 작업하였음.
200624, 기존 테마 삭제하고 새 테마로 교체해서 레이어팝업도 다시 추가 예정(언제쯤..?) 210213, 지금 보니 레이어팝업은 필요가 없다.. -_-
Hero image from danigeza