Guest Adventurer 🥉 Lv.1
🪙 50
🔷 10
← กลับคลังเนื้อหา
Article · programming

HTML & CSS EP17: CSS Grid Layout — สร้างเลย์เอาต์เว็บไซต์แบบหลายคอลัมน์อย่างมืออาชีพ

HTML & CSS

บทเรียน HTML และ CSS ตอนที่ 17 สอน CSS Grid Layout ตั้งแต่แนวคิด Grid Container และ Grid Items การกำหนดคอลัมน์และแถวด้วย grid-template-columns และ grid-template-rows การใช้ fr, repeat(), minmax(), auto-fit, auto-fill, gap, grid-column และ grid-area พร้อมตัวอย่างหน้า Dashboard และ Responsive Gallery ที่สามารถคัดลอกไปทดลองใช้งานได้ทันที

HTML & CSS EP17: CSS Grid Layout

เว็บไซต์สมัยใหม่มักมีโครงสร้างที่ประกอบด้วยหลายส่วน เช่น แถบนำทาง พื้นที่แสดงเนื้อหา การ์ดสินค้า รายการบทความ และแถบข้อมูลด้านข้าง หากใช้ `margin`, `float` หรือกำหนดความกว้างให้แต่ละกล่องด้วยตัวเลขตายตัวเพียงอย่างเดียว การดูแลเลย์เอาต์ที่ซับซ้อนและรองรับหลายขนาดหน้าจออาจกลายเป็นเรื่องยุ่งยาก **CSS Grid Layout** เป็นระบบจัดวางองค์ประกอบแบบสองมิติ ซึ่งสามารถควบคุมทั้งแถวและคอลัมน์ได้ในเวลาเดียวกัน เหมาะสำหรับหน้า Dashboard, Gallery, หน้ารายการสินค้า และเลย์เอาต์หลักของเว็บไซต์ ส่วน Flexbox เหมาะกับการจัดองค์ประกอบตามแนวหลักหนึ่งมิติ เช่น ปุ่มเรียงแนวนอนหรือเมนูนำทาง ทั้งสองระบบสามารถใช้ร่วมกันในหน้าเดียวได้อย่างมีประสิทธิภาพ
🎯 เป้าหมายการเรียนรู้หลังเรียนจบ คุณจะสามารถสร้าง Grid Container กำหนดจำนวนและขนาดคอลัมน์ จัดการระยะห่างด้วย `gap` ขยายองค์ประกอบให้ครอบคลุมหลายช่อง ใช้ `repeat()`, `minmax()` และ `fr` สร้างเลย์เอาต์ที่ยืดหยุ่น รวมถึงออกแบบหน้าเว็บที่ปรับตามขนาดหน้าจอได้โดยไม่ต้องกำหนดความกว้างตายตัวให้ทุกการ์ด

1. ทำความรู้จัก Grid Container และ Grid Items

การเริ่มต้นใช้งาน CSS Grid ต้องกำหนด `display: grid` ให้กับองค์ประกอบแม่ องค์ประกอบลูกโดยตรงของกล่องนั้นจะกลายเป็น Grid Items ซึ่งสามารถจัดตำแหน่งลงในช่องตารางได้ ตัวอย่างเช่น หากมีการ์ดทั้งหมด 6 ใบ และต้องการแสดง 3 คอลัมน์ เราสามารถกำหนด `grid-template-columns: repeat(3, 1fr)` ได้ โดย `repeat(3, 1fr)` หมายถึงสร้างคอลัมน์ขนาดเท่ากันจำนวน 3 คอลัมน์ ส่วน `gap` ใช้กำหนดระยะห่างระหว่างแถวและคอลัมน์
<!DOCTYPE html>
<html lang="th">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>CSS Grid พื้นฐาน</title>
  <style>
    body {
      margin: 0;
      padding: 24px;
      font-family: system-ui, sans-serif;
      background: #f1f5f9;
    }

    .grid {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 16px;
    }

    .item {
      display: grid;
      place-items: center;
      min-height: 100px;
      color: white;
      background: #2563eb;
      border-radius: 12px;
      font-size: 24px;
      font-weight: 700;
    }
  </style>
</head>
<body>
  <h1>CSS Grid พื้นฐาน</h1>
  <div class="grid">
    <div class="item">1</div>
    <div class="item">2</div>
    <div class="item">3</div>
    <div class="item">4</div>
    <div class="item">5</div>
    <div class="item">6</div>
  </div>
</body>
</html>

คุณสมบัติพื้นฐานที่ควรรู้

คุณสมบัติหน้าที่ตัวอย่าง
display: gridเปิดใช้งาน Grid Layoutdisplay: grid;
grid-template-columnsกำหนดรูปแบบและขนาดคอลัมน์grid-template-columns: 1fr 2fr;
grid-template-rowsกำหนดรูปแบบและขนาดแถวgrid-template-rows: 100px auto;
gapกำหนดระยะห่างระหว่างช่องgap: 16px;
grid-columnกำหนดช่วงคอลัมน์ของ Grid Itemgrid-column: span 2;
grid-rowกำหนดช่วงแถวของ Grid Itemgrid-row: span 2;

2. เข้าใจหน่วย fr และการแบ่งพื้นที่

หน่วย `fr` ย่อมาจาก fractional unit ใช้แบ่งพื้นที่ว่างที่มีอยู่ภายใน Grid Container ตามสัดส่วนที่กำหนด ตัวอย่าง `grid-template-columns: 1fr 1fr` แบ่งพื้นที่เป็นสองส่วนเท่า ๆ กัน ส่วน `1fr 2fr` หมายถึงแบ่งพื้นที่ว่างตามสัดส่วนหนึ่งต่อสอง หน่วย `fr` ไม่ได้หมายความว่าความกว้างของคอลัมน์จะเท่ากับสัดส่วนดังกล่าวของความกว้างทั้งหมดในทุกกรณี เพราะการคำนวณยังขึ้นกับพื้นที่ที่เหลือหลังหักขนาดคงที่ ระยะห่าง และข้อจำกัดขั้นต่ำของแต่ละแทร็กด้วย
.layout-equal {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

.layout-sidebar {
  display: grid;
  grid-template-columns: 240px 1fr;
  gap: 24px;
}

.layout-proportional {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: 16px;
}
จำง่าย ๆ`1fr 1fr` ใช้แบ่งพื้นที่ว่างเป็นสองส่วนใกล้เคียงกัน ส่วน `240px 1fr` เหมาะกับเลย์เอาต์ที่มีแถบด้านข้างกว้างคงที่และพื้นที่เนื้อหาหลักยืดตามขนาดหน้าจอ

3. ใช้ repeat() เพื่อลดการเขียน CSS ซ้ำ

หากต้องการสร้างหลายคอลัมน์ที่มีรูปแบบเหมือนกัน การเขียน `1fr 1fr 1fr 1fr` ทำได้ แต่เมื่อจำนวนคอลัมน์เพิ่มขึ้น โค้ดจะอ่านยากขึ้น ฟังก์ชัน `repeat()` ช่วยย่อรูปแบบนี้ให้กระชับ เช่น `repeat(4, 1fr)` สร้างคอลัมน์ขนาดเท่ากัน 4 คอลัมน์ `repeat()` สามารถใช้กับขนาดแบบอื่นได้เช่นกัน เช่น `repeat(3, 200px)` สร้างคอลัมน์ขนาด 200 พิกเซลจำนวน 3 คอลัมน์ แต่ควรพิจารณาความกว้างของหน้าจอด้วย เพราะการกำหนดขนาดคงที่อาจทำให้เนื้อหาล้นบนอุปกรณ์ขนาดเล็ก
.grid-four {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}

.grid-fixed {
  display: grid;
  grid-template-columns: repeat(3, 200px);
  gap: 16px;
}

4. สร้างเลย์เอาต์ยืดหยุ่นด้วย minmax()

`minmax(min, max)` กำหนดช่วงขนาดขั้นต่ำและขั้นสูงของแทร็ก Grid เช่น `minmax(220px, 1fr)` หมายถึงให้คอลัมน์มีขนาดอย่างน้อย 220 พิกเซล และสามารถขยายเพื่อใช้พื้นที่ว่างที่เหลือได้ เมื่อนำไปใช้ร่วมกับ `repeat()` และ `auto-fit` จะช่วยสร้าง Gallery หรือรายการการ์ดที่ปรับจำนวนคอลัมน์ตามพื้นที่ที่มี โดยไม่จำเป็นต้องเขียน Media Query สำหรับทุกขนาดหน้าจอ
.card-grid {
  display: grid;
  grid-template-columns: repeat(
    auto-fit,
    minmax(220px, 1fr)
  );
  gap: 20px;
}

.card {
  min-width: 0;
  padding: 20px;
  border: 1px solid #e2e8f0;
  border-radius: 16px;
  background: #ffffff;
}

auto-fit กับ auto-fill ต่างกันอย่างไร

ทั้ง `auto-fit` และ `auto-fill` ช่วยสร้างจำนวนแทร็กตามพื้นที่ที่มี แต่มีพฤติกรรมต่างกันเมื่อมีพื้นที่สำหรับคอลัมน์เพิ่มเติมแต่ไม่มี Grid Item มาวาง `auto-fill` พยายามคงแทร็กที่สามารถจัดวางได้ไว้ แม้บางแทร็กจะยังว่างอยู่ ส่วน `auto-fit` จะยุบแทร็กว่างที่ไม่มีรายการวางอยู่ เพื่อให้แทร็กที่มีรายการสามารถขยายใช้พื้นที่ที่เหลือได้ ทั้งนี้ผลลัพธ์ขึ้นกับจำนวนรายการ ขนาดขั้นต่ำของแทร็ก และพื้นที่ของคอนเทนเนอร์ด้วย
รูปแบบลักษณะการทำงานกรณีที่เหมาะสม
repeat(3, 1fr)สร้าง 3 คอลัมน์ตามจำนวนที่กำหนดเลย์เอาต์ที่ต้องการจำนวนคอลัมน์แน่นอน
repeat(auto-fit, minmax(220px, 1fr))คอลัมน์ปรับตามพื้นที่และรายการที่มีการ์ดสินค้าและ Gallery ที่ต้องการให้คอลัมน์ขยาย
repeat(auto-fill, minmax(220px, 1fr))สร้างแทร็กตามพื้นที่ที่รองรับ แม้บางแทร็กว่างเลย์เอาต์ที่ต้องการคงโครงสร้างแทร็กว่างไว้

5. จัดระยะห่างและตำแหน่งด้วย gap และ alignment

`gap` ใช้กำหนดระยะห่างระหว่างแถวและคอลัมน์โดยไม่ต้องเพิ่ม Margin ให้กับ Grid Item ทุกชิ้น สามารถระบุค่าเดียวเพื่อใช้กับทั้งสองทิศทาง หรือกำหนดแยกเป็น `row-gap` และ `column-gap` ได้ ส่วน `justify-items` ใช้ควบคุมการจัดวาง Grid Items ภายในช่องตามแนวนอน และ `align-items` ใช้จัดวางตามแนวตั้งในบริบทปกติของ Grid การจัดตำแหน่งระดับทั้ง Grid Container ยังมีคุณสมบัติอย่าง `justify-content` และ `align-content` ซึ่งเกี่ยวข้องกับการจัดกลุ่มแทร็กภายในพื้นที่ที่เหลือ
.grid-layout {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 24px;
  row-gap: 16px;
  align-items: stretch;
}

.grid-centered-items {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  justify-items: center;
  align-items: center;
  gap: 16px;
}

.grid-centered-content {
  display: grid;
  grid-template-columns: repeat(3, 180px);
  justify-content: center;
  gap: 16px;
}
ข้อสังเกต`justify-items` และ `align-items` จัดตำแหน่งรายการภายในช่อง ขณะที่ `justify-content` และ `align-content` ใช้จัดวางแทร็กของ Grid ภายในพื้นที่คอนเทนเนอร์ ความแตกต่างนี้ช่วยให้แก้ปัญหาการจัดตำแหน่งได้ตรงจุด

6. ให้รายการครอบคลุมหลายคอลัมน์หรือหลายแถว

บางครั้งการ์ดหนึ่งใบต้องมีขนาดใหญ่กว่ารายการอื่น เช่น การ์ดสรุปภาพรวมใน Dashboard หรือภาพเด่นใน Gallery สามารถใช้ `grid-column` และ `grid-row` เพื่อกำหนดช่วงที่รายการจะครอบคลุมได้ `grid-column: span 2` ทำให้รายการครอบคลุมพื้นที่สองคอลัมน์ตามการจัดวางของ Grid ส่วน `grid-column: 1 / 3` ระบุเส้นเริ่มต้นและเส้นสิ้นสุดของคอลัมน์ โดยเส้นสิ้นสุดเป็นตำแหน่งขอบเขต ไม่ใช่หมายเลขคอลัมน์ที่ต้องการนับรวมเข้าไป
.dashboard-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}

.summary-card {
  grid-column: span 2;
}

.tall-card {
  grid-row: span 2;
}

.feature-card {
  grid-column: 1 / 3;
}

7. ใช้ grid-template-areas เพื่ออ่านโครงสร้างหน้าเว็บได้ง่าย

สำหรับเลย์เอาต์หลักของเว็บไซต์ เช่น Header, Sidebar, Main และ Footer สามารถตั้งชื่อพื้นที่ด้วย `grid-template-areas` เพื่อให้โครงสร้าง CSS อ่านคล้ายแผนผังได้ จากนั้นกำหนด `grid-area` ให้กับแต่ละองค์ประกอบ แนวทางนี้เหมาะกับหน้า Dashboard หรือหน้าแอดมินที่มีโครงสร้างค่อนข้างชัดเจน และยังสามารถเปลี่ยนรูปแบบพื้นที่ใน Media Query เพื่อปรับการจัดวางบนโทรศัพท์ได้
.page {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
  gap: 16px;
}

.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }

@media (max-width: 700px) {
  .page {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "header"
      "main"
      "sidebar"
      "footer";
  }
}

8. ออกแบบ Responsive Grid โดยไม่ยึดติดกับขนาดหน้าจอเดียว

เลย์เอาต์ที่ดีควรคำนึงถึงความกว้างของเนื้อหา ไม่ใช่เพียงชื่ออุปกรณ์ เช่น โทรศัพท์ แท็บเล็ต หรือคอมพิวเตอร์ ควรกำหนดขนาดขั้นต่ำที่การ์ดต้องการเพื่อให้อ่านเนื้อหาได้ แล้วปล่อยให้ Grid เลือกจำนวนคอลัมน์ที่เหมาะสมตามพื้นที่ การใช้ `minmax(0, 1fr)` กับคอลัมน์ของเนื้อหาหลักช่วยลดปัญหา Grid Item ที่มีข้อความยาวหรือองค์ประกอบกว้างเกินช่อง นอกจากนี้ `min-width: 0` บนรายการบางประเภทอาจช่วยให้ข้อความและเนื้อหาภายในยอมย่อขนาดตามพื้นที่ได้
.product-grid {
  display: grid;
  grid-template-columns: repeat(
    auto-fit,
    minmax(min(100%, 240px), 1fr)
  );
  gap: 20px;
}

.product-card {
  min-width: 0;
  overflow-wrap: anywhere;
}

.product-card img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: 12px;
}

.product-card h3 {
  overflow-wrap: anywhere;
}

9. ตัวอย่างโปรเจกต์: Responsive Product Gallery

ตัวอย่างต่อไปนี้เป็นหน้า Product Gallery แบบเต็มหน้า มีหัวข้อหลัก แถบแนะนำสินค้า และการ์ดสินค้าที่ปรับจำนวนคอลัมน์ตามพื้นที่โดยอัตโนมัติ โค้ดใช้ CSS Grid ร่วมกับ `repeat()`, `minmax()` และ `auto-fit` โดยไม่ต้องเขียน Media Query สำหรับจำนวนคอลัมน์แต่ละขนาดหน้าจอ คุณสามารถบันทึกโค้ดเป็น `index.html` แล้วเปิดในเบราว์เซอร์เพื่อทดลองปรับขนาดหน้าต่าง ลองเปลี่ยนค่าขั้นต่ำของการ์ดจาก `220px` เป็น `260px` แล้วสังเกตว่าจำนวนคอลัมน์เปลี่ยนไปอย่างไร
<!DOCTYPE html>
<html lang="th">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Responsive Product Gallery | EP17</title>
  <style>
    :root {
      --primary: #2563eb;
      --primary-dark: #1d4ed8;
      --page: #f1f5f9;
      --surface: #ffffff;
      --text: #1e293b;
      --muted: #64748b;
      --border: #e2e8f0;
      --radius: 16px;
      --space: 20px;
    }

    * { box-sizing: border-box; }

    body {
      margin: 0;
      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      line-height: 1.6;
      color: var(--text);
      background: var(--page);
    }

    .container {
      width: min(1120px, calc(100% - 32px));
      margin-inline: auto;
      padding-block: 32px 48px;
    }

    .header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      flex-wrap: wrap;
      gap: 16px;
      margin-bottom: 28px;
    }

    .brand {
      display: flex;
      align-items: center;
      gap: 12px;
    }

    .brand-mark {
      display: grid;
      place-items: center;
      width: 46px;
      height: 46px;
      color: white;
      background: var(--primary);
      border-radius: 14px;
      font-size: 23px;
      font-weight: 800;
    }

    h1 { margin: 0; font-size: clamp(23px, 4vw, 32px); line-height: 1.25; }
    .subtitle { margin: 5px 0 0; color: var(--muted); font-size: 14px; }

    .header-link {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      min-height: 42px;
      padding: 10px 16px;
      color: white;
      background: var(--primary);
      border-radius: 10px;
      text-decoration: none;
      font-size: 14px;
      font-weight: 700;
    }

    .header-link:hover { background: var(--primary-dark); }
    .header-link:focus-visible { outline: 3px solid var(--primary); outline-offset: 3px; }

    .intro {
      margin-bottom: 24px;
      padding: clamp(20px, 4vw, 32px);
      border: 1px solid var(--border);
      border-radius: var(--radius);
      background: var(--surface);
    }

    .intro h2 { margin: 0 0 8px; font-size: clamp(20px, 3vw, 26px); }
    .intro p { max-width: 720px; margin: 0; color: var(--muted); }

    .section-heading {
      display: flex;
      align-items: baseline;
      justify-content: space-between;
      flex-wrap: wrap;
      gap: 8px;
      margin: 28px 0 16px;
    }

    .section-heading h2 { margin: 0; font-size: 21px; }
    .section-heading span { color: var(--muted); font-size: 13px; }

    .product-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(min(100%, 230px), 1fr));
      gap: var(--space);
    }

    .product-card {
      min-width: 0;
      display: flex;
      flex-direction: column;
      overflow: hidden;
      border: 1px solid var(--border);
      border-radius: var(--radius);
      background: var(--surface);
      box-shadow: 0 8px 24px rgb(15 23 42 / 5%);
      transition: transform 180ms ease, box-shadow 180ms ease;
    }

    .product-card:hover {
      transform: translateY(-4px);
      box-shadow: 0 14px 32px rgb(15 23 42 / 10%);
    }

    .product-visual {
      display: grid;
      place-items: center;
      min-height: 150px;
      background: linear-gradient(135deg, #dbeafe, #ede9fe);
      font-size: 52px;
    }

    .product-content {
      display: flex;
      flex: 1;
      flex-direction: column;
      align-items: flex-start;
      padding: 20px;
    }

    .category {
      display: inline-block;
      padding: 4px 9px;
      color: #1d4ed8;
      background: #dbeafe;
      border-radius: 999px;
      font-size: 11px;
      font-weight: 750;
    }

    .product-card h3 { margin: 12px 0 6px; font-size: 18px; overflow-wrap: anywhere; }
    .product-card p { margin: 0 0 18px; color: var(--muted); font-size: 14px; }

    .product-bottom {
      display: flex;
      align-items: center;
      justify-content: space-between;
      flex-wrap: wrap;
      gap: 12px;
      width: 100%;
      margin-top: auto;
    }

    .price { font-size: 19px; font-weight: 800; }
    .details {
      padding: 8px 12px;
      border: 1px solid var(--border);
      border-radius: 9px;
      color: var(--text);
      background: var(--surface);
      font: inherit;
      font-size: 12px;
      font-weight: 700;
      cursor: pointer;
    }

    .details:hover { border-color: var(--primary); color: var(--primary); }
    .details:focus-visible { outline: 3px solid var(--primary); outline-offset: 3px; }

    .message {
      display: none;
      margin-top: 20px;
      padding: 16px;
      border: 1px solid #93c5fd;
      border-radius: 12px;
      color: #1e3a8a;
      background: #eff6ff;
    }
    .message.is-visible { display: block; }

    .footer {
      margin-top: 36px;
      padding-top: 20px;
      border-top: 1px solid var(--border);
      color: var(--muted);
      text-align: center;
      font-size: 12px;
    }

    @media (prefers-reduced-motion: reduce) {
      *, *::before, *::after {
        scroll-behavior: auto !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
      }
    }
  </style>
</head>
<body>
  <main class="container">
    <header class="header">
      <div class="brand">
        <div class="brand-mark" aria-hidden="true">G</div>
        <div>
          <h1>Grid Market</h1>
          <p class="subtitle">ตัวอย่าง Responsive Product Gallery ด้วย CSS Grid</p>
        </div>
      </div>
      <a class="header-link" href="#products">เลือกดูสินค้า ↓</a>
    </header>

    <section class="intro" aria-labelledby="intro-title">
      <h2 id="intro-title">ออกแบบให้ยืดหยุ่นทุกหน้าจอ</h2>
      <p>การ์ดสินค้าด้านล่างจัดวางด้วย CSS Grid โดยใช้ auto-fit และ minmax() จำนวนคอลัมน์จะเปลี่ยนตามพื้นที่ว่างโดยอัตโนมัติ ลองปรับความกว้างของหน้าต่างเพื่อดูผลลัพธ์</p>
    </section>

    <div class="section-heading" id="products">
      <h2>สินค้าแนะนำ</h2>
      <span>ทั้งหมด 6 รายการ · Demo UI</span>
    </div>

    <section class="product-grid" aria-label="รายการสินค้า">
      <article class="product-card">
        <div class="product-visual" aria-hidden="true">🎧</div>
        <div class="product-content">
          <span class="category">อุปกรณ์เสริม</span>
          <h3>Studio Headphones</h3>
          <p>หูฟังสำหรับการทำงานและฟังเพลงในชีวิตประจำวัน</p>
          <div class="product-bottom">
            <span class="price">฿1,290</span>
            <button class="details" type="button" data-name="Studio Headphones">รายละเอียด</button>
          </div>
        </div>
      </article>

      <article class="product-card">
        <div class="product-visual" aria-hidden="true">⌨️</div>
        <div class="product-content">
          <span class="category">อุปกรณ์คอมพิวเตอร์</span>
          <h3>Mechanical Keyboard</h3>
          <p>คีย์บอร์ดสำหรับนักพัฒนาและผู้ที่ชอบปรับแต่งโต๊ะทำงาน</p>
          <div class="product-bottom">
            <span class="price">฿2,490</span>
            <button class="details" type="button" data-name="Mechanical Keyboard">รายละเอียด</button>
          </div>
        </div>
      </article>

      <article class="product-card">
        <div class="product-visual" aria-hidden="true">🖱️</div>
        <div class="product-content">
          <span class="category">อุปกรณ์คอมพิวเตอร์</span>
          <h3>Wireless Mouse</h3>
          <p>เมาส์ไร้สายขนาดกะทัดรัดสำหรับทำงานได้สะดวกขึ้น</p>
          <div class="product-bottom">
            <span class="price">฿890</span>
            <button class="details" type="button" data-name="Wireless Mouse">รายละเอียด</button>
          </div>
        </div>
      </article>

      <article class="product-card">
        <div class="product-visual" aria-hidden="true">💻</div>
        <div class="product-content">
          <span class="category">อุปกรณ์ทำงาน</span>
          <h3>Portable Laptop Stand</h3>
          <p>ขาตั้งโน้ตบุ๊กช่วยจัดระดับหน้าจอให้เหมาะกับโต๊ะทำงาน</p>
          <div class="product-bottom">
            <span class="price">฿650</span>
            <button class="details" type="button" data-name="Portable Laptop Stand">รายละเอียด</button>
          </div>
        </div>
      </article>

      <article class="product-card">
        <div class="product-visual" aria-hidden="true">🔌</div>
        <div class="product-content">
          <span class="category">อุปกรณ์เสริม</span>
          <h3>USB-C Hub</h3>
          <p>เพิ่มพอร์ตเชื่อมต่อให้โน้ตบุ๊กสำหรับอุปกรณ์หลายชนิด</p>
          <div class="product-bottom">
            <span class="price">฿990</span>
            <button class="details" type="button" data-name="USB-C Hub">รายละเอียด</button>
          </div>
        </div>
      </article>

      <article class="product-card">
        <div class="product-visual" aria-hidden="true">📷</div>
        <div class="product-content">
          <span class="category">อุปกรณ์สร้างสรรค์</span>
          <h3>Webcam HD</h3>
          <p>กล้องเว็บแคมสำหรับประชุมออนไลน์และบันทึกวิดีโอ</p>
          <div class="product-bottom">
            <span class="price">฿1,590</span>
            <button class="details" type="button" data-name="Webcam HD">รายละเอียด</button>
          </div>
        </div>
      </article>
    </section>

    <div class="message" id="product-message" role="status" aria-live="polite"></div>

    <footer class="footer">
      HTML & CSS Learning Series · EP17: CSS Grid Layout
    </footer>
  </main>

  <script>
    const message = document.querySelector('#product-message');
    const detailButtons = document.querySelectorAll('.details');

    detailButtons.forEach((button) => {
      button.addEventListener('click', () => {
        const productName = button.dataset.name;
        message.textContent = 'คุณเลือกดูรายละเอียดสินค้า: ' + productName + ' (ตัวอย่างนี้เป็นหน้าสาธิต ยังไม่มีระบบสั่งซื้อจริง)';
        message.classList.add('is-visible');
      });
    });
  </script>
</body>
</html>

10. แนวทางตรวจสอบและแก้ปัญหา CSS Grid

อาการสาเหตุที่พบบ่อยแนวทางแก้ไข
คอลัมน์ล้นหน้าจอกำหนดความกว้างคงที่มากเกินไปหรือมีเนื้อหาขนาดใหญ่ใช้ `minmax(0, 1fr)`, ตรวจ `min-width` และใช้ `max-width: 100%` กับสื่อ
การ์ดมีความกว้างไม่เท่ากันใช้ค่าขนาดคอลัมน์ต่างกันหรือเนื้อหาภายในบังคับขนาดตรวจ `grid-template-columns` และข้อจำกัดขั้นต่ำของรายการ
มีช่องว่างมากผิดปกติกำหนด `gap`, `justify-content` หรือขนาดแทร็กไม่เหมาะสมตรวจระยะห่างและขนาดคอนเทนเนอร์
Grid Item ขยายเกินช่องข้อความยาวหรือองค์ประกอบภายในมีขนาดขั้นต่ำใหญ่ลองใช้ `min-width: 0` และ `overflow-wrap: anywhere`
เลย์เอาต์มือถือไม่เหมาะสมกำหนดจำนวนคอลัมน์ตายตัวโดยไม่ตรวจพื้นที่พิจารณา `auto-fit`, `minmax()` หรือ Media Query
พื้นที่ชื่อไม่ตรงกันชื่อใน `grid-template-areas` ไม่ตรงกับ `grid-area`ตรวจชื่อทุกพื้นที่และจำนวนช่องในแต่ละแถวให้สอดคล้องกัน

11. แบบฝึกหัดท้ายบท

แบบฝึกหัดที่ 1: สร้างการ์ด 4 คอลัมน์
สร้าง Grid Container ที่มีการ์ด 8 ใบ ใช้ `grid-template-columns: repeat(4, 1fr)` และกำหนด `gap: 16px` จากนั้นปรับความกว้างหน้าจอและสังเกตว่ารูปแบบ 4 คอลัมน์ยังเหมาะสมหรือไม่
แบบฝึกหัดที่ 2: สร้าง Gallery แบบ Responsive
เปลี่ยนรูปแบบคอลัมน์เป็น `repeat(auto-fit, minmax(220px, 1fr))` แล้วทดสอบกับหน้าจอแคบและกว้าง สังเกตจำนวนคอลัมน์และขนาดการ์ดเมื่อพื้นที่เปลี่ยนแปลง
แบบฝึกหัดที่ 3: สร้าง Dashboard ด้วย grid-template-areas
สร้างหน้าเว็บที่ประกอบด้วย Header, Sidebar, Main และ Footer โดยกำหนด `grid-template-areas` ให้ครบทุกส่วน จากนั้นเขียน Media Query ให้ Sidebar ย้ายไปอยู่ใต้ Main เมื่อหน้าจอแคบลง
แบบฝึกหัดที่ 4: ทำการ์ดเด่นขนาดใหญ่
เลือกการ์ดหนึ่งใบให้ครอบคลุม 2 คอลัมน์ด้วย `grid-column: span 2` จากนั้นตรวจสอบว่าการจัดวางยังไม่ล้นและไม่ทำให้รายการอื่นซ้อนทับกัน

12. สรุปบทเรียน

CSS Grid เป็นเครื่องมือสำคัญสำหรับสร้างเลย์เอาต์แบบสองมิติ โดยใช้ `display: grid` เพื่อเปิดระบบจัดวาง กำหนดคอลัมน์ด้วย `grid-template-columns` กำหนดแถวด้วย `grid-template-rows` และตั้งระยะห่างด้วย `gap` การใช้ `fr`, `repeat()`, `minmax()`, `auto-fit` และ `auto-fill` ช่วยให้สร้างเลย์เอาต์ที่ยืดหยุ่นและลดการเขียน CSS ซ้ำ ส่วน `grid-column`, `grid-row` และ `grid-template-areas` ช่วยจัดวางองค์ประกอบที่มีความสำคัญต่างกันให้เป็นระบบ แนวทางที่ดีคือเริ่มจากโครงสร้าง HTML ที่มีความหมาย เลือก Grid สำหรับเลย์เอาต์สองมิติ ใช้ Flexbox สำหรับการจัดแนวองค์ประกอบหนึ่งมิติ และทดสอบการแสดงผลในหลายขนาดหน้าจอเสมอ
✅ ภารกิจหลังเรียนสร้างหน้า Product Gallery หรือ Dashboard ที่มีอย่างน้อย 6 การ์ด ใช้ CSS Grid จัดคอลัมน์แบบ Responsive มีระยะห่างสม่ำเสมอ และทำให้หน้าเว็บใช้งานได้ทั้งบนโทรศัพท์และคอมพิวเตอร์ โดยไม่มีเนื้อหาล้นหน้าจอ
เลย์เอาต์ที่ดีไม่ใช่เพียงการจัดวางทุกองค์ประกอบให้อยู่ในตำแหน่งที่ต้องการ แต่คือการสร้างโครงสร้างที่ปรับตัวได้เมื่อเนื้อหาและพื้นที่เปลี่ยนไป
อ่านจนถึงระยะ 500px ก่อนท้ายหน้าและอยู่ในหน้านี้อย่างน้อย 5 นาที เพื่อรับ EXP วันละครั้ง

⏳ กำลังจับเวลาอ่าน...

🌐
เลือกภาษา / Select Language
21 World Languages Supported