Article · programming
HTML & CSS EP12: Responsive Design ขั้นสูง — สร้างเว็บไซต์ที่ปรับตัวได้ทุกหน้าจอ
HTML & CSS
บทเรียน HTML และ CSS ตอนที่ 12 เรียนรู้การออกแบบ Responsive Web Design ขั้นสูง ตั้งแต่ Mobile First, Media Queries, Container Queries, Fluid Typography, CSS clamp(), รูปภาพ Responsive, การจัดการเมนูและการ์ดสินค้า ไปจนถึงการสร้างหน้าเว็บไซต์ตัวอย่างที่ปรับตามขนาดหน้าจอได้จริง พร้อมโค้ด HTML เต็มหน้าสำหรับนำไปทดลอง
HTML & CSS EP12: Responsive Design ขั้นสูง
เว็บไซต์ที่ดีไม่ควรทำงานได้เฉพาะบนหน้าจอคอมพิวเตอร์ แต่ต้องอ่านง่าย ใช้งานสะดวก และรักษาความสำคัญของเนื้อหาไว้ได้ทั้งบนมือถือ แท็บเล็ต โน้ตบุ๊ก และจอขนาดใหญ่ ในบทนี้เราจะนำพื้นฐาน HTML, CSS Grid และ Flexbox มาต่อยอดเป็นแนวทาง Responsive Web Design ที่ใช้ได้กับเว็บไซต์ธุรกิจ หน้า Landing Page ระบบหลังบ้าน และหน้ารายการสินค้า
💡 เป้าหมายของบทเรียนหลังเรียนจบ คุณจะสามารถออกแบบด้วยแนวคิด Mobile First เลือกจุดเปลี่ยนหน้าจออย่างมีเหตุผล ใช้ CSS clamp() เพื่อปรับขนาดอย่างยืดหยุ่น ใช้ Container Queries สำหรับคอมโพเนนต์ และสร้างหน้าเว็บที่ลดปัญหาข้อความล้นหรือแถบเลื่อนแนวนอน
1. Responsive Design คืออะไร
Responsive Web Design คือแนวทางการออกแบบหน้าเว็บให้โครงสร้างและเนื้อหาปรับตัวตามพื้นที่แสดงผล แทนที่จะสร้างหน้าเว็บแยกสำหรับอุปกรณ์แต่ละชนิด โดยทั่วไปจะใช้ Layout ที่ยืดหยุ่น รูปภาพที่ปรับขนาดได้ และ Media Queries เพื่อเปลี่ยนรูปแบบเมื่อพื้นที่ไม่เหมาะกับเลย์เอาต์เดิม
การทำ Responsive ไม่ได้หมายถึงการย่อทุกอย่างให้เล็กลงเท่านั้น บางครั้งต้องเปลี่ยนจำนวนคอลัมน์ ย้ายแถบเมนู เรียงการ์ดใหม่ ลดรายละเอียดที่ไม่จำเป็น หรือเพิ่มพื้นที่ให้ปุ่มเพื่อให้แตะได้สะดวก สิ่งสำคัญคือเนื้อหาหลักต้องยังเข้าถึงได้และไม่สร้างภาระให้ผู้ใช้
2. เริ่มจาก Mobile First
แนวคิด Mobile First คือเริ่มออกแบบจากหน้าจอขนาดเล็กก่อน แล้วค่อยเพิ่มความสามารถของเลย์เอาต์เมื่อมีพื้นที่มากขึ้น วิธีนี้ช่วยให้เราจัดลำดับเนื้อหาอย่างชัดเจน ลดการพึ่งพาความกว้างคงที่ และหลีกเลี่ยงการสร้างเลย์เอาต์ขนาดใหญ่ที่ยากต่อการย่อให้เหมาะกับมือถือ
/* ค่าเริ่มต้นสำหรับหน้าจอขนาดเล็ก */
.product-grid {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 16px;
}
/* เพิ่มคอลัมน์เมื่อมีพื้นที่เพียงพอ */
@media (min-width: 600px) {
.product-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: 960px) {
.product-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
ตัวอย่างนี้เริ่มจากหนึ่งคอลัมน์บนมือถือ เพิ่มเป็นสองคอลัมน์เมื่อหน้าจอกว้างอย่างน้อย 600 พิกเซล และเพิ่มเป็นสามคอลัมน์เมื่อกว้างอย่างน้อย 960 พิกเซล ค่าดังกล่าวเป็นตัวอย่างสำหรับฝึกฝน ไม่ใช่มาตรฐานที่ต้องใช้กับทุกเว็บไซต์ จุดเปลี่ยนควรพิจารณาจากเนื้อหาและพื้นที่จริง
3. เลือก Breakpoints จากเนื้อหา ไม่ใช่จากชื่ออุปกรณ์
Breakpoints คือค่าความกว้างที่ใช้เปลี่ยนกฎ CSS เช่น เปลี่ยนจากแถบนำทางแนวนอนเป็นเมนูที่จัดเรียงหลายบรรทัด หรือเปลี่ยนการ์ดสามคอลัมน์ให้เหลือสองคอลัมน์ ไม่จำเป็นต้องมีจุดเปลี่ยนตรงกับชื่อมือถือหรือแท็บเล็ต เพราะอุปกรณ์แต่ละรุ่นมีขนาดหน้าจอและสัดส่วนต่างกัน
| สถานการณ์ | สิ่งที่ควรตรวจสอบ | แนวทางปรับ |
|---|---|---|
| เมนูเริ่มเบียดกัน | ความกว้างรวมของลิงก์และช่องว่าง | ให้เมนูขึ้นบรรทัดใหม่หรือเปลี่ยนรูปแบบ |
| การ์ดอ่านยาก | ความกว้างขั้นต่ำที่เนื้อหาต้องการ | ลดจำนวนคอลัมน์ |
| บทความมีบรรทัดยาวเกินไป | ความกว้างของพื้นที่อ่านข้อความ | จำกัดความกว้างเนื้อหา |
| แถบด้านข้างเบียดพื้นที่หลัก | พื้นที่ที่เหลือสำหรับเนื้อหา | ย้ายแถบด้านข้างลงด้านล่าง |
| ปุ่มเรียงชิดกันเกินไป | ขนาดปุ่มและระยะห่าง | จัดปุ่มเป็นแนวตั้งบนหน้าจอเล็ก |
✅ วิธีคิดที่ช่วยลดการแก้ CSS ซ้ำลองปรับความกว้างหน้าต่างเบราว์เซอร์อย่างช้า ๆ แล้วสังเกตว่าจุดใดที่เนื้อหาเริ่มอึดอัด จากนั้นจึงกำหนด Breakpoint เพื่อแก้ปัญหาที่พบจริง แทนการเลือกตัวเลขจากอุปกรณ์รุ่นใดรุ่นหนึ่ง
4. ใช้ clamp() เพื่อกำหนดขนาดที่ยืดหยุ่น
การกำหนดขนาดตัวอักษรด้วยค่าคงที่เพียงค่าเดียวอาจทำให้หัวข้อเล็กเกินไปบนจอใหญ่ หรือใหญ่เกินไปบนมือถือ ฟังก์ชัน `clamp(min, preferred, max)` ช่วยกำหนดค่าต่ำสุด ค่าที่ต้องการให้ปรับตามพื้นที่ และค่าสูงสุดได้ในคำสั่งเดียว
h1 {
font-size: clamp(2rem, 1.2rem + 3vw, 4rem);
line-height: 1.15;
}
.page-intro {
font-size: clamp(1rem, 0.92rem + 0.5vw, 1.25rem);
line-height: 1.7;
}
.section-title {
font-size: clamp(1.35rem, 1rem + 1.2vw, 2rem);
}
ค่า `2rem` ในตำแหน่งแรกเป็นขนาดต่ำสุด ส่วน `1.2rem + 3vw` เป็นค่าที่ปรับตามพื้นที่ และ `4rem` เป็นขนาดสูงสุด วิธีนี้ช่วยให้หัวข้อปรับขนาดได้ต่อเนื่องโดยไม่ต้องสร้าง Media Query หลายชุดสำหรับตัวอักษรแต่ละขนาด อย่างไรก็ตาม ควรทดสอบการขยายตัวอักษรและตรวจสอบว่าเนื้อหาไม่ทับซ้อนกัน
5. ใช้ min(), max() และ minmax() ให้เหมาะกับพื้นที่
CSS มีฟังก์ชันสำหรับคำนวณขนาดหลายรูปแบบ `min()` เลือกค่าที่เล็กกว่า `max()` เลือกค่าที่ใหญ่กว่า และ `minmax()` ใช้กำหนดช่วงขนาดของแทร็กใน CSS Grid ฟังก์ชันเหล่านี้ช่วยให้คอลัมน์มีความยืดหยุ่นโดยไม่ต้องระบุความกว้างเป็นพิกเซลทุกส่วน
.article-container {
width: min(100% - 32px, 1100px);
margin-inline: auto;
}
.content-layout {
display: grid;
grid-template-columns: minmax(0, 2fr) minmax(220px, 1fr);
gap: 24px;
}
.responsive-card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
gap: 16px;
}
การใช้ `width: min(100% - 32px, 1100px)` ช่วยให้คอนเทนเนอร์เหลือระยะขอบบนหน้าจอแคบ และจำกัดความกว้างสูงสุดบนจอกว้าง ส่วน `repeat(auto-fit, minmax(...))` ช่วยให้ Grid เลือกจำนวนคอลัมน์ตามพื้นที่ที่มี โดยไม่ต้องกำหนด Media Query สำหรับจำนวนคอลัมน์ทุกระดับ
6. รูปภาพ Responsive และอัตราส่วนภาพ
รูปภาพเป็นสาเหตุหนึ่งที่ทำให้หน้าเว็บล้น หากระบุความกว้างตายตัวโดยไม่คำนึงถึงคอนเทนเนอร์ ควรกำหนด `max-width: 100%` และ `height: auto` เป็นพื้นฐาน หากต้องการกรอบภาพที่มีอัตราส่วนแน่นอน สามารถใช้ `aspect-ratio` และ `object-fit` เพื่อให้ภาพอยู่ในพื้นที่อย่างเหมาะสม
img,
picture,
video {
max-width: 100%;
}
img {
height: auto;
}
.product-image {
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
border-radius: 12px;
}
.hero-image {
width: 100%;
aspect-ratio: 16 / 7;
object-fit: cover;
}
สำหรับภาพสำคัญที่ต้องมีหลายขนาด สามารถใช้ `<picture>` และ `<source>` เพื่อให้เบราว์เซอร์เลือกไฟล์ที่เหมาะสมตาม Media Query หรือชนิดไฟล์ที่รองรับ หากเว็บไซต์มีภาพจำนวนมาก ควรปรับขนาดไฟล์และการบีบอัดด้วย เพื่อไม่ให้ผู้ใช้มือถือเสียเวลาโหลดภาพขนาดใหญ่โดยไม่จำเป็น
<picture>
<source
media="(max-width: 600px)"
srcset="images/banner-mobile.webp"
>
<source
media="(min-width: 601px)"
srcset="images/banner-desktop.webp"
>
<img
src="images/banner-desktop.jpg"
alt="ภาพแบนเนอร์แสดงสินค้าในร้าน"
loading="lazy"
>
</picture>
⚠️ ระวังการใช้ loading="lazy"การโหลดภาพแบบ Lazy Loading เหมาะกับภาพที่อยู่ต่ำกว่าส่วนแรกของหน้า แต่ภาพหลักที่ผู้ใช้เห็นทันทีเมื่อเปิดหน้าเว็บอาจไม่ควรใช้ Lazy Loading เพราะอาจทำให้ภาพปรากฏช้าลง ควรเลือกตามตำแหน่งและความสำคัญของภาพ
7. Container Queries: ปรับคอมโพเนนต์ตามพื้นที่ของตัวเอง
Media Queries ตรวจสอบคุณสมบัติของหน้าจอหรือ Viewport ส่วน Container Queries ช่วยให้คอมโพเนนต์ปรับรูปแบบตามขนาดคอนเทนเนอร์ที่ครอบอยู่ เหมาะกับการ์ดสินค้า กล่องแนะนำบทความ หรือวิดเจ็ตที่อาจถูกนำไปวางในหลายตำแหน่งซึ่งมีความกว้างไม่เท่ากัน
.product-panel {
container-type: inline-size;
}
.product-card {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 12px;
}
@container (min-width: 520px) {
.product-card {
grid-template-columns: 180px minmax(0, 1fr);
align-items: center;
}
}
.product-card img {
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
}
ในตัวอย่างนี้ `.product-panel` ทำหน้าที่เป็น Query Container เมื่อพื้นที่ภายในกว้างอย่างน้อย 520 พิกเซล การ์ดจึงเปลี่ยนเป็นสองคอลัมน์ แม้การ์ดจะอยู่ในหน้าจอใหญ่ แต่หากถูกวางในแผงแคบก็ยังสามารถใช้รูปแบบหนึ่งคอลัมน์ได้ ทำให้คอมโพเนนต์นำกลับมาใช้ซ้ำได้ง่ายขึ้น
8. ออกแบบเมนูให้ใช้งานได้บนหน้าจอเล็ก
เมนูนำทางมักมีพื้นที่จำกัดบนมือถือ หากมีลิงก์หลายรายการ ควรจัดลำดับความสำคัญและเลือกแนวทางที่ไม่ทำให้ข้อความเบียดกัน ตัวอย่างพื้นฐานต่อไปนี้ใช้ Flexbox ให้ลิงก์ขึ้นบรรทัดใหม่ได้โดยไม่บังคับให้มี JavaScript หากต้องการทำเมนูเปิดปิด ควรใช้ปุ่มจริงและอัปเดตสถานะการเข้าถึงให้เหมาะสม
.site-nav {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px 20px;
}
.site-nav__links {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 0;
padding: 0;
list-style: none;
}
.site-nav__links a {
display: inline-block;
padding: 10px 12px;
border-radius: 8px;
text-decoration: none;
}
@media (max-width: 560px) {
.site-nav {
align-items: stretch;
flex-direction: column;
}
.site-nav__links {
flex-direction: column;
}
.site-nav__links a {
display: block;
}
}
9. สร้างปุ่มและพื้นที่แตะที่เหมาะกับมือถือ
การใช้งานบนหน้าจอสัมผัสต่างจากการใช้เมาส์ ผู้ใช้ต้องแตะเป้าหมายบนหน้าจอ จึงควรมีพื้นที่แตะที่เหมาะสมและมีระยะห่างระหว่างปุ่มเพื่อลดการกดผิด อย่าพึ่งพา Hover เพียงอย่างเดียวในการแสดงข้อมูลสำคัญ และควรรักษา Focus Indicator สำหรับผู้ใช้คีย์บอร์ดด้วย
.button {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 44px;
padding: 10px 16px;
border: 0;
border-radius: 10px;
font: inherit;
text-align: center;
cursor: pointer;
}
.button:focus-visible,
a:focus-visible,
input:focus-visible {
outline: 3px solid #93c5fd;
outline-offset: 3px;
}
.button-group {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
@media (max-width: 480px) {
.button-group .button {
flex: 1 1 100%;
}
}
10. ป้องกันข้อความและองค์ประกอบล้นหน้าจอ
ข้อความยาว URL ตาราง และองค์ประกอบที่กำหนดความกว้างคงที่อาจทำให้เกิด Horizontal Overflow ได้ ควรเริ่มตรวจจากองค์ประกอบที่มี `width` หรือ `min-width` มากเกินไป จากนั้นตรวจ Grid Track และเนื้อหาภายในคอลัมน์ การใช้ `min-width: 0` กับรายการ Grid หรือ Flexbox บางประเภทช่วยให้เนื้อหาหดได้ตามพื้นที่ แต่ข้อความและตารางก็อาจต้องมีวิธีแสดงผลเฉพาะของตัวเอง
* {
box-sizing: border-box;
}
.page-content,
.grid-item,
.flex-item {
min-width: 0;
}
.long-content {
overflow-wrap: anywhere;
}
.responsive-table-wrapper {
max-width: 100%;
overflow-x: auto;
}
.responsive-table-wrapper table {
width: 100%;
border-collapse: collapse;
}
🚫 อย่าแก้ปัญหาด้วยการซ่อน Overflow ทั้งหน้าโดยไม่ตรวจสอบการใช้ `overflow-x: hidden` กับ Body อาจซ่อนเนื้อหาที่ล้นและทำให้ผู้ใช้เข้าถึงข้อมูลบางส่วนไม่ได้ ควรหาต้นเหตุขององค์ประกอบที่กว้างเกินจริง แล้วแก้ที่ Grid, Flexbox, รูปภาพ ตาราง หรือข้อความที่เกี่ยวข้องก่อน
11. ใช้ CSS Variables เพื่อดูแลธีมและระยะห่าง
เมื่อเว็บไซต์มีหลายหน้า การกำหนดสี ระยะห่าง และความกว้างเนื้อหาไว้ใน CSS Variables ช่วยให้ปรับระบบดีไซน์ได้จากจุดเดียว นอกจากนี้ยังช่วยลดความคลาดเคลื่อนของรูปแบบระหว่างคอมโพเนนต์ และทำให้การแก้ไขธีมในอนาคตสะดวกขึ้น
:root {
--content-max: 1120px;
--space-sm: 0.75rem;
--space-md: 1rem;
--space-lg: 1.5rem;
--space-xl: 2rem;
--surface: #ffffff;
--page-background: #f1f5f9;
--text-primary: #172033;
--text-muted: #64748b;
--brand: #2563eb;
}
.container {
width: min(100% - 2rem, var(--content-max));
margin-inline: auto;
}
.section {
padding-block: clamp(2rem, 5vw, 4.5rem);
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
gap: var(--space-lg);
}
12. ตารางเปรียบเทียบแนวทาง Responsive
| เทคนิค | เหมาะกับ | ข้อควรพิจารณา |
|---|---|---|
| Mobile First | หน้าเว็บที่ต้องรองรับอุปกรณ์หลากหลาย | เริ่มจากเนื้อหาสำคัญและพื้นที่จำกัด |
| Media Queries | การเปลี่ยนเลย์เอาต์ตาม Viewport | กำหนด Breakpoint จากปัญหาจริง |
| CSS clamp() | ตัวอักษรและระยะห่างแบบยืดหยุ่น | กำหนดค่าต่ำสุดและสูงสุดให้เหมาะสม |
| CSS Grid | การแบ่งพื้นที่เป็นแถวและคอลัมน์ | ใช้ minmax() เพื่อลดปัญหาคอลัมน์ล้น |
| Flexbox | การจัดองค์ประกอบตามแกนเดียว | กำหนด flex-wrap เมื่อจำเป็น |
| Container Queries | คอมโพเนนต์ที่อยู่ในคอนเทนเนอร์หลายขนาด | ต้องกำหนด Query Container ให้ถูกต้อง |
| Responsive Images | ภาพประกอบ แบนเนอร์ และภาพสินค้า | คำนึงถึงอัตราส่วนและขนาดไฟล์ |
13. ตัวอย่างเต็ม: Responsive Product Landing Page
ตัวอย่างต่อไปนี้เป็นหน้า HTML แบบเต็ม สามารถบันทึกเป็น `index.html` แล้วเปิดด้วยเบราว์เซอร์ได้ทันที หน้าเว็บใช้ CSS Grid สำหรับการ์ดสินค้า ใช้ Flexbox สำหรับเมนูและปุ่ม ใช้ `clamp()` สำหรับขนาดหัวข้อ และ Media Queries สำหรับปรับการจัดวางเมื่อหน้าจอแคบลง ข้อมูลทั้งหมดเป็นข้อมูลตัวอย่างสำหรับฝึกฝน
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="ตัวอย่างหน้า Landing Page ที่รองรับหน้าจอหลายขนาด">
<title>Responsive Product Landing Page</title>
<style>
* { box-sizing: border-box; }
:root {
--brand: #2563eb;
--brand-dark: #1d4ed8;
--ink: #172033;
--muted: #64748b;
--page: #f8fafc;
--surface: #ffffff;
--border: #e2e8f0;
--radius: 18px;
}
body {
margin: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
color: var(--ink);
background: var(--page);
line-height: 1.6;
}
a { color: inherit; }
.container {
width: min(100% - 32px, 1120px);
margin-inline: auto;
}
.site-header {
position: relative;
background: var(--surface);
border-bottom: 1px solid var(--border);
}
.nav {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px 24px;
padding-block: 16px;
}
.brand {
margin: 0;
font-size: 1.25rem;
font-weight: 800;
letter-spacing: -0.03em;
}
.brand span { color: var(--brand); }
.nav-links {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
margin: 0;
padding: 0;
list-style: none;
}
.nav-links a {
display: inline-block;
padding: 9px 12px;
color: var(--muted);
text-decoration: none;
border-radius: 9px;
}
.nav-links a:hover,
.nav-links a:focus-visible {
color: var(--brand-dark);
background: #eff6ff;
}
.button {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 44px;
padding: 11px 18px;
border: 1px solid transparent;
border-radius: 11px;
background: var(--brand);
color: #fff;
font: inherit;
font-weight: 700;
text-decoration: none;
cursor: pointer;
transition: background .2s ease, transform .2s ease;
}
.button:hover { background: var(--brand-dark); transform: translateY(-1px); }
.button:focus-visible,
a:focus-visible { outline: 3px solid #93c5fd; outline-offset: 3px; }
.button--secondary {
background: #fff;
color: var(--brand-dark);
border-color: #bfdbfe;
}
.button--secondary:hover { background: #eff6ff; }
.hero {
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr);
align-items: center;
gap: clamp(24px, 5vw, 64px);
padding-block: clamp(48px, 8vw, 96px);
}
.eyebrow {
display: inline-block;
margin-bottom: 14px;
padding: 6px 10px;
border-radius: 999px;
background: #dbeafe;
color: #1d4ed8;
font-size: .85rem;
font-weight: 800;
}
.hero h1 {
max-width: 700px;
margin: 0;
font-size: clamp(2rem, 1.1rem + 3vw, 3.8rem);
line-height: 1.13;
letter-spacing: -.04em;
}
.hero-copy {
max-width: 620px;
margin: 20px 0 0;
color: var(--muted);
font-size: clamp(1rem, .95rem + .35vw, 1.2rem);
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 26px;
}
.hero-visual {
display: grid;
place-items: center;
min-height: 280px;
padding: clamp(24px, 5vw, 48px);
border-radius: 28px;
background: linear-gradient(135deg, #dbeafe, #e0e7ff 55%, #f5d0fe);
}
.visual-card {
width: min(100%, 340px);
padding: 24px;
border: 1px solid rgba(255,255,255,.8);
border-radius: 20px;
background: rgba(255,255,255,.92);
box-shadow: 0 18px 50px rgba(30,64,175,.12);
}
.visual-card small { color: var(--muted); }
.visual-card strong { display: block; margin-block: 8px; font-size: 2rem; }
.visual-bar { height: 10px; margin-top: 16px; border-radius: 99px; background: #dbeafe; overflow: hidden; }
.visual-bar span { display: block; width: 76%; height: 100%; border-radius: inherit; background: var(--brand); }
.section { padding-block: clamp(40px, 6vw, 72px); }
.section-heading { max-width: 720px; margin-bottom: 28px; }
.section-heading h2 { margin: 0; font-size: clamp(1.6rem, 1.2rem + 1.5vw, 2.5rem); line-height: 1.2; }
.section-heading p { margin: 12px 0 0; color: var(--muted); }
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 245px), 1fr));
gap: 18px;
}
.product-card {
display: flex;
flex-direction: column;
min-width: 0;
padding: 22px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
box-shadow: 0 4px 16px rgba(15,23,42,.03);
}
.product-icon {
display: grid;
place-items: center;
width: 48px;
height: 48px;
margin-bottom: 16px;
border-radius: 14px;
background: #eff6ff;
font-size: 1.5rem;
}
.product-card h3 { margin: 0; font-size: 1.15rem; }
.product-card p { margin: 10px 0 18px; color: var(--muted); }
.product-card .button { align-self: flex-start; margin-top: auto; }
.feature-band {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 18px;
padding: 24px;
border-radius: var(--radius);
background: #172554;
color: #fff;
}
.feature-band h3 { margin: 0 0 8px; }
.feature-band p { margin: 0; color: #cbd5e1; font-size: .95rem; }
.cta {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 20px;
padding: clamp(24px, 5vw, 44px);
border: 1px solid #bfdbfe;
border-radius: 22px;
background: #eff6ff;
}
.cta h2 { margin: 0; font-size: clamp(1.4rem, 1.1rem + 1vw, 2rem); }
.cta p { margin: 8px 0 0; color: var(--muted); }
.site-footer { padding-block: 28px; border-top: 1px solid var(--border); color: var(--muted); font-size: .92rem; }
.footer-inner { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 12px; }
@media (max-width: 760px) {
.hero { grid-template-columns: minmax(0, 1fr); }
.hero-visual { min-height: 230px; }
.feature-band { grid-template-columns: minmax(0, 1fr); }
.nav { align-items: flex-start; }
.nav-links { width: 100%; }
}
@media (max-width: 480px) {
.container { width: min(100% - 24px, 1120px); }
.nav-links { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; }
.nav-links a { display: block; padding-inline: 8px; }
.hero-actions { flex-direction: column; }
.hero-actions .button { width: 100%; }
.cta .button { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; }
}
</style>
</head>
<body>
<header class="site-header">
<div class="container nav">
<p class="brand">Level<span>Lance</span></p>
<nav aria-label="เมนูหลัก">
<ul class="nav-links">
<li><a href="#products">บริการ</a></li>
<li><a href="#features">จุดเด่น</a></li>
<li><a href="#contact">ติดต่อ</a></li>
</ul>
</nav>
</div>
</header>
<main>
<section class="container hero">
<div>
<span class="eyebrow">แพลตฟอร์มตัวอย่างสำหรับธุรกิจ</span>
<h1>เริ่มต้นธุรกิจออนไลน์ด้วยเว็บไซต์ที่พร้อมเติบโต</h1>
<p class="hero-copy">จัดระเบียบสินค้า นำเสนอข้อมูลบริการ และสร้างประสบการณ์ใช้งานที่เหมาะกับลูกค้าทั้งบนมือถือและคอมพิวเตอร์</p>
<div class="hero-actions">
<a class="button" href="#products">สำรวจบริการ</a>
<a class="button button--secondary" href="#features">ดูรายละเอียด</a>
</div>
</div>
<div class="hero-visual" aria-label="ตัวอย่างแผงข้อมูลธุรกิจ">
<div class="visual-card">
<small>ภาพรวมประสิทธิภาพตัวอย่าง</small>
<strong>76%</strong>
<span>ความคืบหน้าตามเป้าหมาย</span>
<div class="visual-bar" role="progressbar" aria-label="ความคืบหน้า" aria-valuemin="0" aria-valuemax="100" aria-valuenow="76"><span></span></div>
</div>
</div>
</section>
<section class="section" id="products">
<div class="container">
<div class="section-heading">
<h2>เลือกเครื่องมือให้เหมาะกับธุรกิจ</h2>
<p>ตัวอย่างการ์ดบริการที่ปรับจำนวนคอลัมน์โดยอัตโนมัติตามพื้นที่ที่มี</p>
</div>
<div class="product-grid">
<article class="product-card">
<div class="product-icon" aria-hidden="true">🛍️</div>
<h3>ระบบร้านค้าออนไลน์</h3>
<p>จัดแสดงสินค้า หมวดหมู่ และรายละเอียดที่ช่วยให้ลูกค้าตัดสินใจได้ง่ายขึ้น</p>
<a class="button button--secondary" href="#contact">ดูรายละเอียด</a>
</article>
<article class="product-card">
<div class="product-icon" aria-hidden="true">📊</div>
<h3>ระบบจัดการธุรกิจ</h3>
<p>รวบรวมข้อมูลสำคัญไว้ในพื้นที่เดียวเพื่อช่วยติดตามงานประจำวัน</p>
<a class="button button--secondary" href="#contact">ดูรายละเอียด</a>
</article>
<article class="product-card">
<div class="product-icon" aria-hidden="true">💬</div>
<h3>ระบบสื่อสารกับลูกค้า</h3>
<p>จัดระเบียบช่องทางติดต่อและช่วยให้ทีมตอบคำถามได้เป็นระบบ</p>
<a class="button button--secondary" href="#contact">ดูรายละเอียด</a>
</article>
</div>
</div>
</section>
<section class="section" id="features">
<div class="container">
<div class="feature-band">
<div>
<h3>📱 รองรับมือถือ</h3>
<p>จัดลำดับเนื้อหาและปุ่มให้ใช้งานสะดวกบนหน้าจอขนาดเล็ก</p>
</div>
<div>
<h3>🧩 โครงสร้างยืดหยุ่น</h3>
<p>ใช้ Grid และ Flexbox เพื่อจัดวางส่วนต่าง ๆ ตามหน้าที่</p>
</div>
<div>
<h3>♿ ใช้งานเข้าถึงได้</h3>
<p>มีลำดับหัวข้อที่ชัดเจนและสถานะ Focus สำหรับคีย์บอร์ด</p>
</div>
</div>
</div>
</section>
<section class="section" id="contact">
<div class="container">
<div class="cta">
<div>
<h2>พร้อมต่อยอดเว็บไซต์ของคุณแล้วหรือยัง?</h2>
<p>ส่วนนี้เป็นตัวอย่าง Call to Action สามารถเปลี่ยนข้อความและลิงก์ให้ตรงกับโครงการจริงได้</p>
</div>
<a class="button" href="mailto:[email protected]">ติดต่อทีมงาน</a>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<span>© 2026 LevelLance — ตัวอย่างเพื่อการเรียนรู้</span>
<span>HTML + CSS Responsive Layout</span>
</div>
</footer>
</body>
</html>
14. ตรวจสอบ Responsive ก่อนเผยแพร่เว็บไซต์
ก่อนนำเว็บไซต์ขึ้นใช้งานจริง ควรตรวจสอบความกว้างหลายระดับ ทดลองหมุนหน้าจอ ตรวจสอบข้อความยาว ภาพที่มีสัดส่วนต่างกัน ปุ่มและลิงก์บนหน้าจอสัมผัส รวมถึงการใช้คีย์บอร์ด หากมีฟอร์ม ตาราง หรือหน้าต่างโต้ตอบ ต้องตรวจสอบองค์ประกอบเหล่านั้นแยกต่างหากด้วย
ควรทดสอบที่ความกว้างเท่าไรบ้าง?
เริ่มจากหน้าจอแคบประมาณ 320–375 พิกเซล ทดสอบช่วงกลางประมาณ 600–800 พิกเซล และทดสอบจอเดสก์ท็อปที่กว้างขึ้น ค่าที่ใช้เป็นเพียงแนวทาง ควรเพิ่มการทดสอบที่ขนาดซึ่งเลย์เอาต์เริ่มมีปัญหาด้วย และอย่าลืมตรวจสอบการซูมข้อความ
ทำไมเว็บไซต์ยังล้นทั้งที่กำหนด width: 100% แล้ว?
ตรวจสอบ Padding, Border, min-width, รูปภาพที่มีขนาดคงที่ และลูกของ Grid หรือ Flexbox ที่มีขนาดขั้นต่ำใหญ่เกินไป การใช้ box-sizing: border-box ช่วยให้การคำนวณขนาดเข้าใจง่ายขึ้น แต่ยังต้องตรวจสอบองค์ประกอบภายในและเนื้อหาที่ยาวเป็นพิเศษ
ควรใช้ Media Queries หรือ Container Queries?
ใช้ Media Queries เมื่อการเปลี่ยนรูปแบบขึ้นอยู่กับขนาด Viewport เช่น การเปลี่ยนโครงสร้างหน้าเว็บโดยรวม ใช้ Container Queries เมื่อคอมโพเนนต์ควรปรับตัวตามพื้นที่ของกล่องที่ครอบอยู่ เช่น การ์ดที่นำไปใช้ในหลายคอลัมน์หรือแผงด้านข้าง ทั้งสองเทคนิคสามารถใช้ร่วมกันได้
ต้องเขียน CSS แยกทุกขนาดหน้าจอหรือไม่?
ไม่จำเป็น ควรเริ่มจาก Layout ที่ยืดหยุ่นด้วย Grid, Flexbox, minmax(), auto-fit, auto-fill และ clamp() ก่อน แล้วเพิ่ม Media Queries เฉพาะจุดที่เนื้อหาต้องเปลี่ยนโครงสร้างจริง วิธีนี้ช่วยลดจำนวนกฎ CSS และทำให้แก้ไขได้ง่ายขึ้น
15. แบบฝึกหัดท้ายบท
นำหน้า Product Landing Page จากตัวอย่างไปปรับใช้กับหัวข้อธุรกิจที่คุณสนใจ โดยต้องมี Header, Navigation, Hero Section, การ์ดอย่างน้อยสามใบ และส่วน Call to Action จากนั้นปรับให้การ์ดเรียงหนึ่งคอลัมน์บนมือถือและหลายคอลัมน์บนจอใหญ่ ทดลองใช้ `clamp()` กับหัวข้อ ตรวจสอบภาพที่มีอัตราส่วนต่างกัน และทดสอบด้วยข้อความที่ยาวกว่าตัวอย่าง
🎯 เกณฑ์ตรวจงานหน้าเว็บต้องไม่เกิดแถบเลื่อนแนวนอนโดยไม่จำเป็น เนื้อหาสำคัญต้องอ่านได้ทุกขนาด ปุ่มต้องเข้าถึงได้ด้วยคีย์บอร์ด รูปภาพต้องไม่ล้นคอนเทนเนอร์ และการ์ดต้องจัดวางใหม่อย่างเหมาะสมโดยไม่เกิดการซ้อนทับ
16. สรุปบทเรียน EP12
Responsive Design ที่ดีเกิดจากการวางโครงสร้างเนื้อหาอย่างรอบคอบและใช้เครื่องมือ CSS ให้เหมาะสม คุณได้เรียนรู้แนวคิด Mobile First การเลือก Breakpoints จากเนื้อหา การปรับขนาดด้วย clamp() การใช้ Grid และ Flexbox การแสดงภาพแบบ Responsive การใช้ Container Queries และการป้องกันองค์ประกอบล้นหน้าจอ เมื่อนำเทคนิคเหล่านี้มารวมกัน คุณจะสร้างเว็บไซต์ที่ยืดหยุ่นและดูแลรักษาได้ง่ายขึ้น
เว็บไซต์ที่ยืดหยุ่นไม่ได้เพียงย่อขนาดตามหน้าจอ แต่จัดลำดับประสบการณ์ใหม่ให้เหมาะกับพื้นที่และความต้องการของผู้ใช้
Reach within 500px of the page end and stay for at least 5 minutes to earn EXP once per day.
⏳ Reading timer is running...