CSS
Transitions & Animations
keyframes
@keyframes ใช้กำหนดลำดับ "ฉากสำคัญ" ของ animation — บอก browser ว่าแต่ละช่วงเวลาควรแสดงผลอย่างไร แล้วใช้ร่วมกับ animation-name เพื่อเชื่อมกับ element
หัวข้อนี้คืออะไร
@keyframes คือ at-rule ของ CSS ที่ใช้กำหนดลำดับการเปลี่ยนแปลงของ animation วิธีคิดง่ายๆ คือ @keyframes เหมือนบทภาพยนตร์ — คุณบอกว่า "ในฉากที่ 1 ให้เป็นแบบนี้ ฉากที่ 2 เป็นแบบนั้น ฉากสุดท้ายเป็นแบบนี้" แล้ว browser จะเชื่อมแต่ละฉากให้เองอย่างนุ่มนวล ต่างจาก transition ตรงที่: transition ทำได้แค่ A → B (2 ค่า ต้องมี trigger เช่น :hover) @keyframes ทำได้ A → B → C → D → ... (หลายค่า เริ่มเองได้ โดยไม่ต้องรอ trigger) โครงสร้างพื้นฐานของ @keyframes:
from/to คือ shorthand ของ 0%/100% — ใช้เมื่อมีแค่จุดเริ่มและจุดจบ เปอร์เซ็นต์ใช้เมื่อต้องการควบคุมกลางทาง
/* แบบ from / to — เริ่มต้นและจบ */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* แบบเปอร์เซ็นต์ — ควบคุมได้ละเอียดกว่า */
@keyframes bounce {
0% { transform: translateY(0); }
40% { transform: translateY(-20px); }
70% { transform: translateY(-8px); }
100% { transform: translateY(0); }
}ทำไมหัวข้อนี้สำคัญ
transition เพียงอย่างเดียวไม่สามารถทำ animation หลายช่วงได้ เช่น การเด้ง การกะพริบ การ slide เข้ามา หรือ loading spinner @keyframes ช่วยให้คุณกำหนดได้ว่า: ช่วงแรก 0–30% ให้ทำอะไร ช่วงกลาง 30–70% ให้ทำอะไร ช่วงสุดท้าย 70–100% ให้ทำอะไร ทำให้สร้าง motion ที่ซับซ้อนและมีเอกลักษณ์ได้ โดยใช้ CSS เพียงอย่างเดียว ไม่ต้องพึ่ง JavaScript ข้อควรระวัง: animation ที่ดีช่วยเสริมการใช้งาน เช่น บอกว่า "กำลังโหลด" หรือ "แจ้งเตือน" animation ที่มากเกินไปหรือวนซ้ำไม่หยุดทำให้ UI น่ารำคาญและเสียสมาธิ
ตัวอย่างจากชีวิตจริง
ลองนึกถึงป้ายไฟกะพริบ: ไฟปกติ (on/off ทันที) — เหมือน CSS ที่ไม่มี animation ไฟที่ค่อยๆ สว่างขึ้นแล้วค่อยๆ หรี่ลง — เหมือน @keyframes ที่ควบคุมแต่ละช่วง ไฟที่สว่างครึ่งแรกเร็ว แล้วหรี่ช้า — เหมือน @keyframes ที่ใช้เปอร์เซ็นต์ควบคุมจังหวะ บน UI จริง: Loading spinner — วนซ้ำด้วย animation-iteration-count: infinite Skeleton loading — กะพริบเพื่อบอกว่า "กำลังโหลด" Toast notification — slide เข้ามาจากด้านล่าง แล้ว fade ออก Badge แจ้งเตือน — เด้งเล็กน้อยเพื่อดึงความสนใจ Hero section — ข้อความ fade in เมื่อหน้าโหลดเสร็จ
แนวคิดหลักที่ต้องเข้าใจ
@keyframes กำหนดสูตร animation แต่ยังไม่ได้ใช้งาน — ต้องเชื่อมกับ element ผ่าน animation-name ขั้นตอนการใช้งานมี 2 ส่วนเสมอ: 1. ประกาศ @keyframes (กำหนด "สูตร" animation) 2. เรียกใช้ผ่าน animation property บน element (เชื่อม "สูตร" กับ element) ดู animation แต่ละแบบ — กดปุ่มดูตัวอย่าง:
| Syntax | หมายความว่า |
|---|---|
| from { … } | สถานะเริ่มต้น (เทียบเท่า 0%) |
| to { … } | สถานะสุดท้าย (เทียบเท่า 100%) |
| 0% { … } | ณ เวลา 0% ของ animation |
| 50% { … } | ณ เวลากึ่งกลาง |
| 100% { … } | ณ เวลาสิ้นสุด animation |
| animation-name | ชื่อ @keyframes ที่จะใช้กับ element นี้ |
การทำงานทีละขั้นตอน
วิธีสร้าง animation ด้วย @keyframes ตั้งแต่ต้น:
- 1. ตั้งชื่อ animation ที่สื่อความหมาย — เช่น fadeIn, slideUp, spin, pulse ชื่อจะถูกใช้ใน animation-name
- 2. กำหนด keyframes — ถ้า animation มีแค่จุดเริ่มและจบ ใช้ from/to ถ้ามีกลางทางด้วย ใช้เปอร์เซ็นต์
- 3. ระบุ property ที่เปลี่ยนในแต่ละ keyframe — เช่น opacity, transform, background-color
- 4. เชื่อมกับ element ด้วย animation property — animation: ชื่อ duration timing-function delay iteration fill-mode
- 5. ตรวจสอบ animation-fill-mode — ถ้าต้องการให้ element ค้างอยู่ที่ค่าสุดท้าย ใช้ forwards หรือ both
ตัวอย่างเชิงเทคนิค — animation property
หลังจากประกาศ @keyframes แล้ว ต้องเชื่อมกับ element ด้วย animation property animation มี shorthand ที่รวมหลาย sub-property เข้าด้วยกัน — ลองแก้ค่าแล้วดูผล:
animation shorthand ลำดับ: name → duration → timing → delay → iteration → fill-mode
/* animation shorthand — ลำดับที่แนะนำ */
.element {
animation: name duration timing-function delay iteration-count fill-mode;
}
/* ตัวอย่างจริง */
.box {
animation: fadeIn 0.5s ease forwards;
}
/* หลาย animation พร้อมกัน — คั่นด้วย comma */
.card {
animation: slideIn 0.4s ease, pulse 2s ease 0.4s infinite;
}
/* sub-properties แยกกัน */
.loader {
animation-name: spin;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}จุดที่ผู้เริ่มต้นมักสับสน
- ประกาศ @keyframes แต่ไม่ได้เชื่อม animation-name — @keyframes เป็นแค่สูตรที่เก็บไว้ ถ้าไม่มี animation-name บน element animation จะไม่เกิดขึ้นเลย
- ลืมระบุ animation-duration — ค่าเริ่มต้นคือ 0s ทำให้ animation จบทันทีก่อนเห็น ต้องระบุ duration ทุกครั้ง
- ชื่อ @keyframes ต้องตรงกับ animation-name — ตัวใหญ่ตัวเล็กมีผล fadeIn ≠ fadein ≠ FadeIn
- animation-fill-mode: forwards ต้องใช้เมื่อต้องการให้ค้างที่ค่าสุดท้าย — ถ้าไม่ใส่ element จะกลับค่าเดิมทันทีเมื่อ animation จบ
- ใช้ animation กับ property ที่ browser animate ได้ — opacity, transform animate ได้ดี แต่ display: none → block ยังทำไม่ได้
เปรียบเทียบ @keyframes กับ transition
| ลักษณะ | @keyframes + animation | transition |
|---|---|---|
| จำนวนช่วง | ได้หลายช่วง (0%, 30%, 70%, 100%) | แค่ 2 ค่า: ก่อน → หลัง |
| trigger | เริ่มเองได้ หรือรอ class change | ต้องมีการเปลี่ยนค่า เช่น :hover |
| loop | กำหนด infinite ได้ | ไม่มี loop |
| ความซับซ้อน | สูงกว่า เหมาะกับ motion หลายขั้น | เรียบง่าย เหมาะกับ state change |
| ใช้กับ | loader, entrance, pulse, bounce | hover, focus, active states |
สรุปท้ายบทแบบจำง่าย
@keyframes = "กำหนดสูตร animation" animation = "เชื่อมสูตรนั้นกับ element" ขาดอันใดอันหนึ่ง animation จะไม่ทำงาน กฎสำคัญ 3 ข้อ:
- ต้องประกาศ @keyframes และระบุ animation-name ให้ตรงกันเสมอ — ชื่อต้องตรงทุกตัวอักษร
- ต้องระบุ animation-duration เสมอ — ค่าเริ่มต้น 0s ทำให้ animation ไม่ปรากฏ
- ใช้ animation-fill-mode: forwards เมื่อต้องการให้ element ค้างที่ค่าสุดท้ายหลัง animation จบ
Lab 1 — สร้าง @keyframes fadeIn
โจทย์: .box ด้านล่างยังไม่มี animation สร้าง @keyframes ชื่อ fadeIn ที่: - เริ่มต้น: opacity: 0 - จบ: opacity: 1 แล้วเพิ่ม animation: fadeIn 1s ease forwards; บน .box
Lab 2 — ใช้ @keyframes กับ element จริง
โจทย์: สร้าง animation ชื่อ slideUp ที่ทำให้ .card เลื่อนขึ้นมาพร้อม fade in: - 0%: opacity: 0, transform: translateY(30px) - 100%: opacity: 1, transform: translateY(0) แล้วเชื่อม animation กับ .card ด้วย duration 0.5s ease forwards
Lab 3 — แก้ animation ที่ประกาศไว้แต่ไม่ทำงาน
โจทย์: โค้ดด้านล่างประกาศ @keyframes spin ไว้แล้ว แต่ animation ไม่ทำงาน หาสาเหตุและแก้ไข — มีข้อผิดพลาด 2 จุด: 1. animation-name ที่ใช้บน .loader ไม่ตรงกับชื่อ @keyframes 2. ไม่ได้ระบุ animation-duration