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

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

HTML & CSS

บทเรียน HTML และ CSS ตอนที่ 10 เรียนรู้ CSS Grid ตั้งแต่แนวคิดพื้นฐาน การกำหนดแถวและคอลัมน์ การจัดวางพื้นที่ การใช้ repeat(), minmax(), auto-fit, auto-fill และการสร้างหน้าเว็บแบบ Responsive พร้อมตัวอย่าง HTML ที่นำไปทดลองใช้งานได้ทันที

HTML & CSS EP10: CSS Grid คืออะไร และช่วยจัดหน้าเว็บไซต์อย่างไร

เมื่อเว็บไซต์มีองค์ประกอบจำนวนมาก เช่น แถบเมนู การ์ดสินค้า รูปภาพ บทความ และแผงข้อมูล การจัดวางด้วยการกำหนดความกว้างและระยะห่างทีละส่วนอาจทำให้โค้ดซับซ้อนขึ้น CSS Grid เป็นระบบจัดเลย์เอาต์สองมิติที่ช่วยจัดองค์ประกอบตามแถวและคอลัมน์ได้อย่างเป็นระบบ เหมาะกับหน้าแดชบอร์ด แกลเลอรีสินค้า หน้าข่าว และหน้า Landing Page ที่ต้องการควบคุมตำแหน่งของเนื้อหาอย่างยืดหยุ่น
💡 แนวคิดสำคัญFlexbox เหมาะกับการจัดองค์ประกอบเป็นแนวหลักหนึ่งมิติ เช่น แถวปุ่มหรือแถบเมนู ส่วน CSS Grid เหมาะกับเลย์เอาต์ที่ต้องควบคุมทั้งแถวและคอลัมน์ ทั้งสองระบบสามารถใช้ร่วมกันในหน้าเดียวได้

1. เข้าใจ Grid Container และ Grid Items

CSS Grid มีองค์ประกอบหลักสองส่วน ได้แก่ **Grid Container** ซึ่งเป็นกล่องแม่ที่กำหนดระบบตาราง และ **Grid Items** ซึ่งเป็นองค์ประกอบลูกโดยตรงภายในกล่องนั้น เมื่อตั้งค่า `display: grid` ให้กับกล่องแม่ ลูกโดยตรงจะกลายเป็นรายการใน Grid โดยอัตโนมัติ ส่วนองค์ประกอบที่อยู่ลึกลงไปภายในลูกจะไม่ได้กลายเป็น Grid Item ของกล่องแม่โดยตรง
<div class="grid-container">
  <div class="grid-item">รายการ 1</div>
  <div class="grid-item">รายการ 2</div>
  <div class="grid-item">รายการ 3</div>
</div>

<style>
.grid-container {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 12px;
}

.grid-item {
  padding: 24px;
  background: #dbeafe;
  border-radius: 8px;
  text-align: center;
}
</style>
ในตัวอย่างนี้ `display: grid` เปิดใช้ระบบ Grid และ `grid-template-columns: 1fr 1fr 1fr` สร้างคอลัมน์สามคอลัมน์ที่แบ่งพื้นที่ว่างเท่า ๆ กัน ส่วน `gap` กำหนดช่องว่างระหว่างรายการโดยไม่ต้องเพิ่ม margin ให้แต่ละกล่อง

2. กำหนดคอลัมน์ด้วย grid-template-columns

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

2.1 ใช้ repeat() ลดการเขียน CSS ซ้ำ

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

3. จัดการแถวด้วย grid-template-rows

หากต้องการกำหนดความสูงหรือสัดส่วนของแถว สามารถใช้ `grid-template-rows` เช่น `80px auto` เพื่อกำหนดแถวแรกให้สูง 80 พิกเซล และให้แถวที่สองปรับตามเนื้อหาในกรณีทั่วไป นอกจากนี้ยังใช้หน่วย `fr` เพื่อแบ่งพื้นที่ว่างในแกนแถวได้เมื่อความสูงของ Grid Container ถูกกำหนดไว้อย่างเหมาะสม
.layout {
  display: grid;
  grid-template-rows: 80px 1fr auto;
  min-height: 100vh;
}

.header { grid-row: 1; }
.main { grid-row: 2; }
.footer { grid-row: 3; }
⚠️ ข้อควรระวังเกี่ยวกับ frหน่วย `fr` ใช้แบ่งพื้นที่ว่างใน Grid ไม่ได้หมายความว่าเป็นเปอร์เซ็นต์ของพื้นที่ทั้งหมดโดยตรง หากต้องการให้แถวแบ่งความสูงตามสัดส่วน ควรกำหนดความสูงของ Grid Container ให้ชัดเจน และตรวจสอบผลเมื่อเนื้อหามีความสูงแตกต่างกัน

4. กำหนดช่องว่างด้วย gap

คุณสมบัติ `gap` กำหนดระยะห่างระหว่างแถวและคอลัมน์ได้ในคำสั่งเดียว เช่น `gap: 16px` ใช้ระยะห่าง 16 พิกเซลทั้งสองทิศทาง หากต้องการกำหนดแยกกัน ให้ใช้ `row-gap` สำหรับระยะระหว่างแถว และ `column-gap` สำหรับระยะระหว่างคอลัมน์
.gallery {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  row-gap: 24px;
  column-gap: 16px;
}

5. จัดวางรายการให้กินพื้นที่หลายแถวหรือหลายคอลัมน์

คุณสมบัติ `grid-column` และ `grid-row` ใช้กำหนดช่วงเส้น Grid ที่รายการจะครอบครอง ตัวอย่าง `grid-column: span 2` ทำให้รายการกินพื้นที่สองคอลัมน์ และ `grid-row: span 2` ทำให้รายการกินพื้นที่สองแถว เหมาะกับการ์ดเด่นในหน้าแรกหรือภาพขนาดใหญ่ในแกลเลอรี
.featured-card {
  grid-column: span 2;
  grid-row: span 2;
}

/* กำหนดตำแหน่งระหว่างเส้น Grid โดยตรง */
.sidebar {
  grid-column: 1;
  grid-row: 1 / 3;
}
การใช้ `span` ไม่ได้กำหนดว่ารายการต้องเริ่มจากคอลัมน์ใด แต่ระบุว่ารายการนั้นควรกินพื้นที่กี่แทร็ก ส่วนการกำหนดค่าเช่น `grid-column: 1 / 3` หมายถึงเริ่มจากเส้น Grid หมายเลข 1 ไปสิ้นสุดที่เส้นหมายเลข 3 ซึ่งครอบคลุมพื้นที่สองคอลัมน์

6. ตั้งชื่อพื้นที่ด้วย grid-template-areas

สำหรับหน้าเว็บที่มีส่วนประกอบชัดเจน เช่น Header, Sidebar, Main และ Footer สามารถใช้ `grid-template-areas` เพื่ออธิบายโครงสร้างเลย์เอาต์ด้วยชื่อแทนหมายเลขเส้น วิธีนี้ช่วยให้ CSS อ่านง่ายและเห็นภาพรวมของหน้าได้รวดเร็ว
.page-layout {
  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; }
🧩 เคล็ดลับการตั้งชื่อพื้นที่แต่ละแถวใน `grid-template-areas` ต้องมีจำนวนเซลล์สอดคล้องกับโครงสร้างคอลัมน์ หากต้องการเว้นพื้นที่ว่างในแผนผัง สามารถใช้เครื่องหมายจุด (`.`) แทนเซลล์ที่ไม่มีรายการได้

7. ทำเลย์เอาต์ Responsive ด้วย minmax(), auto-fit และ auto-fill

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

8. จัดแนวรายการภายใน Grid

CSS Grid มีคุณสมบัติสำหรับจัดแนวทั้งรายการและเนื้อหาภายในพื้นที่ เช่น `justify-items` สำหรับแนวแกนแถว และ `align-items` สำหรับแนวแกนคอลัมน์ในระบบการจัดแนวของ Grid ส่วน `place-items` เป็นคำสั่งย่อที่รวม `align-items` และ `justify-items` เข้าด้วยกัน สำหรับการจัดแนวแทร็กทั้งชุด สามารถใช้ `justify-content` และ `align-content` ได้เมื่อมีพื้นที่ว่างใน Grid Container
.grid-container {
  display: grid;
  place-items: center;
  gap: 16px;
}

/* จัดตำแหน่งรายการแต่ละชิ้น */
.grid-container {
  justify-items: stretch;
  align-items: start;
}
ควรแยกให้ออกระหว่างการจัดแนวรายการภายในเซลล์กับการจัดตำแหน่งของ Grid ทั้งชุด หากไม่เห็นความเปลี่ยนแปลงจาก `justify-content` หรือ `align-content` ให้ตรวจสอบว่าคอนเทนเนอร์มีพื้นที่ว่างในทิศทางนั้นเพียงพอหรือไม่

9. ตัวอย่างการใช้งานจริง: สร้าง Product Grid

ตัวอย่างต่อไปนี้จำลองพื้นที่แสดงสินค้าแบบการ์ด แต่ละการ์ดประกอบด้วยชื่อ รายละเอียด ราคา และปุ่มเลือกดูสินค้า โดยใช้ CSS Grid เพื่อปรับจำนวนคอลัมน์อัตโนมัติตามขนาดพื้นที่ และใช้ Flexbox ภายในตัวการ์ดเพื่อให้ปุ่มอยู่ด้านล่างอย่างสม่ำเสมอ
<!DOCTYPE html>
<html lang="th">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Product Grid Example</title>
  <style>
    * { box-sizing: border-box; }
    body {
      margin: 0;
      padding: 24px;
      font-family: system-ui, sans-serif;
      color: #172033;
      background: #f1f5f9;
    }
    h1 { margin-top: 0; }
    .product-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
      gap: 18px;
      max-width: 1100px;
      margin: 0 auto;
    }
    .product-card {
      display: flex;
      flex-direction: column;
      padding: 20px;
      border: 1px solid #dbe3ef;
      border-radius: 16px;
      background: #fff;
      box-shadow: 0 6px 18px rgba(15, 23, 42, .06);
    }
    .product-icon {
      display: grid;
      place-items: center;
      min-height: 110px;
      border-radius: 12px;
      background: #dbeafe;
      font-size: 42px;
    }
    .product-card h2 { font-size: 1.15rem; }
    .product-card p { color: #526078; line-height: 1.6; }
    .price { margin-top: auto; font-weight: 800; font-size: 1.25rem; }
    button {
      width: 100%;
      margin-top: 14px;
      padding: 11px 14px;
      border: 0;
      border-radius: 9px;
      background: #2563eb;
      color: #fff;
      font: inherit;
      font-weight: 700;
      cursor: pointer;
    }
    button:hover { background: #1d4ed8; }
    button:focus-visible { outline: 3px solid #93c5fd; outline-offset: 3px; }
    .notice {
      max-width: 1100px;
      margin: 20px auto 0;
      padding: 14px;
      border-radius: 10px;
      background: #dcfce7;
      color: #166534;
    }
  </style>
</head>
<body>
  <main>
    <h1>สินค้าแนะนำ</h1>
    <p>ตัวอย่างเลย์เอาต์ที่ปรับจำนวนคอลัมน์ตามพื้นที่หน้าจอ</p>
    <section class="product-grid" aria-label="รายการสินค้า">
      <article class="product-card">
        <div class="product-icon" aria-hidden="true">💻</div>
        <h2>โน้ตบุ๊กสำหรับทำงาน</h2>
        <p>เหมาะสำหรับงานเอกสาร การเรียน และการพัฒนาเว็บไซต์</p>
        <div class="price">฿18,900</div>
        <button type="button" data-product="โน้ตบุ๊กสำหรับทำงาน">ดูสินค้า</button>
      </article>
      <article class="product-card">
        <div class="product-icon" aria-hidden="true">⌨️</div>
        <h2>คีย์บอร์ดไร้สาย</h2>
        <p>ช่วยให้โต๊ะทำงานเป็นระเบียบและใช้งานสะดวกขึ้น</p>
        <div class="price">฿1,290</div>
        <button type="button" data-product="คีย์บอร์ดไร้สาย">ดูสินค้า</button>
      </article>
      <article class="product-card">
        <div class="product-icon" aria-hidden="true">🖱️</div>
        <h2>เมาส์สำหรับทำงาน</h2>
        <p>ออกแบบให้เหมาะกับการใช้งานทั่วไปในชีวิตประจำวัน</p>
        <div class="price">฿790</div>
        <button type="button" data-product="เมาส์สำหรับทำงาน">ดูสินค้า</button>
      </article>
      <article class="product-card">
        <div class="product-icon" aria-hidden="true">🎧</div>
        <h2>หูฟังสำหรับประชุม</h2>
        <p>เหมาะกับการประชุมออนไลน์และการเรียนผ่านวิดีโอ</p>
        <div class="price">฿1,590</div>
        <button type="button" data-product="หูฟังสำหรับประชุม">ดูสินค้า</button>
      </article>
    </section>
    <p class="notice" id="notice" role="status" aria-live="polite">เลือกดูสินค้าเพื่อทดลองการทำงานของปุ่ม</p>
  </main>
  <script>
    const notice = document.getElementById('notice');
    document.querySelectorAll('[data-product]').forEach((button) => {
      button.addEventListener('click', () => {
        notice.textContent = 'คุณเลือกดู: ' + button.dataset.product + ' — นี่เป็นตัวอย่างการทำงานภายในหน้าเว็บเท่านั้น';
      });
    });
  </script>
</body>
</html>

10. สร้าง Dashboard ด้วย Grid Areas

หน้าแดชบอร์ดมักประกอบด้วยแถบด้านบน เมนูด้านข้าง พื้นที่เนื้อหาหลัก และแผงสรุปข้อมูล CSS Grid ช่วยให้ระบุพื้นที่ของแต่ละส่วนได้อย่างชัดเจน และสามารถเปลี่ยนผังเมื่อหน้าจอเล็กลงได้โดยไม่ต้องเปลี่ยนโครงสร้าง HTML ทั้งหมด
.dashboard {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  grid-template-areas:
    "sidebar header"
    "sidebar content";
  gap: 16px;
}

.dashboard-header { grid-area: header; }
.dashboard-sidebar { grid-area: sidebar; }
.dashboard-content { grid-area: content; }

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

11. ปัญหาที่พบบ่อยและวิธีแก้ไข

ทำไมการ์ดจึงดันหน้าเว็บให้กว้างเกินหน้าจอ?
สาเหตุหนึ่งคือเนื้อหาภายในรายการมีขนาดขั้นต่ำโดยปริยาย เช่น ข้อความยาวหรือรูปภาพขนาดใหญ่ ลองใช้ `minmax(0, 1fr)` กับคอลัมน์ กำหนด `min-width: 0` ให้รายการที่จำเป็น และใช้ `max-width: 100%` กับรูปภาพ พร้อมตรวจสอบว่าไม่มีข้อความหรือองค์ประกอบที่กำหนดความกว้างตายตัวเกินพื้นที่
ควรใช้ Grid หรือ Flexbox เมื่อใด?
เลือก Grid เมื่อเลย์เอาต์ต้องควบคุมทั้งแถวและคอลัมน์ เช่น ตารางการ์ดหรือหน้าแดชบอร์ด เลือก Flexbox เมื่อจัดองค์ประกอบตามแกนหลักเดียว เช่น เมนูแนวนอนหรือปุ่มที่เรียงต่อกัน ทั้งสองแบบใช้ร่วมกันได้ เช่น Grid จัดการ์ดสินค้าเป็นตาราง และ Flexbox จัดเนื้อหาภายในแต่ละการ์ด
auto-fit ไม่สร้างจำนวนคอลัมน์ตามที่คาดไว้ ต้องตรวจอะไร?
ตรวจสอบความกว้างของคอนเทนเนอร์ ค่าใน `minmax()` ระยะห่าง `gap` และความกว้างขั้นต่ำของรายการ หากคอนเทนเนอร์แคบจนไม่สามารถรองรับคอลัมน์ขั้นต่ำหลายคอลัมน์ได้ ระบบจะสร้างคอลัมน์น้อยลง ซึ่งเป็นพฤติกรรมปกติของเลย์เอาต์แบบยืดหยุ่น
Grid Items ต้องมีจำนวนเท่ากับจำนวนคอลัมน์หรือไม่?
ไม่จำเป็น หากมีรายการมากกว่าจำนวนคอลัมน์ รายการจะไหลไปยังแถวถัดไปตามกฎการจัดวางอัตโนมัติ หากจำนวนรายการไม่เต็มแถว พื้นที่ว่างอาจยังคงอยู่หรือถูกขยายตามการตั้งค่า Grid ที่เลือก
จะทำให้รูปภาพไม่ล้นการ์ดได้อย่างไร?
ใช้ `max-width: 100%` และ `height: auto` กับรูปภาพทั่วไป หากต้องการให้ภาพทุกใบมีขนาดพื้นที่เท่ากัน อาจกำหนดกรอบภาพและใช้ `object-fit: cover` เพื่อให้ภาพเต็มกรอบ โดยยอมให้ขอบบางส่วนถูกตัดตามอัตราส่วนที่กำหนด

12. แนวทางตรวจสอบก่อนนำ Grid ไปใช้จริง

ก่อนเผยแพร่หน้าเว็บ ควรตรวจสอบเลย์เอาต์ด้วยขนาดหน้าจอที่หลากหลาย ทดสอบเนื้อหาที่ยาวกว่าปกติ ตรวจดูว่าปุ่มยังใช้งานได้ด้วยคีย์บอร์ด รูปภาพไม่ล้นกรอบ และระยะห่างมีความสม่ำเสมอ นอกจากนี้ควรทดสอบกรณีมีรายการน้อยหรือมากกว่าที่คาดไว้ เพราะเนื้อหาจริงมักเปลี่ยนแปลงได้ตลอดเวลา
รายการตรวจสอบสิ่งที่ควรทำเหตุผล
หน้าจอมือถือตรวจสอบว่าคอลัมน์ลดลงตามพื้นที่ป้องกันเนื้อหาเบียดกัน
ข้อความยาวทดสอบชื่อสินค้าและคำอธิบายที่ยาวลดปัญหาความกว้างเกิน
รูปภาพกำหนดขนาดสูงสุดและตรวจอัตราส่วนป้องกันภาพล้นคอนเทนเนอร์
การเข้าถึงใช้โครงสร้าง HTML ที่มีความหมายและปุ่มที่มีชื่อชัดเจนช่วยผู้ใช้เทคโนโลยีช่วยเหลือ
จำนวนรายการทดลองทั้งกรณีมีรายการน้อยและหลายรายการตรวจสอบการจัดวางอัตโนมัติ
เลย์เอาต์ที่ดีไม่ใช่แค่การจัดองค์ประกอบให้สวยงาม แต่ต้องปรับตัวตามเนื้อหาและอุปกรณ์ของผู้ใช้ได้อย่างเหมาะสม
✅ สรุปบทเรียน EP10คุณได้เรียนรู้วิธีสร้างเลย์เอาต์ด้วย CSS Grid ตั้งแต่การกำหนดคอลัมน์และแถว การใช้ `repeat()` และ `gap` การจัดวางพื้นที่ด้วย `grid-template-areas` ไปจนถึงการสร้าง Responsive Grid ด้วย `minmax()`, `auto-fit` และ `auto-fill` ขั้นต่อไปคือทดลองปรับค่าต่าง ๆ และนำ Grid ไปใช้กับหน้าเว็บจริงของคุณ

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

ลองปรับตัวอย่าง Product Grid ให้มีการ์ดอย่างน้อยหกใบ เปลี่ยนสีพื้นหลังของแต่ละการ์ด ทดลองเปลี่ยนค่า `minmax(220px, 1fr)` เป็น `minmax(260px, 1fr)` แล้วสังเกตว่าจำนวนคอลัมน์เปลี่ยนไปอย่างไร จากนั้นเพิ่มคลาส `featured-card` ให้การ์ดหนึ่งใบและกำหนด `grid-column: span 2` บนหน้าจอที่กว้างพอ พร้อมปรับเลย์เอาต์บนมือถือไม่ให้การ์ดกินพื้นที่เกินหน้าจอ

14. ตัวอย่างฉบับเต็ม: Responsive Grid Playground

โค้ดด้านล่างเป็นไฟล์ HTML แบบเต็มหน้า สามารถบันทึกเป็น `index.html` แล้วเปิดด้วยเบราว์เซอร์ได้ทันที ภายในมีตัวอย่างการ์ดที่ใช้ CSS Grid พร้อมปุ่มเปลี่ยนจำนวนคอลัมน์ เพื่อทดลองเปรียบเทียบเลย์เอาต์โดยไม่ต้องแก้โค้ดเองทุกครั้ง
<!DOCTYPE html>
<html lang="th">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Responsive Grid Playground</title>
  <style>
    * { box-sizing: border-box; }
    :root {
      color-scheme: light;
      --primary: #2563eb;
      --ink: #172033;
      --muted: #5b6475;
      --surface: #ffffff;
      --page: #f1f5f9;
      --border: #dbe3ef;
    }
    body {
      margin: 0;
      padding: 24px;
      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      color: var(--ink);
      background: var(--page);
      line-height: 1.6;
    }
    .app { max-width: 1100px; margin: 0 auto; }
    .hero {
      padding: 24px;
      border-radius: 18px;
      color: #fff;
      background: linear-gradient(135deg, #1d4ed8, #7c3aed);
      margin-bottom: 20px;
    }
    .hero h1 { margin: 0 0 8px; line-height: 1.25; }
    .hero p { margin: 0; color: #e0e7ff; }
    .controls {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: 10px;
      padding: 16px;
      margin-bottom: 20px;
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: 14px;
    }
    .controls strong { margin-right: auto; }
    button {
      padding: 9px 14px;
      border: 1px solid #bfdbfe;
      border-radius: 9px;
      background: #eff6ff;
      color: #1d4ed8;
      font: inherit;
      font-weight: 700;
      cursor: pointer;
    }
    button[aria-pressed="true"] { background: var(--primary); color: #fff; border-color: var(--primary); }
    button:focus-visible { outline: 3px solid #93c5fd; outline-offset: 3px; }
    .grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
      gap: 18px;
    }
    .card {
      display: flex;
      flex-direction: column;
      min-width: 0;
      padding: 18px;
      border: 1px solid var(--border);
      border-radius: 15px;
      background: var(--surface);
      box-shadow: 0 5px 18px rgba(15, 23, 42, .05);
      transition: transform .2s ease, box-shadow .2s ease;
    }
    .card:hover { transform: translateY(-3px); box-shadow: 0 12px 24px rgba(15, 23, 42, .1); }
    .icon {
      display: grid;
      place-items: center;
      min-height: 100px;
      border-radius: 12px;
      background: #dbeafe;
      font-size: 38px;
    }
    .card:nth-child(3n + 2) .icon { background: #ede9fe; }
    .card:nth-child(3n) .icon { background: #dcfce7; }
    .card h2 { margin: 15px 0 6px; font-size: 1.12rem; }
    .card p { margin: 0 0 16px; color: var(--muted); }
    .card .tag { margin-top: auto; font-size: .85rem; color: #1d4ed8; font-weight: 700; }
    .status {
      margin-top: 20px;
      padding: 14px 16px;
      border: 1px solid #bbf7d0;
      border-radius: 12px;
      background: #f0fdf4;
      color: #166534;
    }
    .footer { margin-top: 24px; color: var(--muted); font-size: .9rem; text-align: center; }
    @media (max-width: 520px) {
      body { padding: 14px; }
      .hero { padding: 18px; }
      .controls { align-items: stretch; }
      .controls strong { width: 100%; }
      .controls button { flex: 1; }
      .grid { grid-template-columns: minmax(0, 1fr); }
    }
    @media (prefers-reduced-motion: reduce) {
      *, *::before, *::after { transition-duration: .01ms !important; scroll-behavior: auto !important; }
    }
  </style>
</head>
<body>
  <main class="app">
    <header class="hero">
      <h1>Responsive Grid Playground</h1>
      <p>ทดลองจัดเลย์เอาต์ด้วย CSS Grid และเปรียบเทียบรูปแบบคอลัมน์</p>
    </header>
    <section class="controls" aria-label="ตัวเลือกจำนวนคอลัมน์">
      <strong>เลือกจำนวนคอลัมน์บนหน้าจอขนาดใหญ่</strong>
      <button type="button" data-columns="2" aria-pressed="false">2 คอลัมน์</button>
      <button type="button" data-columns="3" aria-pressed="true">3 คอลัมน์</button>
      <button type="button" data-columns="4" aria-pressed="false">4 คอลัมน์</button>
      <button type="button" data-columns="auto" aria-pressed="false">อัตโนมัติ</button>
    </section>
    <section class="grid" id="grid" aria-label="ตัวอย่างการ์ด">
      <article class="card"><div class="icon" aria-hidden="true">🧱</div><h2>Grid Container</h2><p>กำหนดกล่องแม่ให้เป็นระบบ Grid เพื่อจัดวางรายการลูก</p><span class="tag">display: grid</span></article>
      <article class="card"><div class="icon" aria-hidden="true">📐</div><h2>Columns & Rows</h2><p>ควบคุมจำนวนคอลัมน์และแถวให้เหมาะกับโครงสร้างหน้าเว็บ</p><span class="tag">grid-template</span></article>
      <article class="card"><div class="icon" aria-hidden="true">↔️</div><h2>Flexible Sizing</h2><p>ใช้หน่วย fr เพื่อแบ่งพื้นที่ว่างตามสัดส่วนที่ต้องการ</p><span class="tag">1fr 2fr</span></article>
      <article class="card"><div class="icon" aria-hidden="true">📱</div><h2>Responsive Layout</h2><p>ให้จำนวนคอลัมน์ปรับตามพื้นที่และขนาดอุปกรณ์</p><span class="tag">auto-fit</span></article>
      <article class="card"><div class="icon" aria-hidden="true">🧩</div><h2>Grid Areas</h2><p>ตั้งชื่อพื้นที่เพื่อทำให้ผังหน้าเว็บอ่านง่ายและแก้ไขสะดวก</p><span class="tag">grid-template-areas</span></article>
      <article class="card"><div class="icon" aria-hidden="true">🎯</div><h2>Alignment</h2><p>จัดแนวรายการและควบคุมระยะห่างระหว่างองค์ประกอบ</p><span class="tag">place-items</span></article>
    </section>
    <p class="status" id="status" role="status" aria-live="polite">กำลังใช้เลย์เอาต์แบบอัตโนมัติ โดยปรับจำนวนคอลัมน์ตามพื้นที่หน้าจอ</p>
    <p class="footer">ตัวอย่างเพื่อการเรียนรู้ HTML และ CSS — ปรับขนาดหน้าต่างเพื่อดู Responsive Layout</p>
  </main>
  <script>
    const grid = document.getElementById('grid');
    const status = document.getElementById('status');
    const buttons = document.querySelectorAll('[data-columns]');

    buttons.forEach((button) => {
      button.addEventListener('click', () => {
        const value = button.dataset.columns;
        buttons.forEach((item) => {
          item.setAttribute('aria-pressed', String(item === button));
        });

        if (value === 'auto') {
          grid.style.gridTemplateColumns = 'repeat(auto-fit, minmax(220px, 1fr))';
          status.textContent = 'เลือกโหมดอัตโนมัติ: เบราว์เซอร์จะปรับจำนวนคอลัมน์ตามพื้นที่ที่มี';
        } else {
          grid.style.gridTemplateColumns = 'repeat(' + value + ', minmax(0, 1fr))';
          status.textContent = 'เลือก ' + value + ' คอลัมน์บนพื้นที่ที่รองรับ หากหน้าจอแคบมาก อาจต้องเลื่อนหรือปรับขนาดหน้าต่างเพื่อดูทุกคอลัมน์';
        }
      });
    });
  </script>
</body>
</html>
อ่านจนถึงระยะ 500px ก่อนท้ายหน้าและอยู่ในหน้านี้อย่างน้อย 5 นาที เพื่อรับ EXP วันละครั้ง

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

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