728x90

웹 개발을 하다 보면 사용자가 자유롭게 이동할 수 있는 모달창이 필요할 때가 있습니다. 오늘은 jQuery나 외부 라이브러리 없이 순수 JavaScript만으로 드래그 가능한 모달창을 만드는 방법을 알아보겠습니다.
📋 목차
🎯 완성 예시
먼저 우리가 만들 모달창의 기능을 살펴보겠습니다:
- ✨ 헤더를 드래그하여 자유롭게 이동
- 🎨 부드러운 애니메이션 효과
- 📱 모바일 터치 지원
- ⌨️ ESC 키로 닫기
- 🖱️ 배경 클릭으로 닫기
기본 HTML 구조
가장 먼저 모달창의 기본 구조를 만들어보겠습니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>드래그 모달창</title>
</head>
<body>
<!-- 메인 콘텐츠 -->
<div class="main-content" id="mainContent">
<h1>🎯 드래그 가능한 모달창</h1>
<p>헤더를 드래그해서 모달창을 자유롭게 이동시켜보세요!</p>
<button class="open-modal-btn" onclick="openModal()">모달 열기</button>
</div>
<!-- 모달 오버레이 -->
<div class="modal-overlay" id="modalOverlay" onclick="closeModal(event)">
<div class="modal-container" id="modalContainer">
<!-- 드래그 가능한 헤더 -->
<div class="modal-header" id="modalHeader">
<div class="drag-indicator"></div>
<h2 class="modal-title">🎨 드래그 모달창</h2>
<button class="control-btn" onclick="closeModal()">×</button>
</div>
<!-- 모달 본문 -->
<div class="modal-body">
<h3>드래그 앤 드롭 모달창에 오신 것을 환영합니다!</h3>
<p>이 모달창은 헤더 부분을 드래그하여 화면 내에서 자유롭게 이동할 수 있습니다.</p>
</div>
<!-- 모달 푸터 -->
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal()">취소</button>
<button class="btn btn-primary" onclick="handleAction()">확인</button>
</div>
</div>
</div>
<!-- 위치 표시기 -->
<div class="position-indicator" id="positionIndicator">
위치: <span id="positionText">중앙</span>
</div>
</body>
</html>
📌 핵심 포인트
- modal-overlay: 모달의 배경과 전체 컨테이너
- modal-header: 드래그 가능한 헤더 영역 (중요!)
- drag-indicator: 드래그 가능함을 알려주는 시각적 표시
- position-indicator: 현재 모달 위치를 표시
CSS 스타일링
이제 모달창을 예쁘게 꾸며보겠습니다. 특히 드래그 기능을 위한 스타일에 주목해주세요.
/* 기본 리셋 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 메인 배경 */
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
transition: filter 0.3s ease;
}
/* 모달 오버레이 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
z-index: 1000;
visibility: hidden;
opacity: 0;
transition: all 0.3s ease;
backdrop-filter: blur(5px);
}
.modal-overlay.active {
visibility: visible;
opacity: 1;
}
/* 모달 컨테이너 */
.modal-container {
position: absolute;
background: white;
border-radius: 20px;
box-shadow: 0 25px 50px rgba(0,0,0,0.2);
min-width: 400px;
max-width: 600px;
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
transform: scale(0.7) translateY(-50px);
}
.modal-overlay.active .modal-container {
transform: scale(1) translateY(0);
}
🎨 드래그 헤더 스타일
.modal-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1rem 1.5rem;
cursor: move; /* 드래그 가능함을 표시 */
user-select: none; /* 텍스트 선택 방지 */
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
}
.modal-header.dragging {
cursor: grabbing; /* 드래그 중 커서 변경 */
}
/* 드래그 표시기 */
.drag-indicator {
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 30px;
height: 4px;
background: rgba(255,255,255,0.5);
border-radius: 2px;
top: 8px;
}
JavaScript 드래그 기능
이제 핵심인 드래그 기능을 구현해보겠습니다.
1. 기본 변수 선언
let isDragging = false;
let startX, startY, startLeft, startTop;
2. 모달 열기/닫기 함수
function openModal() {
const overlay = document.getElementById('modalOverlay');
const container = document.getElementById('modalContainer');
const mainContent = document.getElementById('mainContent');
// 모달 위치 초기화 (중앙)
container.style.left = '50%';
container.style.top = '50%';
container.style.transform = 'translate(-50%, -50%) scale(0.7)';
// 모달 표시
overlay.classList.add('active');
mainContent.classList.add('blur');
document.body.style.overflow = 'hidden';
// 애니메이션 후 transform 조정
setTimeout(() => {
container.style.transform = 'translate(-50%, -50%) scale(1)';
}, 50);
}
function closeModal(event) {
// 모달 내부 클릭 시 닫기 방지
if (event && event.target.closest('.modal-container') &&
event.target !== document.getElementById('modalOverlay')) {
return;
}
const overlay = document.getElementById('modalOverlay');
const mainContent = document.getElementById('mainContent');
overlay.classList.remove('active');
mainContent.classList.remove('blur');
document.body.style.overflow = 'auto';
}
3. 드래그 기능 핵심 코드
// 이벤트 리스너 등록
document.getElementById('modalHeader').addEventListener('mousedown', startDrag);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', stopDrag);
function startDrag(e) {
isDragging = true;
const container = document.getElementById('modalContainer');
const header = document.getElementById('modalHeader');
header.classList.add('dragging');
// 현재 위치 계산
const rect = container.getBoundingClientRect();
startX = e.clientX;
startY = e.clientY;
startLeft = rect.left;
startTop = rect.top;
// transform 초기화 (중요!)
container.style.transform = 'none';
container.style.left = startLeft + 'px';
container.style.top = startTop + 'px';
e.preventDefault();
}
function drag(e) {
if (!isDragging) return;
const container = document.getElementById('modalContainer');
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
let newLeft = startLeft + deltaX;
let newTop = startTop + deltaY;
// 화면 경계 체크
const containerRect = container.getBoundingClientRect();
const maxLeft = window.innerWidth - containerRect.width;
const maxTop = window.innerHeight - containerRect.height;
newLeft = Math.max(0, Math.min(newLeft, maxLeft));
newTop = Math.max(0, Math.min(newTop, maxTop));
container.style.left = newLeft + 'px';
container.style.top = newTop + 'px';
}
function stopDrag() {
if (!isDragging) return;
isDragging = false;
const header = document.getElementById('modalHeader');
header.classList.remove('dragging');
}
4. 모바일 터치 지원
// 터치 이벤트 추가
document.getElementById('modalHeader').addEventListener('touchstart', startTouchDrag, { passive: false });
document.addEventListener('touchmove', touchDrag, { passive: false });
document.addEventListener('touchend', stopDrag);
function startTouchDrag(e) {
const touch = e.touches[0];
startDrag({
clientX: touch.clientX,
clientY: touch.clientY,
preventDefault: () => e.preventDefault()
});
}
function touchDrag(e) {
if (!isDragging) return;
const touch = e.touches[0];
drag({
clientX: touch.clientX,
clientY: touch.clientY
});
e.preventDefault();
}
5. 키보드 지원 (ESC로 닫기)
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeModal();
}
});
✅ 주요 기능
- 드래그 앤 드롭: 헤더를 드래그하여 자유롭게 이동
- 경계 체크: 화면 밖으로 나가지 않도록 제한
- 반응형 디자인: 모바일에서도 완벽 작동
- 접근성: 키보드 지원 및 적절한 ARIA 속성
- 부드러운 애니메이션: CSS transition 활용
🎯 핵심 팁
- Transform 초기화: 드래그 시작 시 transform: none으로 설정하여 CSS transform과 JavaScript 위치 계산 충돌 방지
- 이벤트 버블링 방지: 모달 내부 클릭 시 닫기 방지를 위한 이벤트 처리
- 경계 체크: Math.max()와 Math.min()을 활용한 화면 경계 제한
- 터치 지원: 모바일 기기를 위한 터치 이벤트 추가
🔧 추가 개선사항
원한다면 다음 기능들을 추가할 수 있습니다:
- 크기 조절: 모달 모서리 드래그로 크기 변경
- 최소화/최대화: 윈도우와 같은 기능
- 다중 모달: 여러 모달창 동시 관리
- 저장 기능: 모달 위치를 localStorage에 저장
📁 완성된 예시 파일
위에서 설명한 모든 기능이 포함된 완전한 HTML 파일을 확인하고 싶으시면, 밑에 파일을 다운로드해서 확인해주세요~
728x90
'웹 퍼블리싱 | Web Publishing > 팝업 | Popup' 카테고리의 다른 글
| 부드럽게 열리는 팝업창 만들기 - 초보자도 쉽게 따라하는 완벽 가이드 (1) | 2025.06.27 |
|---|