JSFiddle - React, Tailwind, and code Playground
by seulbiLee
HTML
<div class="wrapper">
<p><a href="http://naver.com">포커스 테스트를 위한 링크</a></p>
<button aria-haspopup="dialog" data-popup="popup1">팝업 열기</button>
<div style="height:130vh; display: flex; align-items: flex-end;">
<button aria-haspopup="dialog" data-popup="popup3">팝업 열기</button>
</div>
</div>
<section class="wrap-layer-popup" data-popup="popup1" role="dialog">
<div class="inner-layer-popup">
<div class="wrap-layer-popup-title" tabindex="0">
<h1>타이틀</h1>
</div>
<div class="layer-popup-contents">
<div class="inner">
내용<br/>내용<br/>내용<br/>내용<br/><a href="javascript:;">테스트테스트</a>내용<br/>내용<br/>내용<br/>내용<br/><a href="javascript:;">테스트테스트</a>내용<br/>내용<br/>내용<br/>
<input type="checkbox" id="checkbox" />
<label for="checkbox">
<span>체크체크</span>
</label>
<button aria-haspopup="dialog" data-popup="popup2"><span>2차 팝업 열기</span></button>
<button aria-haspopup="dialog" data-popup="popup4"><span>2차 컨펌 팝업 열기</span></button>
</div>
</div>
<div class="layer-popup-bottom">
<button popup-close="popup1" popup-close-all="true">팝업 모두 닫기</button>
<button popup-close="popup1">현재 팝업 닫기</button>
</div>
<button class="btn-layer-close" popup-close="popup1"><span class="hidden">현재 팝업 닫기</span></button>
</div>
</section>
<section class="wrap-layer-popup" data-popup="popup2" role="dialog">
<div class="inner-layer-popup">
<div class="wrap-layer-popup-title" tabindex="0">
<h1>타이틀22</h1>
</div>
<div class="layer-popup-contents">
<div class="inner">
내용<br/><a href="javascript:;">테스트테스트</a>내용<br/>내용<br/><a href="javascript:;">테스트테스트</a>내용<br/>내용<br/>
<button aria-haspopup="dialog" data-popup="popup3">3차 팝업 열기</button>
</div>
</div>
<div class="layer-popup-bottom">
<button popup-close="popup2">현재 팝업 닫기</button>
<button popup-close="popup2" popup-close-all="true">팝업 모두 닫기</button>
</div>
<button...
CSS
.wrap-layer-popup {
display: none;
align-items: center;
justify-content: center;
position: fixed;
z-index: 991;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.wrap-layer-popup.popup-open {
display: flex;
}
.wrap-layer-popup.popup-open > .inner-layer-popup {
opacity: 1;
}
.wrap-layer-popup.prev-popup {
z-index: 989;
}
.wrap-layer-popup > .inner-layer-popup {
position: relative;
display: flex;
flex-wrap: wrap;
align-content: stretch;
width: 400px;
max-height: 100%;
height: auto;
opacity: 0;
border-radius: 5px;
overflow: hidden;
}
.wrap-layer-popup .wrap-layer-popup-title {
width: 100%;
padding: 10px;
background-color: #fff;
}
.wrap-layer-popup .wrap-layer-popup-title h1 {
margin: 0;
font-size: 18px;
font-weight: 700;
line-height: 30px;
}
.wrap-layer-popup .layer-popup-contents {
position: relative;
width: 100%;
height: 100%;
overflow-y: auto;
background-color: #fff;
}
.wrap-layer-popup .layer-popup-contents > .inner {
/* max-height: calc(100% - 50px); */
box-sizing: border-box;
padding: 20px;
}
.wrap-layer-popup .layer-popup-bottom {
display: flex;
width: 100%;
}
.wrap-layer-popup .layer-popup-bottom button {
width: 100%;
height: 40px;
border: 0;
background-color: #666;
color: #fff;
box-sizing: border-box;
border: 1px solid transparent;
}
.wrap-layer-popup .layer-popup-bottom button[popup-cancle] {
background-color: #fff;
color: #000;
border-top-color: #ccc;
}
.popup-dimmed {
position: fixed;
z-index: 990;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,.7);
transition: opacity .2s;
}
.scroll-lock {
overflow: hidden;
}
.wrapper {
width: 900px;
margin: 0 auto;
}
.hidden {
position: relative;
z-index: -1;
display: inline-block;
overflow: hidden;
height: 1px;
width: 1px;
border: 0;
clip: rect(1px,1px,1px,1px);
clip-path: inset(50%);
word-break: initial;
word-wrap: initial;
}
.btn-layer-close {
...
JavaScript
const popupBtnAll = document.querySelectorAll('[aria-haspopup="dialog"]');
if (popupBtnAll) {
let currentTarget, focusEl = [], popupDepth = 0, popupDimmed, keyEscapeEvt, KeyEvtEl;
const _$this = this,
popupAll = document.querySelectorAll('[role="dialog"]'),
popupCloseBtnAll = document.querySelectorAll('[popup-close]');
// ESC 누름 감지
const keyEvent = {
get keyEscape() {
return this._state;
},
set keyEscape(state) {
this._state = state;
if (state) escKeyEvt(KeyEvtEl, keyEscapeEvt);
},
};
keyEvent;
// popup dimmed 생성
const createdDimmed = () => {
const createDiv = document.createElement('div');
createDiv.classList.add('popup-dimmed');
document.querySelector('body').appendChild(createDiv);
};
// popup dimmed click 시 팝업 닫기
const dimmedClick = (e) => {
if (e.target.classList.contains('wrap-layer-popup')) {
popupCloseAll();
keyEvent.keyEscape = false;
}
};
// popup open
const popupOpen = (e) => {
currentTarget = e.target.tagName;
currentTarget === 'BUTTON' || currentTarget === 'A' ? currentTarget = e.target : currentTarget = e.target.closest('button') || e.target.closest('a');
popupDimmed = document.querySelectorAll('.popup-dimmed');
if (popupDimmed.length === 0) createdDimmed();
popupAll.forEach((popupEl) => {
if (popupEl.getAttribute('data-popup') === currentTarget.getAttribute('data-popup')) {
popupDepth += 1; // popup depth 저장
focusEl.splice((popupDepth - 1), 0, currentTarget); // popup focus Element 저장
popupEl.classList.add('popup-open'); // open class add
popupEl.setAttribute('popup-depth', popupDepth); // popup depth 설정
// dimmed click 이벤트 할당
popupEl.removeEventListener('click', dimmedClick);
popupEl.addEventListener('click', dimmedClick);
document.body.classList.add('scroll-lock'); // popup scroll lock
popupEl.querySelector('.wrap-layer-popup-title').focus(); //...