docs:모달창 생성시 유의사항 개발문서 추가

- pointer-events 문제와 해결방법
- AJAX HTML에서 함수 호출 문제
- 이벤트 델리게이션 사용법
- 완전한 모달 구현 예시

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
pro
2026-01-29 21:45:21 +09:00
parent af88536be6
commit bfc0ee3006

View File

@@ -0,0 +1,233 @@
# 모달창 생성 시 유의사항
## 개요
이 문서는 SAM 프로젝트에서 모달창을 구현할 때 발생할 수 있는 문제점과 해결 방법을 정리한 것입니다.
---
## 1. pointer-events 문제
### 문제 상황
모달 배경 클릭을 방지하면서 모달 내부만 클릭 가능하게 하려고 다음과 같은 구조를 사용했을 때:
```html
<!-- 문제가 발생하는 구조 -->
<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` 구조를 사용하지 않고 단순화:
```html
<!-- 올바른 구조 -->
<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에서 함수 호출 문제
### 문제 상황
```html
<!-- AJAX로 로드된 HTML -->
<button onclick="closeModal()">닫기</button>
```
**증상**: `closeModal is not defined` 오류 발생
### 원인
- 함수가 `function closeModal() {}` 형태로 정의되면 호이스팅되지만, 모듈 스코프나 블록 스코프 안에 있을 수 있음
- AJAX로 로드된 HTML에서 전역 함수에 접근하지 못할 수 있음
### 해결 방법
**방법 1: window 객체에 명시적 등록**
```javascript
// 전역 스코프에 함수 등록
window.closeModal = function() {
document.getElementById('modal').classList.add('hidden');
document.body.style.overflow = '';
};
```
**방법 2: 이벤트 델리게이션 (권장)**
```html
<!-- HTML: data 속성 사용 -->
<button data-close-modal>닫기</button>
```
```javascript
// JavaScript: document 레벨에서 이벤트 감지
document.addEventListener('click', function(e) {
const closeBtn = e.target.closest('[data-close-modal]');
if (closeBtn) {
e.preventDefault();
window.closeModal();
}
});
```
---
## 3. 배경 스크롤 방지
### 모달 열 때
```javascript
document.body.style.overflow = 'hidden';
```
### 모달 닫을 때
```javascript
document.body.style.overflow = '';
```
---
## 4. ESC 키로 모달 닫기
```javascript
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
window.closeModal();
}
});
```
---
## 5. 완전한 모달 구현 예시
### HTML 구조
```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
```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로 로드되는 부분 뷰
```html
<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` - 수정 모달 부분 뷰