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 Layout | display: grid; |
| grid-template-columns | กำหนดรูปแบบและขนาดคอลัมน์ | grid-template-columns: 1fr 2fr; |
| grid-template-rows | กำหนดรูปแบบและขนาดแถว | grid-template-rows: 100px auto; |
| gap | กำหนดระยะห่างระหว่างช่อง | gap: 16px; |
| grid-column | กำหนดช่วงคอลัมน์ของ Grid Item | grid-column: span 2; |
| grid-row | กำหนดช่วงแถวของ Grid Item | grid-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 วันละครั้ง
⏳ กำลังจับเวลาอ่าน...