본문 바로가기
웹 퍼블리싱 | Web Publishing/팝업 | Popup

순수 JavaScript로 드래그 가능한 모달창 만들기

by 테드 창 2025. 6. 27.
728x90

드래그 되는 팝업 화면 예시 이미지

웹 개발을 하다 보면 사용자가 자유롭게 이동할 수 있는 모달창이 필요할 때가 있습니다. 오늘은 jQuery나 외부 라이브러리 없이 순수 JavaScript만으로 드래그 가능한 모달창을 만드는 방법을 알아보겠습니다.

📋 목차

  1. 기본 HTML 구조
  2. CSS 스타일링
  3. JavaScript 드래그 기능
  4. 완성된 결과

🎯 완성 예시

먼저 우리가 만들 모달창의 기능을 살펴보겠습니다:

  • ✨ 헤더를 드래그하여 자유롭게 이동
  • 🎨 부드러운 애니메이션 효과
  • 📱 모바일 터치 지원
  • ⌨️ 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 활용

🎯 핵심 팁

  1. Transform 초기화: 드래그 시작 시 transform: none으로 설정하여 CSS transform과 JavaScript 위치 계산 충돌 방지
  2. 이벤트 버블링 방지: 모달 내부 클릭 시 닫기 방지를 위한 이벤트 처리
  3. 경계 체크: Math.max()와 Math.min()을 활용한 화면 경계 제한
  4. 터치 지원: 모바일 기기를 위한 터치 이벤트 추가

🔧 추가 개선사항

원한다면 다음 기능들을 추가할 수 있습니다:

  • 크기 조절: 모달 모서리 드래그로 크기 변경
  • 최소화/최대화: 윈도우와 같은 기능
  • 다중 모달: 여러 모달창 동시 관리
  • 저장 기능: 모달 위치를 localStorage에 저장

📁 완성된 예시 파일

위에서 설명한 모든 기능이 포함된 완전한 HTML 파일을 확인하고 싶으시면, 밑에 파일을 다운로드해서 확인해주세요~

드래그가능한모달창_예시.html
0.02MB

728x90