472 lines
12 KiB
Plaintext
472 lines
12 KiB
Plaintext
|
|
---
|
||
|
|
description: 스크롤 문제 디버깅 및 해결 가이드 - Flexbox 레이아웃에서 스크롤이 작동하지 않을 때 체계적인 진단과 해결 방법
|
||
|
|
---
|
||
|
|
|
||
|
|
# 스크롤 문제 디버깅 및 해결 가이드
|
||
|
|
|
||
|
|
React/Next.js 프로젝트에서 Flexbox 레이아웃의 스크롤이 작동하지 않을 때 사용하는 체계적인 디버깅 및 해결 방법입니다.
|
||
|
|
|
||
|
|
## 1. 스크롤 문제의 일반적인 원인
|
||
|
|
|
||
|
|
### 근본 원인: Flexbox의 높이 계산 실패
|
||
|
|
|
||
|
|
Flexbox 레이아웃에서 스크롤이 작동하지 않는 이유:
|
||
|
|
|
||
|
|
1. **부모 컨테이너의 높이가 확정되지 않음**: `h-full`은 부모가 명시적인 높이를 가져야만 작동
|
||
|
|
2. **`minHeight: auto` 기본값**: Flex item은 콘텐츠 크기만큼 늘어나려고 함
|
||
|
|
3. **`overflow` 속성 누락**: 부모가 `overflow: hidden`이 없으면 자식이 부모를 밀어냄
|
||
|
|
4. **`display: flex` 누락**: Flex container가 명시적으로 선언되지 않음
|
||
|
|
|
||
|
|
## 2. 디버깅 프로세스
|
||
|
|
|
||
|
|
### 단계 1: 시각적 디버깅 (컬러 테두리)
|
||
|
|
|
||
|
|
문제가 발생한 컴포넌트에 **컬러 테두리**를 추가하여 각 레이어의 실제 크기를 확인:
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// 최상위 컨테이너 (빨간색)
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
display: "flex",
|
||
|
|
flexDirection: "column",
|
||
|
|
height: "100%",
|
||
|
|
border: "3px solid red", // 🔍 디버그
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{/* 헤더 (파란색) */}
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
flexShrink: 0,
|
||
|
|
height: "64px",
|
||
|
|
border: "3px solid blue", // 🔍 디버그
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
헤더
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 스크롤 영역 (초록색) */}
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
flex: 1,
|
||
|
|
minHeight: 0,
|
||
|
|
overflowY: "auto",
|
||
|
|
border: "3px solid green", // 🔍 디버그
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
콘텐츠
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
**브라우저에서 확인할 사항:**
|
||
|
|
|
||
|
|
- 🔴 빨간색 테두리가 화면 전체 높이를 차지하는가?
|
||
|
|
- 🔵 파란색 테두리가 고정된 높이를 유지하는가?
|
||
|
|
- 🟢 초록색 테두리가 남은 공간을 차지하는가?
|
||
|
|
|
||
|
|
### 단계 2: 부모 체인 추적
|
||
|
|
|
||
|
|
스크롤이 작동하지 않으면 **부모 컨테이너부터 역순으로 추적**:
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// ❌ 문제 예시
|
||
|
|
<div className="flex flex-col"> {/* 높이가 확정되지 않음 */}
|
||
|
|
<div className="flex-1"> {/* flex-1이 작동하지 않음 */}
|
||
|
|
<ComponentWithScroll /> {/* 스크롤 실패 */}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
// ✅ 해결
|
||
|
|
<div className="flex flex-col h-screen"> {/* 높이 확정 */}
|
||
|
|
<div className="flex-1 overflow-hidden"> {/* overflow 제한 */}
|
||
|
|
<ComponentWithScroll /> {/* 스크롤 성공 */}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
### 단계 3: 개발자 도구로 Computed Style 확인
|
||
|
|
|
||
|
|
브라우저 개발자 도구에서 확인:
|
||
|
|
|
||
|
|
1. **Height**: `auto`가 아닌 구체적인 px 값이 있는가?
|
||
|
|
2. **Display**: `flex`가 제대로 적용되었는가?
|
||
|
|
3. **Overflow**: `overflow-y: auto` 또는 `scroll`이 적용되었는가?
|
||
|
|
4. **Min-height**: `minHeight: 0`이 적용되었는가? (Flex item의 경우)
|
||
|
|
|
||
|
|
## 3. 해결 패턴
|
||
|
|
|
||
|
|
### 패턴 A: 최상위 Fixed/Absolute 컨테이너
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// 페이지 레벨 (예: dataflow/page.tsx)
|
||
|
|
<div className="fixed inset-0 z-50 bg-background">
|
||
|
|
<div className="flex h-full flex-col">
|
||
|
|
{/* 헤더 (고정) */}
|
||
|
|
<div className="flex items-center gap-4 border-b bg-background p-4">
|
||
|
|
헤더
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 에디터 (flex-1) */}
|
||
|
|
<div className="flex-1 overflow-hidden">
|
||
|
|
{" "}
|
||
|
|
{/* ⚠️ overflow-hidden 필수! */}
|
||
|
|
<FlowEditor />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
**핵심 포인트:**
|
||
|
|
|
||
|
|
- `fixed inset-0`: 뷰포트 전체 차지
|
||
|
|
- `flex h-full flex-col`: Flex column 레이아웃
|
||
|
|
- `flex-1 overflow-hidden`: 자식이 부모를 넘지 못하게 제한
|
||
|
|
|
||
|
|
### 패턴 B: 중첩된 Flex 컨테이너
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// 컴포넌트 레벨 (예: FlowEditor.tsx)
|
||
|
|
<div
|
||
|
|
className="flex h-full w-full"
|
||
|
|
style={{ height: "100%", overflow: "hidden" }} // ⚠️ 인라인 스타일로 강제
|
||
|
|
>
|
||
|
|
{/* 좌측 사이드바 */}
|
||
|
|
<div className="h-full w-[300px] border-r bg-white">사이드바</div>
|
||
|
|
|
||
|
|
{/* 중앙 캔버스 */}
|
||
|
|
<div className="relative flex-1">캔버스</div>
|
||
|
|
|
||
|
|
{/* 우측 속성 패널 */}
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
height: "100%",
|
||
|
|
width: "350px",
|
||
|
|
display: "flex", // ⚠️ Flex 컨테이너 명시
|
||
|
|
flexDirection: "column",
|
||
|
|
}}
|
||
|
|
className="border-l bg-white"
|
||
|
|
>
|
||
|
|
<PropertiesPanel />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
**핵심 포인트:**
|
||
|
|
|
||
|
|
- 인라인 스타일 `height: '100%'`: Tailwind보다 우선순위 높음
|
||
|
|
- `display: "flex"`: Flex 컨테이너 명시
|
||
|
|
- `overflow: 'hidden'`: 자식 크기 제한
|
||
|
|
|
||
|
|
### 패턴 C: 스크롤 가능 영역
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// 스크롤 영역 (예: PropertiesPanel.tsx)
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
display: "flex",
|
||
|
|
flexDirection: "column",
|
||
|
|
height: "100%",
|
||
|
|
width: "100%",
|
||
|
|
overflow: "hidden", // ⚠️ 최상위는 overflow hidden
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{/* 헤더 (고정) */}
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
flexShrink: 0, // ⚠️ 축소 방지
|
||
|
|
height: "64px", // ⚠️ 명시적 높이
|
||
|
|
}}
|
||
|
|
className="flex items-center justify-between border-b bg-white p-4"
|
||
|
|
>
|
||
|
|
헤더
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 스크롤 영역 */}
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
flex: 1, // ⚠️ 남은 공간 차지
|
||
|
|
minHeight: 0, // ⚠️ 핵심! Flex item 축소 허용
|
||
|
|
overflowY: "auto", // ⚠️ 세로 스크롤
|
||
|
|
overflowX: "hidden", // ⚠️ 가로 스크롤 방지
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{/* 실제 콘텐츠 */}
|
||
|
|
<PropertiesContent />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
**핵심 포인트:**
|
||
|
|
|
||
|
|
- `flexShrink: 0`: 헤더가 축소되지 않도록 고정
|
||
|
|
- `minHeight: 0`: **가장 중요!** Flex item이 축소되도록 허용
|
||
|
|
- `flex: 1`: 남은 공간 모두 차지
|
||
|
|
- `overflowY: 'auto'`: 콘텐츠가 넘치면 스크롤 생성
|
||
|
|
|
||
|
|
## 4. 왜 `minHeight: 0`이 필요한가?
|
||
|
|
|
||
|
|
### Flexbox의 기본 동작
|
||
|
|
|
||
|
|
```css
|
||
|
|
/* Flexbox의 기본값 */
|
||
|
|
.flex-item {
|
||
|
|
min-height: auto; /* 콘텐츠 크기만큼 늘어남 */
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**문제:**
|
||
|
|
|
||
|
|
- Flex item은 **콘텐츠 크기만큼 늘어나려고 함**
|
||
|
|
- `flex: 1`만으로는 **스크롤이 생기지 않고 부모를 밀어냄**
|
||
|
|
- 결과: 스크롤 영역이 화면 밖으로 넘어감
|
||
|
|
|
||
|
|
**해결:**
|
||
|
|
|
||
|
|
```css
|
||
|
|
.flex-item {
|
||
|
|
flex: 1;
|
||
|
|
min-height: 0; /* 축소 허용 → 스크롤 발생 */
|
||
|
|
overflow-y: auto;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 5. Tailwind vs 인라인 스타일
|
||
|
|
|
||
|
|
### 언제 인라인 스타일을 사용하는가?
|
||
|
|
|
||
|
|
**Tailwind가 작동하지 않을 때:**
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// ❌ Tailwind가 작동하지 않음
|
||
|
|
<div className="flex flex-col h-full">
|
||
|
|
|
||
|
|
// ✅ 인라인 스타일로 강제
|
||
|
|
<div
|
||
|
|
className="flex flex-col"
|
||
|
|
style={{ height: '100%', overflow: 'hidden' }}
|
||
|
|
>
|
||
|
|
```
|
||
|
|
|
||
|
|
**이유:**
|
||
|
|
|
||
|
|
1. **CSS 특이성**: 인라인 스타일이 가장 높은 우선순위
|
||
|
|
2. **동적 계산**: 브라우저가 직접 해석
|
||
|
|
3. **디버깅 쉬움**: 개발자 도구에서 바로 확인 가능
|
||
|
|
|
||
|
|
## 6. 체크리스트
|
||
|
|
|
||
|
|
스크롤 문제 발생 시 확인할 사항:
|
||
|
|
|
||
|
|
### 레이아웃 체크
|
||
|
|
|
||
|
|
- [ ] 최상위 컨테이너: `fixed` 또는 `absolute`로 높이 확정
|
||
|
|
- [ ] 부모: `flex flex-col h-full`
|
||
|
|
- [ ] 중간 컨테이너: `flex-1 overflow-hidden`
|
||
|
|
- [ ] 스크롤 컨테이너 부모: `display: flex, flexDirection: column, height: 100%`
|
||
|
|
|
||
|
|
### 스크롤 영역 체크
|
||
|
|
|
||
|
|
- [ ] 헤더: `flexShrink: 0` + 명시적 높이
|
||
|
|
- [ ] 스크롤 영역: `flex: 1, minHeight: 0, overflowY: auto`
|
||
|
|
- [ ] 콘텐츠: 자연스러운 높이 (height 제약 없음)
|
||
|
|
|
||
|
|
### 디버깅 체크
|
||
|
|
|
||
|
|
- [ ] 컬러 테두리로 각 레이어의 크기 확인
|
||
|
|
- [ ] 개발자 도구로 Computed Style 확인
|
||
|
|
- [ ] 부모 체인을 역순으로 추적
|
||
|
|
- [ ] `minHeight: 0` 적용 확인
|
||
|
|
|
||
|
|
## 7. 일반적인 실수
|
||
|
|
|
||
|
|
### 실수 1: 부모의 높이 미확정
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// ❌ 부모의 높이가 auto
|
||
|
|
<div className="flex flex-col">
|
||
|
|
<div className="flex-1">
|
||
|
|
<ScrollComponent /> {/* 작동 안 함 */}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
// ✅ 부모의 높이 확정
|
||
|
|
<div className="flex flex-col h-screen">
|
||
|
|
<div className="flex-1 overflow-hidden">
|
||
|
|
<ScrollComponent /> {/* 작동 */}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
### 실수 2: overflow-hidden 누락
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// ❌ overflow-hidden 없음
|
||
|
|
<div className="flex-1">
|
||
|
|
<ScrollComponent /> {/* 부모를 밀어냄 */}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
// ✅ overflow-hidden 추가
|
||
|
|
<div className="flex-1 overflow-hidden">
|
||
|
|
<ScrollComponent /> {/* 제한됨 */}
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
### 실수 3: minHeight: 0 누락
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// ❌ minHeight: 0 없음
|
||
|
|
<div style={{ flex: 1, overflowY: 'auto' }}>
|
||
|
|
{/* 스크롤 안 됨 */}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
// ✅ minHeight: 0 추가
|
||
|
|
<div style={{ flex: 1, minHeight: 0, overflowY: 'auto' }}>
|
||
|
|
{/* 스크롤 됨 */}
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
### 실수 4: display: flex 누락
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// ❌ Flex 컨테이너 미지정
|
||
|
|
<div style={{ height: '100%', width: '350px' }}>
|
||
|
|
<PropertiesPanel /> {/* flex-1이 작동 안 함 */}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
// ✅ Flex 컨테이너 명시
|
||
|
|
<div style={{
|
||
|
|
height: '100%',
|
||
|
|
width: '350px',
|
||
|
|
display: 'flex',
|
||
|
|
flexDirection: 'column'
|
||
|
|
}}>
|
||
|
|
<PropertiesPanel /> {/* 작동 */}
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
## 8. 완전한 예시
|
||
|
|
|
||
|
|
### 전체 레이아웃 구조
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// 페이지 (dataflow/page.tsx)
|
||
|
|
<div className="fixed inset-0 z-50 bg-background">
|
||
|
|
<div className="flex h-full flex-col">
|
||
|
|
{/* 헤더 */}
|
||
|
|
<div className="flex items-center gap-4 border-b bg-background p-4">
|
||
|
|
헤더
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 에디터 */}
|
||
|
|
<div className="flex-1 overflow-hidden">
|
||
|
|
<FlowEditor />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
// 에디터 (FlowEditor.tsx)
|
||
|
|
<div
|
||
|
|
className="flex h-full w-full"
|
||
|
|
style={{ height: '100%', overflow: 'hidden' }}
|
||
|
|
>
|
||
|
|
{/* 사이드바 */}
|
||
|
|
<div className="h-full w-[300px] border-r">
|
||
|
|
사이드바
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 캔버스 */}
|
||
|
|
<div className="relative flex-1">
|
||
|
|
캔버스
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 속성 패널 */}
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
height: "100%",
|
||
|
|
width: "350px",
|
||
|
|
display: "flex",
|
||
|
|
flexDirection: "column",
|
||
|
|
}}
|
||
|
|
className="border-l bg-white"
|
||
|
|
>
|
||
|
|
<PropertiesPanel />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
// 속성 패널 (PropertiesPanel.tsx)
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
display: 'flex',
|
||
|
|
flexDirection: 'column',
|
||
|
|
height: '100%',
|
||
|
|
width: '100%',
|
||
|
|
overflow: 'hidden'
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{/* 헤더 */}
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
flexShrink: 0,
|
||
|
|
height: '64px'
|
||
|
|
}}
|
||
|
|
className="flex items-center justify-between border-b bg-white p-4"
|
||
|
|
>
|
||
|
|
헤더
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 스크롤 영역 */}
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
flex: 1,
|
||
|
|
minHeight: 0,
|
||
|
|
overflowY: 'auto',
|
||
|
|
overflowX: 'hidden'
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{/* 콘텐츠 */}
|
||
|
|
<PropertiesContent />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
## 9. 요약
|
||
|
|
|
||
|
|
### 핵심 원칙
|
||
|
|
|
||
|
|
1. **높이 확정**: 부모 체인의 모든 요소가 명시적인 높이를 가져야 함
|
||
|
|
2. **overflow 제어**: 중간 컨테이너는 `overflow-hidden`으로 자식 제한
|
||
|
|
3. **Flex 명시**: `display: flex` + `flexDirection: column` 명시
|
||
|
|
4. **minHeight: 0**: 스크롤 영역의 Flex item은 반드시 `minHeight: 0` 적용
|
||
|
|
5. **인라인 스타일**: Tailwind가 작동하지 않으면 인라인 스타일 사용
|
||
|
|
|
||
|
|
### 디버깅 순서
|
||
|
|
|
||
|
|
1. 🎨 **컬러 테두리** 추가로 시각적 확인
|
||
|
|
2. 🔍 **개발자 도구**로 Computed Style 확인
|
||
|
|
3. 🔗 **부모 체인** 역순으로 추적
|
||
|
|
4. ✅ **체크리스트** 항목 확인
|
||
|
|
5. 🔧 **패턴 적용** 및 테스트
|
||
|
|
|
||
|
|
### 최종 구조
|
||
|
|
|
||
|
|
```
|
||
|
|
페이지 (fixed inset-0)
|
||
|
|
└─ flex flex-col h-full
|
||
|
|
├─ 헤더 (고정)
|
||
|
|
└─ 컨테이너 (flex-1 overflow-hidden)
|
||
|
|
└─ 에디터 (height: 100%, overflow: hidden)
|
||
|
|
└─ flex row
|
||
|
|
└─ 패널 (display: flex, flexDirection: column)
|
||
|
|
└─ 패널 내부 (height: 100%)
|
||
|
|
├─ 헤더 (flexShrink: 0, height: 64px)
|
||
|
|
└─ 스크롤 (flex: 1, minHeight: 0, overflowY: auto)
|
||
|
|
```
|
||
|
|
|
||
|
|
## 10. 참고 자료
|
||
|
|
|
||
|
|
이 가이드는 다음 파일을 기반으로 작성되었습니다:
|
||
|
|
|
||
|
|
- [dataflow/page.tsx](<mdc:frontend/app/(main)/admin/dataflow/page.tsx>)
|
||
|
|
- [FlowEditor.tsx](mdc:frontend/components/dataflow/node-editor/FlowEditor.tsx)
|
||
|
|
- [PropertiesPanel.tsx](mdc:frontend/components/dataflow/node-editor/panels/PropertiesPanel.tsx)
|