Article · programming
HTML & CSS EP14: CSS Transitions และ Animations — สร้างเว็บไซต์เคลื่อนไหวอย่างมืออาชีพ
HTML & CSS
บทเรียน HTML และ CSS ตอนที่ 14 เรียนรู้การสร้างเอฟเฟกต์เคลื่อนไหวด้วย CSS Transitions, Keyframes, Animation Properties, Transform, Timing Functions และ Accessibility พร้อมตัวอย่างที่นำไปทดลองและต่อยอดเป็นเว็บไซต์จริงได้
HTML & CSS EP14: CSS Transitions และ Animations — สร้างเว็บไซต์เคลื่อนไหวอย่างมืออาชีพ
ในบทก่อนหน้า เราได้เรียนรู้การจัดวางองค์ประกอบด้วย CSS Grid และการสร้าง Layout ที่รองรับหน้าจอหลากหลายขนาดแล้ว บทนี้จะพาไปอีกขั้นด้วยการเพิ่มการเคลื่อนไหวให้เว็บไซต์ ไม่ว่าจะเป็นปุ่มที่เปลี่ยนสีอย่างนุ่มนวล การ์ดที่ยกตัวขึ้นเมื่อเลื่อนเมาส์ เมนูที่ปรากฏพร้อมเอฟเฟกต์ หรือข้อความที่เคลื่อนไหวเมื่อเปิดหน้าเว็บ ทั้งหมดนี้สามารถทำได้ด้วย CSS โดยไม่จำเป็นต้องเขียน JavaScript สำหรับทุกเอฟเฟกต์
🎯 เป้าหมายของบทเรียนเมื่อจบบทนี้ คุณจะสามารถเลือกใช้ `transition` สำหรับการเปลี่ยนสถานะอย่างนุ่มนวล ใช้ `@keyframes` สำหรับแอนิเมชันที่มีหลายขั้นตอน ควบคุมระยะเวลาและจังหวะการเคลื่อนไหว สร้าง Hover Effects และ Loading Indicators ตลอดจนออกแบบแอนิเมชันที่คำนึงถึงผู้ใช้ที่ไม่ต้องการการเคลื่อนไหวได้
1. ทำความเข้าใจ CSS Transitions และ CSS Animations
**CSS Transition** ใช้เปลี่ยนค่าของ CSS Property จากสถานะหนึ่งไปยังอีกสถานะหนึ่งอย่างต่อเนื่อง โดยทั่วไปจะเกิดเมื่อมีการเปลี่ยนสถานะ เช่น `:hover`, `:focus-visible` หรือเมื่อมีการเพิ่มคลาสจาก JavaScript ส่วน **CSS Animation** ใช้กำหนดลำดับการเคลื่อนไหวผ่าน `@keyframes` ทำให้มีหลายจุดเปลี่ยนสถานะ และสามารถเล่นซ้ำหรือเริ่มทำงานเมื่อโหลดหน้าเว็บได้โดยไม่ต้องอาศัยการ Hover
| คุณสมบัติ | CSS Transition | CSS Animation |
|---|---|---|
| การเริ่มทำงาน | มักเริ่มเมื่อค่า CSS เปลี่ยน | เริ่มตามการกำหนด Animation |
| กำหนดขั้นตอนกลาง | เปลี่ยนจากค่าเริ่มต้นไปยังค่าปลายทาง | กำหนดหลาย Keyframes ได้ |
| การทำซ้ำ | ไม่ทำซ้ำต่อเนื่องโดยตัวมันเอง | กำหนดจำนวนรอบหรือวนไม่สิ้นสุดได้ |
| ตัวอย่างที่เหมาะสม | ปุ่มเปลี่ยนสี การ์ดยกตัว | Loading Spinner การปรากฏขององค์ประกอบ |
2. เริ่มต้นด้วย Transition
คุณสมบัติ `transition` ช่วยให้การเปลี่ยนค่า CSS ไม่เกิดขึ้นทันที แต่ค่อย ๆ เปลี่ยนตามเวลาที่กำหนด รูปแบบพื้นฐานคือ `transition: property duration timing-function delay;` โดย `property` คือคุณสมบัติที่ต้องการเปลี่ยน `duration` คือระยะเวลา `timing-function` คือรูปแบบความเร็ว และ `delay` คือเวลาหน่วงก่อนเริ่ม
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ตัวอย่าง CSS Transition</title>
<style>
body {
font-family: system-ui, sans-serif;
background: #f1f5f9;
padding: 32px;
color: #0f172a;
}
.demo-button {
border: 0;
border-radius: 12px;
padding: 14px 24px;
background: #2563eb;
color: #fff;
font-size: 16px;
cursor: pointer;
transition: background-color 250ms ease, transform 250ms ease, box-shadow 250ms ease;
}
.demo-button:hover {
background: #1d4ed8;
transform: translateY(-3px);
box-shadow: 0 10px 20px rgb(37 99 235 / 22%);
}
.demo-button:focus-visible {
outline: 3px solid #93c5fd;
outline-offset: 4px;
}
</style>
</head>
<body>
<h1>ปุ่มที่ตอบสนองต่อผู้ใช้</h1>
<p>เลื่อนเมาส์เหนือปุ่ม หรือใช้แป้น Tab เพื่อโฟกัส แล้วสังเกตการเปลี่ยนแปลง</p>
<button class="demo-button" type="button">ทดลอง Hover Effect</button>
</body>
</html>
ในตัวอย่างนี้ `background-color` เปลี่ยนสีอย่างนุ่มนวล ส่วน `transform: translateY(-3px)` ทำให้ปุ่มเลื่อนขึ้นเล็กน้อย และ `box-shadow` เพิ่มมิติให้ปุ่ม การระบุ Property ที่ต้องการเปลี่ยนอย่างชัดเจนช่วยให้อ่านโค้ดง่ายกว่าการใช้ `transition: all` โดยไม่จำเป็น
2.1 เลือก Timing Function ให้เหมาะกับการเคลื่อนไหว
Timing Function ควบคุมว่าการเคลื่อนไหวจะเร็วหรือช้าในแต่ละช่วง แม้จะกำหนดระยะเวลาเท่ากัน แต่ผลลัพธ์ที่เห็นอาจแตกต่างกันได้
| ค่า | พฤติกรรม | การใช้งานที่เหมาะสม |
|---|---|---|
| `linear` | เคลื่อนที่ด้วยอัตราคงที่ | การเคลื่อนที่ที่ต้องการความสม่ำเสมอ |
| `ease` | เริ่มและจบอย่างนุ่มนวล | เอฟเฟกต์ทั่วไปในส่วนติดต่อผู้ใช้ |
| `ease-in` | เริ่มช้าแล้วเร็วขึ้น | องค์ประกอบที่กำลังเริ่มเคลื่อนออก |
| `ease-out` | เริ่มเร็วแล้วช้าลง | การปรากฏหรือการหยุดขององค์ประกอบ |
| `ease-in-out` | เริ่มช้า เร็วขึ้น แล้วช้าลง | การเคลื่อนไหวที่ต้องการความนุ่มนวลทั้งสองด้าน |
เคล็ดลับเอฟเฟกต์ของปุ่มและการ์ดมักใช้เวลาประมาณ 150–300 มิลลิวินาทีเป็นจุดเริ่มต้นที่ดี แต่ควรทดลองกับเนื้อหาและอุปกรณ์จริง ระยะเวลาที่เหมาะสมขึ้นอยู่กับบริบท ไม่ใช่ค่าตายตัวสำหรับทุกเว็บไซต์
3. ใช้ Transform สร้างการเคลื่อนไหว
`transform` เป็นเครื่องมือสำคัญสำหรับการเคลื่อนไหวแบบสองมิติ โดยไม่จำเป็นต้องเปลี่ยนตำแหน่งใน Layout โดยตรง คุณสมบัติที่ใช้บ่อย ได้แก่ `translate()` สำหรับเลื่อนตำแหน่ง `scale()` สำหรับปรับขนาด `rotate()` สำหรับหมุน และ `skew()` สำหรับเอียงองค์ประกอบ เมื่อนำมารวมกับ `transition` จะสร้างเอฟเฟกต์ที่ดูเป็นธรรมชาติได้ง่าย
.card {
transition: transform 220ms ease, box-shadow 220ms ease;
}
.card:hover {
transform: translateY(-6px) scale(1.01);
box-shadow: 0 16px 32px rgb(15 23 42 / 14%);
}
.icon {
transition: transform 220ms ease;
}
.icon:hover {
transform: rotate(8deg) scale(1.08);
}
/* ใช้กับองค์ประกอบที่ต้องการหมุน */
.spinner {
transform: rotate(0deg);
}
ควรใช้ `transform` อย่างระมัดระวังและไม่ขยายองค์ประกอบจนบังเนื้อหาข้างเคียง หากใช้เอฟเฟกต์กับการ์ดจำนวนมาก ให้รักษาระดับการยกตัวและเงาให้สม่ำเสมอ เพื่อให้ผู้ใช้เข้าใจว่าองค์ประกอบใดสามารถโต้ตอบได้
4. สร้าง Animation ด้วย @keyframes
`@keyframes` ช่วยกำหนดค่าของ CSS ในจุดต่าง ๆ ของแอนิเมชัน โดยใช้ `from` และ `to` สำหรับจุดเริ่มต้นและจุดสิ้นสุด หรือใช้เปอร์เซ็นต์ เช่น `0%`, `50%` และ `100%` เมื่อต้องการควบคุมหลายช่วง ตัวอย่างเช่น การเลื่อนเข้ามาพร้อมการค่อย ๆ ปรากฏ หรือการหมุนไอคอนโหลดข้อมูล
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.04);
opacity: 0.75;
}
}
.panel {
animation: fade-in-up 500ms ease-out both;
}
.spinner {
animation: spin 900ms linear infinite;
}
.status-dot {
animation: pulse 1600ms ease-in-out infinite;
}
ในตัวอย่าง `fade-in-up` ทำให้องค์ประกอบเริ่มจากตำแหน่งที่ต่ำกว่าเล็กน้อยและมีความโปร่งใสเป็นศูนย์ ก่อนเข้าสู่สถานะปกติ ส่วน `spin` หมุนครบหนึ่งรอบ และ `pulse` ขยายขนาดเล็กน้อยเป็นจังหวะ การใช้ชื่อ Animation ที่สื่อความหมายช่วยให้ทีมพัฒนาเข้าใจวัตถุประสงค์ของแต่ละเอฟเฟกต์ได้ง่าย
4.1 ทำความเข้าใจ Animation Properties
| Property | ความหมาย | ตัวอย่าง |
|---|---|---|
| `animation-name` | ชื่อ Keyframes | `fade-in-up` |
| `animation-duration` | ระยะเวลาต่อรอบ | `500ms` |
| `animation-timing-function` | จังหวะความเร็ว | `ease-out` |
| `animation-delay` | เวลาหน่วงก่อนเริ่ม | `150ms` |
| `animation-iteration-count` | จำนวนรอบ | `1` หรือ `infinite` |
| `animation-direction` | ทิศทางการเล่น | `normal` หรือ `alternate` |
| `animation-fill-mode` | การใช้ค่าก่อนหรือหลังแอนิเมชัน | `both` |
| `animation-play-state` | สถานะการเล่น | `running` หรือ `paused` |
ตัวอย่างการใช้แบบย่อคือ `animation: fade-in-up 500ms ease-out 100ms 1 normal both;` ซึ่งกำหนดชื่อ ระยะเวลา จังหวะความเร็ว เวลาหน่วง จำนวนรอบ ทิศทาง และ Fill Mode ตามลำดับ หากต้องการควบคุมแต่ละค่าชัดเจน สามารถเขียนแยกเป็น Property ได้เช่นกัน
5. สร้าง Loading Spinner และสถานะกำลังทำงาน
เมื่อเว็บไซต์ต้องรอข้อมูลจากเซิร์ฟเวอร์ ผู้ใช้ควรได้รับสัญญาณว่าระบบกำลังทำงานอยู่ Loading Spinner เป็นตัวอย่างของแอนิเมชันที่พบได้บ่อย แต่ควรใช้เฉพาะเมื่อเหมาะสม พร้อมมีข้อความอธิบายสถานะสำหรับเทคโนโลยีช่วยเหลือ และไม่ควรปล่อยให้หมุนค้างโดยไม่มีข้อมูลหรือทางเลือกเมื่อการโหลดล้มเหลว
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Loading Spinner</title>
<style>
body {
font-family: system-ui, sans-serif;
background: #f8fafc;
color: #0f172a;
padding: 28px;
}
.loading-box {
display: flex;
align-items: center;
gap: 14px;
max-width: 340px;
padding: 20px;
background: white;
border: 1px solid #e2e8f0;
border-radius: 14px;
}
.spinner {
width: 28px;
height: 28px;
border: 4px solid #dbeafe;
border-top-color: #2563eb;
border-radius: 50%;
animation: spin 850ms linear infinite;
flex: 0 0 auto;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
.spinner { animation: none; }
}
</style>
</head>
<body>
<h1>สถานะการโหลด</h1>
<div class="loading-box" role="status" aria-live="polite">
<span class="spinner" aria-hidden="true"></span>
<span>กำลังโหลดข้อมูล กรุณารอสักครู่</span>
</div>
</body>
</html>
องค์ประกอบนี้ใช้ `border` สี่ด้านร่วมกับ `border-top-color` เพื่อสร้างส่วนที่มองเห็นเด่นขึ้น จากนั้นหมุนด้วย `@keyframes` โดยใช้ `aria-hidden="true"` กับวงกลมตกแต่ง และให้ข้อความสถานะเป็นส่วนที่เทคโนโลยีช่วยเหลือสามารถรับรู้ได้
6. สร้างการ์ดที่เคลื่อนไหวอย่างมีลำดับ
ในหน้าที่มีการ์ดหลายใบ เช่น รายการสินค้า บริการ หรือบทความ เราอาจต้องการให้การ์ดค่อย ๆ ปรากฏโดยมีระยะหน่วงต่างกัน วิธีหนึ่งคือกำหนดคลาสสำหรับการ์ดและใช้ `animation-delay` เพื่อสร้างลำดับการแสดงผล อย่างไรก็ตาม ไม่ควรใส่ความล่าช้ามากเกินไปจนผู้ใช้ต้องรอเนื้อหาที่จำเป็น
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 18px;
}
.product-card {
padding: 20px;
border: 1px solid #e2e8f0;
border-radius: 16px;
background: #fff;
animation: fade-in-up 450ms ease-out both;
}
.product-card:nth-child(2) {
animation-delay: 80ms;
}
.product-card:nth-child(3) {
animation-delay: 160ms;
}
.product-card:nth-child(4) {
animation-delay: 240ms;
}
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(12px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
ระวังการซ่อนเนื้อหาสำคัญหากแอนิเมชันเริ่มต้นด้วย `opacity: 0` เนื้อหาจะมองไม่เห็นจนกว่าแอนิเมชันจะเริ่มทำงาน ควรหลีกเลี่ยงการพึ่งพาเอฟเฟกต์เพื่อให้เนื้อหาสำคัญปรากฏเพียงอย่างเดียว โดยเฉพาะเมื่อมีการโหลดช้า ปิดแอนิเมชัน หรือใช้เบราว์เซอร์ที่มีข้อจำกัด
7. รองรับผู้ใช้ที่ลดการเคลื่อนไหว
ผู้ใช้บางคนตั้งค่าในระบบปฏิบัติการให้ลดการเคลื่อนไหวด้วยเหตุผลด้านความสะดวกสบายหรือการเข้าถึงเว็บไซต์ เราสามารถตรวจสอบการตั้งค่านี้ผ่าน Media Query `prefers-reduced-motion` และลดหรือยกเลิกแอนิเมชันที่ไม่จำเป็นได้ โดยยังคงข้อมูลและสถานะสำคัญให้เข้าถึงได้ครบถ้วน
@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;
}
}
/* หากองค์ประกอบต้องเคลื่อนไหวเพื่อสื่อสถานะ
ให้แน่ใจว่ามีข้อความหรือสัญญาณอื่นประกอบด้วย */
กฎนี้เป็นแนวทางเริ่มต้นสำหรับลดเอฟเฟกต์ทั่วหน้า แต่ในโปรเจกต์จริงควรทดสอบกับทุกองค์ประกอบ เพราะบางแอนิเมชันอาจสื่อสถานะสำคัญ เช่น การแจ้งว่ากำลังโหลดหรือการเปลี่ยนสถานะของข้อมูล การลดการเคลื่อนไหวไม่ควรทำให้ผู้ใช้สูญเสียข้อมูล
8. ประสิทธิภาพ: เลือก Property ที่เหมาะสม
แอนิเมชันบางประเภททำให้เบราว์เซอร์ต้องคำนวณ Layout หรือวาดภาพใหม่หลายครั้ง ซึ่งอาจทำให้เกิดอาการกระตุก โดยทั่วไป `transform` และ `opacity` เป็นตัวเลือกที่เหมาะสำหรับเอฟเฟกต์เคลื่อนไหวจำนวนมาก แต่ไม่ได้หมายความว่าจะเร็วที่สุดในทุกสถานการณ์ ควรตรวจสอบผลจริงด้วยเครื่องมือ Performance ของเบราว์เซอร์
| Property | ข้อควรพิจารณา | แนวทาง |
|---|---|---|
| `transform` | มักเหมาะกับการเลื่อน หมุน และปรับขนาด | ใช้กับเอฟเฟกต์ที่ไม่จำเป็นต้องเปลี่ยน Layout |
| `opacity` | เหมาะกับการค่อย ๆ ปรากฏหรือจางหาย | ระวังการทำให้องค์ประกอบสำคัญมองไม่เห็น |
| `width` และ `height` | อาจทำให้เกิดการคำนวณ Layout | ใช้เมื่อจำเป็นและทดสอบประสิทธิภาพ |
| `top` และ `left` | อาจกระทบการจัดวางตามบริบท | พิจารณา `transform` สำหรับการเลื่อนภาพ |
| `box-shadow` | การเปลี่ยนแปลงเงาขนาดใหญ่หรือหลายองค์ประกอบอาจมีต้นทุน | ใช้ความเข้มและจำนวนเอฟเฟกต์อย่างพอดี |
อย่าใช้ will-change โดยไม่จำเป็น`will-change` เป็นคำใบ้ให้เบราว์เซอร์เตรียมปรับประสิทธิภาพสำหรับ Property ที่จะเปลี่ยน แต่การใช้กับองค์ประกอบจำนวนมากหรือเปิดทิ้งไว้นานอาจใช้ทรัพยากรเพิ่ม ควรใช้เฉพาะเมื่อมีเหตุผลจากการวัดประสิทธิภาพและนำออกเมื่อไม่จำเป็น
9. ข้อผิดพลาดที่พบบ่อยและวิธีแก้
กำหนด Transition แล้วแต่ไม่มีเอฟเฟกต์
ตรวจสอบว่าค่า CSS เปลี่ยนจริงหรือไม่ เช่น มี `:hover` หรือ `:focus-visible` ถูกเรียกใช้ และ Property ที่กำหนดรองรับการเปลี่ยนค่าแบบต่อเนื่องหรือไม่ อย่าลืมตรวจสอบว่าไม่มี CSS กฎอื่นเขียนทับค่าเดิม
Animation เล่นแล้วไม่คงสถานะสุดท้าย
ตรวจสอบ `animation-fill-mode` หากต้องการให้ค่าจาก Keyframes คงอยู่หลังแอนิเมชัน ให้พิจารณาใช้ `forwards` หรือ `both` ตามความเหมาะสม และตรวจสอบว่าสไตล์พื้นฐานขององค์ประกอบไม่ขัดแย้งกับสถานะที่ต้องการ
แอนิเมชันทำให้หน้าเว็บดูช้าหรือรบกวนผู้ใช้
ลดระยะเวลาและจำนวนองค์ประกอบที่เคลื่อนไหว หลีกเลี่ยงการวนซ้ำแบบไม่มีความจำเป็น เพิ่มทางเลือกสำหรับ `prefers-reduced-motion` และอย่าใช้เอฟเฟกต์เพื่อหน่วงการเข้าถึงเนื้อหาหลัก
การ์ดกระตุกเมื่อมีจำนวนมาก
เริ่มจากลดจำนวนเอฟเฟกต์และใช้ `transform` หรือ `opacity` เมื่อเหมาะสม ตรวจสอบ Performance Timeline และทดสอบบนอุปกรณ์ที่มีประสิทธิภาพต่ำกว่า อย่าตัดสินจากคอมพิวเตอร์ของผู้พัฒนาเพียงเครื่องเดียว
10. Workshop: สร้างหน้าแสดงบริการพร้อม Transition และ Animation
ตัวอย่างต่อไปนี้เป็นหน้าเว็บ HTML แบบเต็มไฟล์ที่รวมแนวคิดจากบทเรียน ได้แก่ Layout แบบ Grid การ์ดบริการที่ยกตัวเมื่อ Hover ปุ่มที่เปลี่ยนสถานะ แอนิเมชันปรากฏตัวแบบสั้น และการรองรับ `prefers-reduced-motion` สามารถคัดลอกโค้ดทั้งหมดไปบันทึกเป็น `index.html` แล้วเปิดในเบราว์เซอร์เพื่อทดลองได้ทันที
<!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="ตัวอย่างหน้าแสดงบริการที่ใช้ CSS Transitions และ Animations">
<title>บริการดิจิทัล | CSS Motion Workshop</title>
<style>
:root {
color-scheme: light;
--primary: #2563eb;
--primary-dark: #1d4ed8;
--ink: #0f172a;
--muted: #475569;
--surface: #ffffff;
--page: #f1f5f9;
--border: #dbe4f0;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
line-height: 1.65;
color: var(--ink);
background: var(--page);
}
.site-header {
background: var(--surface);
border-bottom: 1px solid var(--border);
}
.header-inner,
main,
.site-footer {
width: min(1100px, calc(100% - 32px));
margin-inline: auto;
}
.header-inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding-block: 16px;
}
.brand {
margin: 0;
font-size: 1.1rem;
font-weight: 800;
letter-spacing: -0.02em;
}
.nav-link {
color: var(--primary-dark);
font-weight: 650;
text-decoration-thickness: 2px;
text-underline-offset: 4px;
}
main {
padding-block: 48px 64px;
}
.hero {
max-width: 720px;
margin-bottom: 32px;
animation: fade-in-up 450ms ease-out both;
}
.eyebrow {
margin: 0 0 8px;
color: var(--primary-dark);
font-size: 0.82rem;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
}
h1 {
margin: 0 0 14px;
font-size: clamp(2rem, 5vw, 3.5rem);
line-height: 1.12;
letter-spacing: -0.04em;
}
.hero p:last-child {
margin: 0;
color: var(--muted);
font-size: 1.05rem;
}
.service-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 230px), 1fr));
gap: 18px;
}
.service-card {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 24px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 18px;
box-shadow: 0 4px 12px rgb(15 23 42 / 4%);
animation: fade-in-up 500ms ease-out both;
transition: transform 220ms ease, box-shadow 220ms ease, border-color 220ms ease;
}
.service-card:nth-child(2) {
animation-delay: 70ms;
}
.service-card:nth-child(3) {
animation-delay: 140ms;
}
.service-card:nth-child(4) {
animation-delay: 210ms;
}
.service-card:hover {
transform: translateY(-5px);
border-color: #93c5fd;
box-shadow: 0 16px 32px rgb(15 23 42 / 10%);
}
.service-card:focus-within {
outline: 3px solid #bfdbfe;
outline-offset: 3px;
}
.service-icon {
display: grid;
width: 48px;
height: 48px;
place-items: center;
margin-bottom: 16px;
border-radius: 14px;
background: #dbeafe;
font-size: 1.5rem;
transition: transform 220ms ease;
}
.service-card:hover .service-icon {
transform: rotate(-5deg) scale(1.06);
}
.service-card h2 {
margin: 0 0 8px;
font-size: 1.2rem;
}
.service-card p {
margin: 0 0 20px;
color: var(--muted);
}
.button {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 44px;
margin-top: auto;
padding: 10px 16px;
border: 0;
border-radius: 10px;
background: var(--primary);
color: white;
font: inherit;
font-weight: 750;
text-decoration: none;
cursor: pointer;
transition: background-color 180ms ease, transform 180ms ease;
}
.button:hover {
background: var(--primary-dark);
transform: translateY(-2px);
}
.button:focus-visible,
.nav-link:focus-visible {
outline: 3px solid #93c5fd;
outline-offset: 4px;
}
.notice {
margin-top: 28px;
padding: 18px 20px;
border: 1px solid #bfdbfe;
border-radius: 14px;
background: #eff6ff;
color: #1e3a8a;
}
.site-footer {
padding-block: 22px;
border-top: 1px solid var(--border);
color: var(--muted);
font-size: 0.9rem;
}
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(12px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@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;
}
.service-card:hover,
.service-card:hover .service-icon,
.button:hover {
transform: none;
}
}
</style>
</head>
<body>
<header class="site-header">
<div class="header-inner">
<p class="brand">Motion Studio</p>
<a class="nav-link" href="#services">บริการของเรา</a>
</div>
</header>
<main>
<section class="hero" aria-labelledby="page-title">
<p class="eyebrow">ออกแบบเพื่อธุรกิจยุคใหม่</p>
<h1 id="page-title">บริการดิจิทัลที่ทำให้ธุรกิจเติบโต</h1>
<p>ทดลองเอฟเฟกต์ด้วย CSS Transitions และ Animations ที่ช่วยให้ส่วนติดต่อผู้ใช้ตอบสนองอย่างนุ่มนวล โดยไม่ลดทอนความชัดเจนของเนื้อหา</p>
</section>
<section id="services" class="service-grid" aria-label="รายการบริการ">
<article class="service-card">
<div class="service-icon" aria-hidden="true">✦</div>
<h2>ออกแบบเว็บไซต์</h2>
<p>สร้างหน้าเว็บที่อ่านง่าย รองรับหลายขนาดหน้าจอ และมีรูปแบบสอดคล้องกับแบรนด์</p>
<a class="button" href="#contact">ดูรายละเอียด</a>
</article>
<article class="service-card">
<div class="service-icon" aria-hidden="true">⌘</div>
<h2>พัฒนาระบบ</h2>
<p>ออกแบบฟังก์ชันสำหรับงานธุรกิจ โดยคำนึงถึงการใช้งานจริงและการดูแลระยะยาว</p>
<a class="button" href="#contact">ดูรายละเอียด</a>
</article>
<article class="service-card">
<div class="service-icon" aria-hidden="true">▣</div>
<h2>ประสบการณ์ผู้ใช้</h2>
<p>ปรับปรุงการนำทาง สถานะปุ่ม และการตอบสนองให้เข้าใจได้ง่ายยิ่งขึ้น</p>
<a class="button" href="#contact">ดูรายละเอียด</a>
</article>
<article class="service-card">
<div class="service-icon" aria-hidden="true">↗</div>
<h2>วิเคราะห์ข้อมูล</h2>
<p>จัดระเบียบข้อมูลและนำเสนอสิ่งที่จำเป็นต่อการตัดสินใจของทีม</p>
<a class="button" href="#contact">ดูรายละเอียด</a>
</article>
</section>
<aside class="notice" id="contact">
<strong>ทดลองใช้งาน:</strong> เลื่อนเมาส์เหนือการ์ดและปุ่ม หรือใช้แป้น Tab เพื่อทดสอบการโฟกัส เอฟเฟกต์จะลดลงเมื่อระบบของผู้ใช้ตั้งค่าให้ลดการเคลื่อนไหว
</aside>
</main>
<footer class="site-footer">
<p>ตัวอย่างเพื่อการเรียนรู้ HTML และ CSS — ปรับแต่งข้อความ สี และระยะเวลาได้ตามต้องการ</p>
</footer>
</body>
</html>
11. แบบฝึกหัดท้ายบท
ลองแก้ไขตัวอย่างหน้าเว็บด้วยตัวเอง โดยเปลี่ยนค่าต่าง ๆ แล้วสังเกตผลลัพธ์ก่อนและหลังแก้ไข การฝึกจากการทดลองจะช่วยให้เข้าใจความสัมพันธ์ระหว่าง Property และพฤติกรรมของแอนิเมชันได้ดีกว่าการจำรูปแบบโค้ดเพียงอย่างเดียว
แบบฝึกหัดที่ 1: ปรับความเร็วปุ่ม
เปลี่ยนระยะเวลา Transition ของปุ่มจาก 180ms เป็น 400ms แล้วทดลองเปรียบเทียบว่าการตอบสนองรู้สึกแตกต่างกันอย่างไร จากนั้นลองใช้ `ease-in-out` และ `linear` โดยคงระยะเวลาเท่าเดิม
แบบฝึกหัดที่ 2: สร้าง Animation ใหม่
เขียน `@keyframes` ชื่อ `slide-in` ให้กล่องเริ่มจาก `transform: translateX(-20px)` และ `opacity: 0` ก่อนจบที่ `translateX(0)` และ `opacity: 1` แล้วนำไปใช้กับองค์ประกอบที่เหมาะสม
แบบฝึกหัดที่ 3: ปรับการ์ดสินค้า
เพิ่มการ์ดอีกสองใบลงใน Grid ปรับ `gap` ให้เหมาะสม และทดลองกำหนด `animation-delay` ให้แต่ละใบแตกต่างกัน โดยต้องแน่ใจว่าการหน่วงไม่ทำให้ผู้ใช้ต้องรอเนื้อหาสำคัญ
แบบฝึกหัดที่ 4: ตรวจสอบ Accessibility
เปิดการตั้งค่า Reduce Motion ในระบบปฏิบัติการหรือเครื่องมือจำลองของเบราว์เซอร์ แล้วตรวจสอบว่าเอฟเฟกต์ที่ไม่จำเป็นลดลงหรือไม่ และเนื้อหายังเข้าถึงได้ครบถ้วน
12. สรุปบทเรียน
CSS Transitions เหมาะกับการเปลี่ยนสถานะอย่างนุ่มนวล ส่วน CSS Animations และ `@keyframes` เหมาะกับการกำหนดการเคลื่อนไหวหลายช่วงหรือการเล่นซ้ำ การใช้ `transform` และ `opacity` มักเป็นจุดเริ่มต้นที่ดีสำหรับเอฟเฟกต์ทั่วไป แต่ควรทดสอบประสิทธิภาพจริง เลือกระยะเวลาให้เหมาะสม และรองรับผู้ใช้ที่ต้องการลดการเคลื่อนไหวเสมอ
Checklist ก่อนนำขึ้นเว็บไซต์จริงตรวจสอบว่าเอฟเฟกต์ช่วยสื่อสารสถานะ ไม่รบกวนการอ่าน มีสถานะ Focus สำหรับผู้ใช้คีย์บอร์ด ไม่ซ่อนเนื้อหาสำคัญ ไม่ทำงานวนซ้ำโดยไม่มีเหตุผล และรองรับ `prefers-reduced-motion` พร้อมทดสอบทั้งหน้าจอเล็กและใหญ่
แอนิเมชันที่ดีไม่ใช่การทำให้ทุกอย่างเคลื่อนไหว แต่คือการทำให้การเปลี่ยนแปลงที่สำคัญเข้าใจง่ายขึ้น โดยไม่ขวางทางผู้ใช้
ดูเอกสาร CSS Transitions จาก MDN
ดูเอกสาร CSS Animations จาก MDN
บทถัดไป EP15: จะต่อยอดจากการสร้างเอฟเฟกต์ด้วย CSS ไปสู่การออกแบบองค์ประกอบส่วนติดต่อผู้ใช้ที่นำกลับมาใช้ซ้ำได้ การจัดการสถานะของ UI และการวางโครงสร้างหน้าเว็บให้ดูแลรักษาง่ายขึ้น
อ่านจนถึงระยะ 500px ก่อนท้ายหน้าและอยู่ในหน้านี้อย่างน้อย 5 นาที เพื่อรับ EXP วันละครั้ง
⏳ กำลังจับเวลาอ่าน...