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

HTML & CSS EP13: CSS Transitions, Transforms และ Animations — สร้างเว็บไซต์ที่มีชีวิตชีวา

HTML & CSS

บทเรียน HTML และ CSS ตอนที่ 13 เรียนรู้การสร้างเอฟเฟกต์และภาพเคลื่อนไหวบนเว็บไซต์ด้วย CSS Transitions, Transforms, Keyframe Animations, Hover States, Focus States และ Reduced Motion พร้อมแนวทางเพิ่มประสิทธิภาพและตัวอย่างหน้าเว็บ Interactive Product Showcase แบบ HTML เต็มหน้าที่สามารถทดลองได้ทันที

HTML & CSS EP13: CSS Transitions, Transforms และ Animations

จากบทเรียนก่อนหน้าที่เราเรียนรู้การจัดวางเว็บไซต์ด้วย CSS Grid, Flexbox และ Responsive Design ตอนนี้เราจะเพิ่มอีกหนึ่งองค์ประกอบสำคัญให้เว็บไซต์ นั่นคือการตอบสนองต่อการกระทำของผู้ใช้และการเคลื่อนไหวที่ช่วยสื่อสารข้อมูลได้ชัดเจนขึ้น ไม่ว่าจะเป็นปุ่มที่เปลี่ยนสีอย่างนุ่มนวล การ์ดสินค้าที่ขยับเล็กน้อยเมื่อเลื่อนเมาส์เหนือ หรือแถบแจ้งเตือนที่ค่อย ๆ ปรากฏขึ้น
CSS สามารถสร้างการเคลื่อนไหวได้โดยไม่จำเป็นต้องเขียน JavaScript ทุกครั้ง เราจะใช้ `transition` สำหรับการเปลี่ยนค่าระหว่างสถานะ ใช้ `transform` เพื่อเปลี่ยนตำแหน่ง หมุน หรือปรับขนาดองค์ประกอบ และใช้ `@keyframes` เพื่อกำหนดลำดับการเคลื่อนไหวหลายช่วงอย่างเป็นระบบ
💡 สิ่งที่คุณจะทำได้หลังเรียนจบสร้างปุ่มที่ตอบสนองอย่างนุ่มนวล การ์ดสินค้าที่มี Hover Effect แอนิเมชันเปิดเผยเนื้อหา การเคลื่อนไหวแบบวนซ้ำ และหน้าแสดงสินค้าแบบ Interactive โดยคำนึงถึงประสิทธิภาพ การใช้งานด้วยคีย์บอร์ด และผู้ใช้ที่ต้องการลดการเคลื่อนไหว

1. เข้าใจความแตกต่างระหว่าง Transition, Transform และ Animation

ทั้งสามแนวคิดทำงานร่วมกันได้ แต่มีหน้าที่แตกต่างกัน `transition` กำหนดวิธีเปลี่ยนจากค่าหนึ่งไปสู่อีกค่าหนึ่งในช่วงเวลาที่กำหนด `transform` ใช้เปลี่ยนรูปทรงหรือตำแหน่งที่แสดงผลขององค์ประกอบ และ `animation` ใช้เล่นลำดับการเปลี่ยนแปลงที่กำหนดด้วย `@keyframes` ซึ่งสามารถทำงานโดยไม่ต้องมีการ Hover หรือคลิกก็ได้
คุณสมบัติหน้าที่ตัวอย่างการใช้งาน
transitionเปลี่ยนค่าระหว่างสถานะอย่างต่อเนื่องปุ่มค่อย ๆ เปลี่ยนสีเมื่อ Hover
transformเปลี่ยนตำแหน่ง หมุน ย่อ หรือขยายการ์ดเลื่อนขึ้นเล็กน้อย
@keyframesกำหนดลำดับการเปลี่ยนแปลงหลายช่วงจุดแจ้งเตือนเต้นเป็นจังหวะ
animationกำหนดการเล่น Keyframes ระยะเวลา และจำนวนรอบแสดงเอฟเฟกต์ปรากฏขึ้นเมื่อโหลดหน้า

2. เริ่มต้นด้วย CSS Transition

Transition เหมาะกับการเปลี่ยนสถานะที่มีจุดเริ่มต้นและจุดสิ้นสุดชัดเจน เช่น สีพื้นหลัง ความทึบ เงา หรือการเลื่อนตำแหน่ง เมื่อผู้ใช้เลื่อนเมาส์ออกจากปุ่ม เบราว์เซอร์สามารถเปลี่ยนกลับสู่สถานะเดิมได้โดยอัตโนมัติ หากกำหนด Transition ไว้ในสถานะพื้นฐาน
<button class="button" type="button">ทดลองเลื่อนเมาส์</button>

<style>
  .button {
    padding: 12px 20px;
    border: 0;
    border-radius: 10px;
    background: #2563eb;
    color: white;
    font: inherit;
    cursor: pointer;
    transition: background-color 200ms ease,
                transform 200ms ease,
                box-shadow 200ms ease;
  }

  .button:hover {
    background: #1d4ed8;
    transform: translateY(-2px);
    box-shadow: 0 8px 18px rgb(37 99 235 / 20%);
  }
</style>
ในตัวอย่างนี้ `transition` มีการระบุทั้งคุณสมบัติ ระยะเวลา และ Timing Function โดยใช้ `ease` เพื่อให้การเปลี่ยนแปลงเริ่มและจบอย่างนุ่มนวล การระบุเฉพาะคุณสมบัติที่ต้องการเปลี่ยนยังช่วยให้โค้ดชัดเจนกว่าการใช้ `transition: all` ซึ่งอาจทำให้คุณสมบัติอื่นเปลี่ยนตามโดยไม่ตั้งใจ

2.1 โครงสร้างของ Transition

รูปแบบทั่วไปคือ `transition: property duration timing-function delay;` โดย `property` คือคุณสมบัติที่ต้องการเปลี่ยน `duration` คือระยะเวลาของการเปลี่ยน `timing-function` คือรูปแบบความเร็วระหว่างการเปลี่ยน และ `delay` คือเวลารอก่อนเริ่ม ตัวอย่างเช่น `transition: opacity 250ms ease-in-out 100ms;` จะเปลี่ยนความทึบภายใน 250 มิลลิวินาทีหลังจากรอ 100 มิลลิวินาที
Timing Functionลักษณะการเคลื่อนไหวเหมาะกับ
linearความเร็วคงที่การเคลื่อนไหวที่ต้องการอัตราสม่ำเสมอ
easeเริ่มและจบอย่างนุ่มนวลเอฟเฟกต์ทั่วไปบน UI
ease-inเริ่มช้าแล้วเร็วขึ้นองค์ประกอบที่เริ่มเคลื่อนออก
ease-outเริ่มเร็วแล้วช้าลงองค์ประกอบที่กำลังเข้าสู่ตำแหน่ง
ease-in-outเริ่มช้า เร็วขึ้น แล้วช้าลงการเปลี่ยนสถานะที่ต้องการความนุ่มนวลทั้งสองด้าน

3. ใช้ Transform เพื่อเคลื่อนย้าย หมุน และปรับขนาด

คุณสมบัติ `transform` ใช้ปรับลักษณะการแสดงผลขององค์ประกอบ โดยไม่ต้องเปลี่ยนตำแหน่งปกติขององค์ประกอบอื่นในเอกสารเหมือนการแก้ Layout โดยตรง จึงเหมาะกับเอฟเฟกต์ที่ต้องการให้การ์ดขยับหรือปุ่มขยายเล็กน้อยโดยไม่ดันเนื้อหาข้างเคียง
.move-card:hover {
  transform: translateY(-6px);
}

.zoom-image:hover {
  transform: scale(1.04);
}

.rotate-icon:hover {
  transform: rotate(12deg);
}

.shift-and-scale:hover {
  transform: translateY(-4px) scale(1.02);
}

.flip-icon {
  transform: rotate(180deg);
}
`translate()` ใช้เลื่อนตำแหน่ง `scale()` ใช้ย่อหรือขยาย `rotate()` ใช้หมุน และ `skew()` ใช้เอียงองค์ประกอบได้ หากใช้หลายฟังก์ชันในค่าเดียวกัน ควรตรวจสอบลำดับของฟังก์ชัน เพราะลำดับการแปลงอาจทำให้ผลลัพธ์ต่างกัน ควรใช้การขยายอย่างพอเหมาะเพื่อไม่ให้การ์ดชนกับองค์ประกอบข้างเคียง
⚠️ Transform ไม่ได้เปลี่ยนพื้นที่ Layout เดิมเมื่อใช้ `transform: scale()` องค์ประกอบอาจดูใหญ่ขึ้น แต่พื้นที่ที่สงวนไว้ใน Layout ยังคงเป็นขนาดเดิม จึงอาจทับซ้อนกับสิ่งรอบข้างได้ ควรเผื่อพื้นที่และทดสอบการแสดงผลบนหน้าจอหลายขนาด

4. สร้าง Hover Effects ให้การ์ดสินค้า

การ์ดสินค้ามักใช้เอฟเฟกต์เพื่อช่วยสื่อว่ารายการนั้นสามารถเปิดดูรายละเอียดได้ เราสามารถเปลี่ยนเงา ยกการ์ดขึ้นเล็กน้อย หรือขยายภาพภายในกรอบ โดยไม่จำเป็นต้องใช้ JavaScript สำหรับการเปลี่ยนสถานะเมื่อเมาส์อยู่เหนือองค์ประกอบ
.product-card {
  overflow: hidden;
  border: 1px solid #e2e8f0;
  border-radius: 16px;
  background: white;
  box-shadow: 0 4px 12px rgb(15 23 42 / 5%);
  transition: transform 220ms ease,
              box-shadow 220ms ease,
              border-color 220ms ease;
}

.product-card:hover {
  transform: translateY(-5px);
  border-color: #93c5fd;
  box-shadow: 0 16px 32px rgb(15 23 42 / 12%);
}

.product-card__image {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  transition: transform 300ms ease;
}

.product-card:hover .product-card__image {
  transform: scale(1.04);
}
การใช้ `overflow: hidden` กับกรอบภาพช่วยตัดส่วนภาพที่ขยายเกินขอบ แต่ต้องระวังไม่ใช้กับคอนเทนเนอร์ที่มีเมนูหรือ Focus Outline ซึ่งจำเป็นต้องแสดงออกมานอกกรอบ ในตัวอย่างจริงควรแยกกรอบภาพออกจากเนื้อหาการ์ดเพื่อควบคุมการตัดภาพได้ตรงจุด

5. ใช้ Keyframes เพื่อกำหนดลำดับการเคลื่อนไหว

เมื่อการเคลื่อนไหวมีหลายช่วงหรือควรเล่นเองโดยไม่ต้องรอ Hover ให้ใช้ `@keyframes` โดยกำหนดสถานะเริ่มต้นด้วย `from` และสถานะสุดท้ายด้วย `to` หรือระบุเป็นเปอร์เซ็นต์ เช่น `0%`, `50%` และ `100%` เพื่อควบคุมลำดับการเปลี่ยนแปลงได้ละเอียดขึ้น
@keyframes fade-in-up {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.intro-panel {
  animation: fade-in-up 500ms ease-out both;
}
คำสั่ง `animation` ในตัวอย่างระบุชื่อ Keyframes ระยะเวลา และ Timing Function ส่วน `both` ใน `animation-fill-mode` ทำให้เบราว์เซอร์ใช้ค่าจาก Keyframes ทั้งในช่วงรอเริ่มและหลังจบตามที่กำหนด ควรตรวจสอบค่าเริ่มต้นขององค์ประกอบด้วย เพื่อไม่ให้เกิดการกระพริบก่อนแอนิเมชันเริ่มทำงาน

5.1 ควบคุมจำนวนรอบและทิศทาง

คุณสามารถใช้ `animation-iteration-count` เพื่อกำหนดจำนวนรอบ หรือใช้ค่า `infinite` เพื่อวนซ้ำอย่างต่อเนื่อง และใช้ `animation-direction` เพื่อกำหนดทิศทางการเล่น เช่น `alternate` ทำให้รอบถัดไปเล่นย้อนทิศทาง เหมาะกับเอฟเฟกต์ขยับขึ้นลงเล็กน้อย แต่ไม่ควรใช้การเคลื่อนไหววนซ้ำกับองค์ประกอบจำนวนมากโดยไม่มีเหตุผล
@keyframes gentle-pulse {
  0%, 100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.05);
  }
}

.status-dot {
  animation: gentle-pulse 1.8s ease-in-out infinite;
}

.float-icon {
  animation: float-up-down 2.5s ease-in-out infinite alternate;
}

@keyframes float-up-down {
  from { transform: translateY(0); }
  to { transform: translateY(-8px); }
}
⚠️ ใช้แอนิเมชันวนซ้ำอย่างมีเหตุผลการเคลื่อนไหวต่อเนื่องอาจรบกวนสมาธิ ทำให้หน้าเว็บดูวุ่นวาย หรือสร้างความไม่สบายให้ผู้ใช้บางคน ควรใช้กับข้อมูลที่มีความหมาย เช่น สถานะกำลังทำงาน และเปิดโอกาสให้ลดหรือหยุดการเคลื่อนไหวเมื่อไม่จำเป็น

6. สร้างลำดับแอนิเมชันด้วย Delay

หากต้องการให้รายการหลายชิ้นปรากฏต่อกัน สามารถใช้ `animation-delay` เพื่อหน่วงเวลาแต่ละรายการ วิธีนี้ช่วยนำสายตาไปตามลำดับของเนื้อหาได้ แต่ควรจำกัดช่วงเวลารวมให้สั้นและหลีกเลี่ยงการทำให้ผู้ใช้ต้องรอนานก่อนเข้าถึงข้อมูลหลัก
.feature-item {
  animation: fade-in-up 450ms ease-out both;
}

.feature-item:nth-child(2) {
  animation-delay: 100ms;
}

.feature-item:nth-child(3) {
  animation-delay: 200ms;
}

.feature-item:nth-child(4) {
  animation-delay: 300ms;
}
วิธีนี้เรียกว่า Staggered Animation โดยแต่ละรายการเริ่มในเวลาที่ต่างกันเล็กน้อย เหมาะกับกลุ่มการ์ดหรือรายการคุณสมบัติ แต่ถ้ารายการมีจำนวนมาก ควรใช้เอฟเฟกต์อย่างระมัดระวังและพิจารณาว่าจำเป็นต้องทำให้ทุกรายการเคลื่อนไหวหรือไม่

7. ใช้ Hover, Focus และ Active อย่างครอบคลุม

เว็บไซต์ที่ใช้ Hover Effect ควรออกแบบสถานะสำหรับผู้ใช้คีย์บอร์ดด้วย เพราะผู้ใช้บางคนไม่ใช้เมาส์ การกำหนด `:focus-visible` ช่วยแสดงตัวบ่งชี้โฟกัสเมื่อต้องใช้งานผ่านคีย์บอร์ด ส่วน `:active` ใช้แสดงสถานะขณะกดปุ่ม การเปลี่ยนสถานะเหล่านี้ควรช่วยให้เข้าใจการตอบสนองโดยไม่ทำให้การใช้งานสับสน
.action-button {
  transition: background-color 180ms ease,
              transform 180ms ease;
}

.action-button:hover {
  background-color: #1d4ed8;
}

.action-button:focus-visible {
  outline: 3px solid #93c5fd;
  outline-offset: 4px;
}

.action-button:active {
  transform: translateY(1px);
}

@media (hover: none) {
  .action-button:hover {
    background-color: #2563eb;
  }
}
Media Feature `hover: none` ช่วยตรวจสอบว่าอุปกรณ์หลักไม่มีความสามารถ Hover แบบแม่นยำ แต่ไม่ควรใช้เพื่อลบข้อมูลหรือฟังก์ชันที่ผู้ใช้จำเป็นต้องเข้าถึง ให้แน่ใจว่าข้อมูลสำคัญไม่ถูกแสดงเฉพาะเมื่อเลื่อนเมาส์เหนือองค์ประกอบเท่านั้น

8. เคารพการตั้งค่าลดการเคลื่อนไหวของผู้ใช้

ระบบปฏิบัติการและอุปกรณ์บางประเภทเปิดให้ผู้ใช้ตั้งค่าลดการเคลื่อนไหวเพื่อลดความไม่สบายจากแอนิเมชัน เว็บไซต์สามารถตรวจสอบการตั้งค่านี้ด้วย Media Query `prefers-reduced-motion` แล้วลดหรือปิดแอนิเมชันที่ไม่จำเป็น โดยยังคงรักษาข้อมูลและความหมายขององค์ประกอบไว้
@media (prefers-reduced-motion: reduce) {
  .decorative-animation,
  .status-dot,
  .float-icon {
    animation: none;
  }

  .product-card,
  .product-card__image,
  .action-button {
    transition-duration: 0.01ms;
  }

  html {
    scroll-behavior: auto;
  }
}
✅ หลักการสำคัญด้าน Accessibilityการลดการเคลื่อนไหวไม่ควรทำให้เนื้อหาหายไป หากองค์ประกอบใช้ Animation เพื่อเปิดเผยข้อมูล ต้องออกแบบให้ข้อมูลยังมองเห็นหรือเข้าถึงได้เมื่อปิด Animation ด้วย

9. เลือกคุณสมบัติที่เหมาะสมเพื่อประสิทธิภาพ

การเคลื่อนไหวบางชนิดอาจทำให้เบราว์เซอร์ต้องคำนวณ Layout หรือวาดภาพใหม่บ่อยครั้ง โดยเฉพาะเมื่อเปลี่ยนคุณสมบัติที่กระทบขนาดและตำแหน่งขององค์ประกอบจำนวนมาก สำหรับเอฟเฟกต์ทั่วไป ควรพิจารณาใช้ `transform` และ `opacity` ก่อน เพราะมักเหมาะกับการทำภาพเคลื่อนไหวที่มีประสิทธิภาพ อย่างไรก็ตาม ประสิทธิภาพจริงขึ้นอยู่กับหน้าเว็บ อุปกรณ์ และเบราว์เซอร์ จึงควรทดสอบกับงานจริง
คุณสมบัติการใช้งานแนวทาง
transformเลื่อน หมุน หรือขยายองค์ประกอบเหมาะกับเอฟเฟกต์การเคลื่อนไหวหลายประเภท
opacityค่อย ๆ ปรากฏหรือจางหายใช้ร่วมกับ transform ได้
width และ heightปรับขนาด Layoutอาจทำให้ต้องคำนวณ Layout ใหม่
top และ leftเปลี่ยนตำแหน่งตาม Layoutพิจารณาใช้ transform เมื่อเป็นการเคลื่อนไหวเพื่อการแสดงผล
box-shadowเปลี่ยนความลึกขององค์ประกอบใช้พอเหมาะและทดสอบบนอุปกรณ์เป้าหมาย
อย่าเพิ่ม `will-change` ให้ทุกองค์ประกอบโดยอัตโนมัติ เพราะอาจใช้ทรัพยากรหน่วยความจำมากเกินความจำเป็น ควรใช้เมื่อมีหลักฐานจากการวัดประสิทธิภาพว่าจำเป็นจริง และถอดออกเมื่อไม่ต้องการแล้ว

10. สร้าง Interactive Product Showcase แบบเต็มหน้า

ตัวอย่างต่อไปนี้เป็นหน้า HTML แบบเต็มสำหรับทดลองในเบราว์เซอร์ มีส่วนหัว การ์ดสินค้า เอฟเฟกต์ Hover แอนิเมชันปรากฏตัว และปุ่มที่เปลี่ยนสถานะข้อความเมื่อคลิก โค้ดใช้ HTML, CSS และ JavaScript ขนาดเล็กเฉพาะส่วนที่ต้องตอบสนองต่อการคลิก ส่วนการเคลื่อนไหวหลักใช้ CSS
<!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, Transforms และ Animations">
  <title>Interactive Product Showcase</title>
  <style>
    * { box-sizing: border-box; }
    :root {
      --brand: #4f46e5;
      --brand-dark: #3730a3;
      --ink: #172033;
      --muted: #64748b;
      --surface: #ffffff;
      --page: #f5f7ff;
      --border: #e2e8f0;
    }
    body {
      margin: 0;
      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      color: var(--ink);
      background: var(--page);
      line-height: 1.6;
    }
    .container {
      width: min(100% - 32px, 1100px);
      margin-inline: auto;
    }
    .header {
      background: var(--surface);
      border-bottom: 1px solid var(--border);
    }
    .nav {
      display: flex;
      align-items: center;
      justify-content: space-between;
      flex-wrap: wrap;
      gap: 12px;
      padding-block: 18px;
    }
    .brand {
      margin: 0;
      font-size: 1.25rem;
      font-weight: 850;
      letter-spacing: -.03em;
    }
    .brand span { color: var(--brand); }
    .nav a {
      color: var(--muted);
      text-decoration: none;
    }
    .nav a:hover { color: var(--brand); }
    .hero {
      padding-block: clamp(48px, 8vw, 84px) 40px;
      text-align: center;
      animation: fade-up 550ms ease-out both;
    }
    .eyebrow {
      display: inline-block;
      padding: 6px 12px;
      border-radius: 999px;
      background: #e0e7ff;
      color: var(--brand-dark);
      font-size: .85rem;
      font-weight: 750;
    }
    h1 {
      max-width: 760px;
      margin: 18px auto 12px;
      font-size: clamp(2rem, 1.2rem + 3vw, 3.5rem);
      line-height: 1.15;
      letter-spacing: -.04em;
    }
    .hero p {
      max-width: 660px;
      margin: 0 auto;
      color: var(--muted);
    }
    .product-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
      gap: 20px;
      padding-bottom: 48px;
    }
    .product-card {
      display: flex;
      flex-direction: column;
      min-width: 0;
      padding: 22px;
      border: 1px solid var(--border);
      border-radius: 20px;
      background: var(--surface);
      box-shadow: 0 6px 20px rgb(15 23 42 / 4%);
      animation: fade-up 550ms ease-out both;
      transition: transform 220ms ease, box-shadow 220ms ease, border-color 220ms ease;
    }
    .product-card:nth-child(2) { animation-delay: 100ms; }
    .product-card:nth-child(3) { animation-delay: 200ms; }
    .product-card:hover {
      transform: translateY(-6px);
      border-color: #a5b4fc;
      box-shadow: 0 18px 36px rgb(79 70 229 / 12%);
    }
    .product-art {
      display: grid;
      place-items: center;
      min-height: 150px;
      margin-bottom: 18px;
      overflow: hidden;
      border-radius: 14px;
      background: linear-gradient(135deg, #e0e7ff, #f5d0fe);
      font-size: 3.5rem;
    }
    .product-card:nth-child(2) .product-art { background: linear-gradient(135deg, #d1fae5, #dbeafe); }
    .product-card:nth-child(3) .product-art { background: linear-gradient(135deg, #fef3c7, #fce7f3); }
    .product-art span {
      display: inline-block;
      transition: transform 280ms ease;
    }
    .product-card:hover .product-art span { transform: scale(1.15) rotate(-4deg); }
    .product-card h2 { margin: 0; font-size: 1.25rem; }
    .product-card p { color: var(--muted); margin: 10px 0 18px; }
    .price { margin-top: auto; margin-bottom: 16px; font-size: 1.4rem; font-weight: 850; }
    .button {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      min-height: 44px;
      width: 100%;
      padding: 10px 16px;
      border: 0;
      border-radius: 11px;
      background: var(--brand);
      color: white;
      font: inherit;
      font-weight: 750;
      cursor: pointer;
      transition: background-color 180ms ease, transform 180ms ease;
    }
    .button:hover { background: var(--brand-dark); }
    .button:active { transform: translateY(1px); }
    .button:focus-visible {
      outline: 3px solid #a5b4fc;
      outline-offset: 3px;
    }
    .status {
      min-height: 28px;
      margin: 0 0 36px;
      color: var(--brand-dark);
      text-align: center;
      font-weight: 700;
    }
    .footer {
      padding-block: 24px;
      border-top: 1px solid var(--border);
      color: var(--muted);
      text-align: center;
      font-size: .9rem;
    }
    @keyframes fade-up {
      from { opacity: 0; transform: translateY(14px); }
      to { opacity: 1; transform: translateY(0); }
    }
    @media (max-width: 520px) {
      .nav { align-items: flex-start; flex-direction: column; }
      .hero { text-align: left; }
      h1 { margin-inline: 0; }
      .hero p { margin-inline: 0; }
      .product-grid { gap: 14px; }
    }
    @media (prefers-reduced-motion: reduce) {
      *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        scroll-behavior: auto !important;
        transition-duration: .01ms !important;
      }
    }
  </style>
</head>
<body>
  <header class="header">
    <div class="container nav">
      <p class="brand">Level<span>Lance</span></p>
      <a href="#products">เลือกดูสินค้า ↓</a>
    </div>
  </header>

  <main>
    <section class="container hero">
      <span class="eyebrow">Interactive UI Demo</span>
      <h1>เลือกเครื่องมือที่ช่วยให้ธุรกิจของคุณเติบโต</h1>
      <p>ตัวอย่างหน้าแสดงสินค้าที่ใช้ CSS สร้างการตอบสนองอย่างนุ่มนวล พร้อมรองรับหน้าจอขนาดเล็กและการใช้งานด้วยคีย์บอร์ด</p>
    </section>

    <section class="container product-grid" id="products" aria-label="รายการสินค้า">
      <article class="product-card">
        <div class="product-art" aria-hidden="true"><span>🛍️</span></div>
        <h2>ระบบร้านค้าออนไลน์</h2>
        <p>จัดการรายการสินค้าและนำเสนอข้อมูลให้ลูกค้าค้นหาได้สะดวก</p>
        <div class="price">เริ่มต้น ฿490</div>
        <button class="button" type="button" data-product="ระบบร้านค้าออนไลน์">สนใจรายการนี้</button>
      </article>

      <article class="product-card">
        <div class="product-art" aria-hidden="true"><span>📊</span></div>
        <h2>ระบบจัดการธุรกิจ</h2>
        <p>รวมข้อมูลสำคัญและช่วยให้ทีมติดตามการทำงานได้เป็นระบบ</p>
        <div class="price">เริ่มต้น ฿690</div>
        <button class="button" type="button" data-product="ระบบจัดการธุรกิจ">สนใจรายการนี้</button>
      </article>

      <article class="product-card">
        <div class="product-art" aria-hidden="true"><span>💬</span></div>
        <h2>ระบบดูแลลูกค้า</h2>
        <p>จัดระเบียบการติดต่อและช่วยให้ทีมตอบคำถามได้รวดเร็วขึ้น</p>
        <div class="price">เริ่มต้น ฿390</div>
        <button class="button" type="button" data-product="ระบบดูแลลูกค้า">สนใจรายการนี้</button>
      </article>
    </section>

    <p class="container status" id="status-message" role="status" aria-live="polite">เลือกสินค้าเพื่อทดลองการตอบสนองของปุ่ม</p>
  </main>

  <footer class="footer">
    <div class="container">ตัวอย่างเพื่อการเรียนรู้ HTML, CSS Transitions, Transforms และ Animations</div>
  </footer>

  <script>
    const statusMessage = document.querySelector('#status-message');
    const productButtons = document.querySelectorAll('[data-product]');

    productButtons.forEach((button) => {
      button.addEventListener('click', () => {
        const productName = button.dataset.product;
        statusMessage.textContent = `คุณเลือก: ${productName} — นี่เป็นตัวอย่างการตอบสนองของหน้าเว็บ`;
      });
    });
  </script>
</body>
</html>

11. แก้ปัญหาแอนิเมชันที่ทำงานไม่ตรงตามต้องการ

หาก Transition ไม่ทำงาน ให้ตรวจสอบว่าคุณสมบัติที่เปลี่ยนสามารถเปลี่ยนผ่านอย่างต่อเนื่องได้หรือไม่ และมีการเปลี่ยนค่าระหว่างสถานะจริงหรือไม่ หาก Animation ไม่ปรากฏ ให้ตรวจสอบชื่อ Keyframes การสะกดชื่อคุณสมบัติ ระยะเวลา และกฎ CSS ที่อาจเขียนทับค่าเดิม รวมถึงตรวจสอบการตั้งค่า Reduced Motion ของอุปกรณ์
อาการสาเหตุที่อาจเกิดขึ้นวิธีตรวจสอบ
Hover แล้วไม่เปลี่ยนSelector ไม่ตรงหรือถูกกฎอื่นเขียนทับตรวจสอบชื่อคลาสและลำดับ CSS
การ์ดกระโดดทันทีไม่มี Transition ในสถานะพื้นฐานกำหนด Transition ที่องค์ประกอบหลัก
แอนิเมชันไม่เริ่มชื่อ Keyframes ผิดหรือ Animation ถูกปิดตรวจสอบชื่อและกฎ Media Query
องค์ประกอบทับกันTransform ขยายเกินพื้นที่เดิมลด Scale หรือเพิ่มพื้นที่รอบองค์ประกอบ
หน้าเว็บทำงานช้ามีเอฟเฟกต์จำนวนมากหรือเปลี่ยนคุณสมบัติที่มีต้นทุนสูงลดจำนวนแอนิเมชันและตรวจวัดประสิทธิภาพ

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

สร้างการ์ดสินค้าอย่างน้อยสามใบด้วย HTML และ CSS กำหนด Transition ให้การ์ดขยับขึ้นเล็กน้อยเมื่อ Hover เพิ่ม Transform ให้ไอคอนหรือภาพประกอบ และสร้าง Keyframes สำหรับแสดงการ์ดอย่างนุ่มนวล จากนั้นเพิ่ม Focus State สำหรับคีย์บอร์ดและ Media Query สำหรับ `prefers-reduced-motion` ทดลองลบกฎแต่ละส่วนแล้วสังเกตความแตกต่าง
แบบฝึกหัดเพิ่มเติม: สร้างปุ่มที่มีสถานะชัดเจน
สร้างปุ่มที่มีสถานะปกติ Hover, Focus และ Active โดยใช้สีพื้นหลัง เส้นขอบ และ Transform ที่แตกต่างกัน ตรวจสอบให้แน่ใจว่าข้อความยังอ่านง่าย และผู้ใช้ที่ไม่ใช้เมาส์สามารถเห็นได้ว่าปุ่มใดกำลังได้รับ Focus
แบบฝึกหัดเพิ่มเติม: สร้าง Animation แบบวนซ้ำ
สร้างจุดสถานะที่ขยายและย่ออย่างนุ่มนวลด้วย @keyframes กำหนดระยะเวลาประมาณ 1.5–2 วินาที และทำให้หยุดหรือถูกลดการเคลื่อนไหวเมื่อผู้ใช้เปิดการตั้งค่า Reduce Motion หลีกเลี่ยงการใช้เอฟเฟกต์กะพริบถี่ ๆ

13. Checklist ก่อนนำไปใช้จริง

ก่อนเผยแพร่เว็บไซต์ ให้ตรวจสอบว่าแอนิเมชันมีวัตถุประสงค์ชัดเจน ไม่ขัดขวางการอ่าน ไม่ทำให้ปุ่มเคลื่อนจนกดยาก และไม่ทำให้เนื้อหาสำคัญหายไป ผู้ใช้คีย์บอร์ดต้องเข้าถึงปุ่มและลิงก์ได้ ขณะเดียวกันอุปกรณ์ที่ตั้งค่า Reduce Motion ต้องยังใช้งานหน้าเว็บได้ครบถ้วน
✅ Checklist สำหรับนักพัฒนาใช้ Transition กับการเปลี่ยนสถานะที่เหมาะสม เลือก Transform สำหรับการเคลื่อนไหวที่ไม่จำเป็นต้องเปลี่ยน Layout จำกัดแอนิเมชันวนซ้ำ ระบุ Focus Indicator ทดสอบบนมือถือ และรองรับ prefers-reduced-motion

14. สรุปบทเรียน EP13

ในบทนี้คุณได้เรียนรู้วิธีใช้ CSS Transitions เพื่อเปลี่ยนสถานะอย่างนุ่มนวล ใช้ Transforms เพื่อเลื่อน หมุน และปรับขนาดองค์ประกอบ ใช้ Keyframes เพื่อสร้างแอนิเมชันหลายช่วง และใช้ Hover, Focus และ Active เพื่อให้หน้าเว็บตอบสนองต่อการใช้งานหลายรูปแบบ คุณยังได้เรียนรู้แนวทางลดต้นทุนการวาดภาพและเคารพการตั้งค่าลดการเคลื่อนไหวของผู้ใช้
เมื่อนำเทคนิคเหล่านี้ไปใช้ร่วมกับ Responsive Design จาก EP12 คุณจะสามารถสร้างหน้าเว็บที่ไม่เพียงจัดวางได้เหมาะสมกับทุกขนาดหน้าจอ แต่ยังมีการตอบสนองที่ชัดเจน ใช้งานง่าย และดูเป็นมืออาชีพ โดยควรจำไว้เสมอว่าแอนิเมชันมีหน้าที่ช่วยสื่อสาร ไม่ใช่ดึงความสนใจจนรบกวนเนื้อหาหลัก
การเคลื่อนไหวที่ดีไม่ใช่การทำให้ทุกอย่างขยับ แต่คือการช่วยให้ผู้ใช้เข้าใจว่าระบบกำลังตอบสนองต่อสิ่งที่พวกเขาทำ
อ่านจนถึงระยะ 500px ก่อนท้ายหน้าและอยู่ในหน้านี้อย่างน้อย 5 นาที เพื่อรับ EXP วันละครั้ง

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

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