document_cover
ทำไม minHeight: 0 ถึงจำเป็นสำหรับ ScrollArea ใน Nested Flexbox
flexboxcssmantinescrollareafrontendreacttypescriptlayouttimeline
2 ต.ค. 68 , 14:45
เมื่อใช้ ScrollArea ของ Mantine หรือ component ที่มีการ scroll ใน nested flexbox containers มักพบปัญหาที่ content ทะลุออกจาก container แม้จะใช้ flex: 1 แล้ว บทความนี้จะอธิบายสาเหตุของปัญหา และวิธีแก้ไขด้วย minHeight: 0 อย่างละเอียด พร้อมตัวอย่างการใช้งานจริง
เนื้อหาของเอกสารนี้ ใช้ AI ช่วยเขียนนะครับ อยากรู้ว่ามันจะทำได้รู้เรื่องแค่ไหน แต่เท่าที่ดูมัน อธิบายยืดเยื้อชะมัด อยากได้กระชับกว่านี้จัง สงสัยต้องปรับ prompt อีกบาน

ปัญหาที่พบบ่อย

  • ScrollArea ไม่ทำงาน content ทะลุออกมา
  • Scrollbar ไม่ปรากฏแม้ content ยาวเกิน
  • Container ขยายตาม content แทนที่จะถูกจำกัดโดย parent

ตัวอย่างโค้ดที่มีปัญหา

<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
  <div style={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
    <div style={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
      <ScrollArea style={{ flex: 1 }}>
        {/* Content ทะลุออกมา! */}
      </ScrollArea>
    </div>
  </div>
</div>

ต้นเหตุของปัญหา

Flexbox มี default value ที่เป็นต้นเหตุของปัญหา:

.flex-item {
  min-height: auto;  /* ไม่ใช่ 0! */
  min-width: auto;   /* ไม่ใช่ 0! */
}

ค่า auto หมายความว่า "ขนาดต่ำสุดต้องพอกับ content ภายใน" กล่าวคือ flex item จะไม่ยอมเล็กกว่าขนาดของ content แม้ว่า parent จะกำหนดขนาดไว้แล้วก็ตาม

ทำไมถึงเกิดปัญหา

1. Content ยาว 1000px อยู่ใน ScrollArea
   ↓
2. ScrollArea อยู่ใน flex container ที่มี min-height: auto
   ↓
3. Flex container นั้นจะ "ไม่ยอมเล็กกว่า 1000px" 
   เพราะต้อง "พอกับ content"
   ↓
4. แม้ parent กำหนด height: 500px
   แต่ flex container ก็ยังขยายเป็น 1000px
   ↓
5. ผลลัพธ์: ScrollArea ทะลุออกมา!

Parent Container (height: 500px)
┌─────────────────────────────┐
│                             │ ← ควรจะอยู่ในขอบเขตนี้
│  Flex Item                  │
│  (min-height: auto)         │
│  ┌─────────────────────┐    │
│  │ ScrollArea          │    │
│  │ Content 1000px      │    │
└──┤                     ├────┘
   │                     │ ← แต่ทะลุออกมา!
   │                     │
   │                     │
   └─────────────────────┘

ทำไมถึงไม่คิดว่าปัญหาอยู่ที่ min-height

  1. ไม่ค่อยมีคนรู้จัก- เอกสารส่วนใหญ่ไม่ได้เน้นเรื่องmin-height: auto
  1. คิดว่า flex: 1 พอแล้ว- หลายคนคิดว่าflex: 1ควรทำให้ยืดหดได้เอง
  1. ไม่มี error message- เพียงแค่ทะลุออกมา ไม่มีข้อความบอกว่าผิดตรงไหน
  1. เฉพาะ nested flex- ถ้าไม่ซ้อนหลายชั้น ปัญหาไม่เกิด

วิธีแก้ปัญหา

หลักการแก้ไข Override ค่า default ด้วย minHeight: 0 เพื่อบอกว่า: "ยอมเล็กได้ถึง 0px - ให้ถูกจำกัดขนาดโดย parent แทนที่จะขยายตาม content"

ตัวอย่างโค้ดที่แก้ไขแล้ว

<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
  <div style={{ 
    display: 'flex', 
    flexDirection: 'column', 
    flex: 1,
    minHeight: 0  // ✅ เพิ่มบรรทัดนี้
  }}>
    <div style={{ 
      display: 'flex', 
      flexDirection: 'column', 
      flex: 1,
      minHeight: 0  // ✅ เพิ่มบรรทัดนี้
    }}>
      <ScrollArea style={{ flex: 1, minHeight: 0 }}>
        {/* Content ทำงานถูกต้อง! */}
      </ScrollArea>
    </div>
  </div>
</div>

ตัวอย่างการตัดสินใจ

// Element A: ต้องใส่หรือไม่?
<div style={{ padding: '20px' }}>  // ❌ ไม่ต้องใส่ (ไม่ได้เป็น flex item)

// Element B: ต้องใส่หรือไม่?
<div style={{ 
  display: 'flex',
  height: '100vh'
}}>  // ❌ ไม่ต้องใส่ (เป็น root container)

// Element C: ต้องใส่หรือไม่?
<div style={{ 
  display: 'flex',
  flexDirection: 'column',
  flex: 1,
  minHeight: 0  // ✅ ต้องใส่! (ตรงเงื่อนไขทั้งหมด)
}}>

กรณี Mixed Flex Directions

เมื่อมีทั้ง row และ column ผสมกัน:

<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
  
  {/* Level 1: Parent = column → ใส่ minHeight: 0 */}
  <div style={{ 
    display: 'flex',              // ← row (default)
    flex: 1, 
    minHeight: 0                  // ✅
  }}>
    <div>Sidebar</div>
    
    {/* Level 2: Parent = row → ใส่ minWidth: 0 */}
    <div style={{ 
      display: 'flex', 
      flexDirection: 'column',    // ← column
      flex: 1, 
      minWidth: 0                 // ✅
    }}>
      <div>Header</div>
      
      {/* Level 3: Parent = column → ใส่ minHeight: 0 */}
      <div style={{ 
        display: 'flex', 
        flexDirection: 'column',  // ← column
        flex: 1, 
        minHeight: 0              // ✅
      }}>
        <ScrollArea style={{ flex: 1, minHeight: 0 }}>
          Content
        </ScrollArea>
      </div>
    </div>
  </div>
</div>

กฎสำคัญ: มองที่ flexDirection ของ parent ไม่ใช่ตัว element เอง