Files
sam-docs/guides/모달창_생성시_유의사항.md
pro ec74d63120 docs:mng/claudedocs 가이드 문서들 guides 폴더로 이동
이동된 파일:
- 2025-12-02_file-attachment-feature.md
- ai-config-설정.md
- archive-restore-feature-analysis.md
- barobill-members-migration.md
- super-admin-protection.md
- 명함추출로직.md
- 모달창_생성시_유의사항.md
- 상품관리정보.md
- 수당지급.md
- 영업파트너구조.md
- 홈택스 매입매출 조회성공.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 16:29:16 +09:00

6.4 KiB

모달창 생성 시 유의사항

개요

이 문서는 SAM 프로젝트에서 모달창을 구현할 때 발생할 수 있는 문제점과 해결 방법을 정리한 것입니다.


1. pointer-events 문제

문제 상황

모달 배경 클릭을 방지하면서 모달 내부만 클릭 가능하게 하려고 다음과 같은 구조를 사용했을 때:

<!-- 문제가 발생하는 구조 -->
<div class="fixed inset-0 z-50">
    <div class="absolute inset-0 bg-black bg-opacity-50"></div>
    <div class="absolute inset-0 flex items-center justify-center pointer-events-none">
        <div class="bg-white rounded-xl pointer-events-auto">
            <!-- AJAX로 로드되는 내용 -->
        </div>
    </div>
</div>

증상: 모달은 표시되지만 내부의 버튼, 입력 필드 등 모든 요소가 클릭되지 않음 (마치 돌덩어리처럼 동작)

원인

  • pointer-events-none이 부모에 있고 pointer-events-auto가 자식에 있는 구조
  • AJAX로 로드된 내용이 pointer-events-auto div 안에 들어가도, 그 안의 요소들에 pointer-events가 제대로 상속되지 않을 수 있음
  • 특히 동적으로 로드된 HTML에서 이 문제가 자주 발생

해결 방법

pointer-events-none/auto 구조를 사용하지 않고 단순화:

<!-- 올바른 구조 -->
<div id="modal" class="hidden fixed inset-0 z-50 overflow-y-auto">
    <!-- 배경 오버레이 -->
    <div class="fixed inset-0 bg-black bg-opacity-50"></div>
    <!-- 모달 컨텐츠 wrapper -->
    <div class="flex min-h-full items-center justify-center p-4">
        <div id="modalContent" class="relative bg-white rounded-xl shadow-2xl w-full max-w-3xl">
            <!-- 내용 -->
        </div>
    </div>
</div>

2. AJAX로 로드된 HTML에서 함수 호출 문제

문제 상황

<!-- AJAX로 로드된 HTML -->
<button onclick="closeModal()">닫기</button>

증상: closeModal is not defined 오류 발생

원인

  • 함수가 function closeModal() {} 형태로 정의되면 호이스팅되지만, 모듈 스코프나 블록 스코프 안에 있을 수 있음
  • AJAX로 로드된 HTML에서 전역 함수에 접근하지 못할 수 있음

해결 방법

방법 1: window 객체에 명시적 등록

// 전역 스코프에 함수 등록
window.closeModal = function() {
    document.getElementById('modal').classList.add('hidden');
    document.body.style.overflow = '';
};

방법 2: 이벤트 델리게이션 (권장)

<!-- HTML: data 속성 사용 -->
<button data-close-modal>닫기</button>
// JavaScript: document 레벨에서 이벤트 감지
document.addEventListener('click', function(e) {
    const closeBtn = e.target.closest('[data-close-modal]');
    if (closeBtn) {
        e.preventDefault();
        window.closeModal();
    }
});

3. 배경 스크롤 방지

모달 열 때

document.body.style.overflow = 'hidden';

모달 닫을 때

document.body.style.overflow = '';

4. ESC 키로 모달 닫기

document.addEventListener('keydown', function(e) {
    if (e.key === 'Escape') {
        window.closeModal();
    }
});

5. 완전한 모달 구현 예시

HTML 구조

<!-- 모달 -->
<div id="exampleModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
    <!-- 배경 오버레이 -->
    <div class="fixed inset-0 bg-black bg-opacity-50 transition-opacity"></div>
    <!-- 모달 컨텐츠 wrapper -->
    <div class="flex min-h-full items-center justify-center p-4">
        <div id="exampleModalContent" class="relative bg-white rounded-xl shadow-2xl w-full max-w-3xl">
            <!-- 로딩 표시 또는 내용 -->
        </div>
    </div>
</div>

JavaScript

// 전역 함수 등록
window.openExampleModal = function(id) {
    const modal = document.getElementById('exampleModal');
    const content = document.getElementById('exampleModalContent');

    modal.classList.remove('hidden');
    document.body.style.overflow = 'hidden';

    // AJAX로 내용 로드
    fetch(`/api/example/${id}`)
        .then(response => response.text())
        .then(html => {
            content.innerHTML = html;
        });
};

window.closeExampleModal = function() {
    document.getElementById('exampleModal').classList.add('hidden');
    document.body.style.overflow = '';
};

// ESC 키 지원
document.addEventListener('keydown', function(e) {
    if (e.key === 'Escape') {
        window.closeExampleModal();
    }
});

// 이벤트 델리게이션 (닫기 버튼)
document.addEventListener('click', function(e) {
    if (e.target.closest('[data-close-modal]')) {
        e.preventDefault();
        window.closeExampleModal();
    }
});

AJAX로 로드되는 부분 뷰

<div class="p-6">
    <div class="flex justify-between items-center mb-4">
        <h2 class="text-xl font-bold">모달 제목</h2>
        <!-- data-close-modal 속성 사용 -->
        <button type="button" data-close-modal class="text-gray-400 hover:text-gray-600">
            <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
            </svg>
        </button>
    </div>

    <!-- 내용 -->

    <div class="flex justify-end gap-3 mt-6">
        <button type="button" data-close-modal class="px-4 py-2 border rounded-lg">취소</button>
        <button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded-lg">확인</button>
    </div>
</div>

6. 체크리스트

모달 구현 시 다음 사항을 확인하세요:

  • pointer-events-none/auto 구조를 사용하지 않음
  • 함수를 window 객체에 등록했음
  • 닫기 버튼에 data-close-modal 속성을 추가했음
  • document 레벨 이벤트 델리게이션을 설정했음
  • 모달 열 때 body.style.overflow = 'hidden' 설정
  • 모달 닫을 때 body.style.overflow = '' 복원
  • ESC 키 이벤트 리스너 등록
  • z-index가 다른 요소들과 충돌하지 않음 (보통 z-50 사용)

관련 파일

  • /resources/views/sales/managers/index.blade.php - 영업파트너 관리 모달 구현 예시
  • /resources/views/sales/managers/partials/show-modal.blade.php - 상세 모달 부분 뷰
  • /resources/views/sales/managers/partials/edit-modal.blade.php - 수정 모달 부분 뷰