ข้ามไปเนื้อหาหลัก

Category: reference

CSS Animations & Transitions — ทำ UI ให้มีชีวิตชีวา

รวม pattern CSS animation และ transition ที่ใช้จริงในการสร้าง UI ที่ smooth — hover effects, micro-interactions, page transitions และ reduced motion

· อ่านประมาณ 3 นาที

สารบัญ

Transition vs Animation

TransitionAnimation
Triggerต้องมี state change (hover, :focus, class)รันได้เอง หรือ trigger ด้วย class
Controlจาก A → B เท่านั้นหลาย keyframes ได้
Loopไม่ได้ได้ (infinite)
ใช้กับhover effects, state changesloading, complex sequences

Transition พื้นฐาน

.btn {
  background: #2563eb;
  transform: translateY(0);
  /* ระบุ property ที่ transition + duration + easing */
  transition: background 0.2s ease, transform 0.2s ease;
}

.btn:hover {
  background: #1d4ed8;
  transform: translateY(-2px);
}

Easing ที่ควรรู้

transition-timing-function: ease;          /* slow-fast-slow (default) */
transition-timing-function: ease-in;       /* slow start */
transition-timing-function: ease-out;      /* slow end — เหมาะกับ enter animations */
transition-timing-function: ease-in-out;   /* slow start + end */
transition-timing-function: linear;        /* คงที่ตลอด — เหมาะกับ rotation */
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); /* spring bounce */

Card Hover Effect

.card {
  transform: translateY(0);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}

.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}

ใช้ transform และ opacity เท่านั้นสำหรับ animation ที่ smooth เพราะ GPU-accelerated ไม่กระตุ้น layout reflow

CSS Keyframe Animation

/* fade in จาก ล่าง */
@keyframes fade-up {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.section {
  animation: fade-up 0.4s ease-out both;
}

animation-fill-mode: both ทำให้ element อยู่ใน state สุดท้ายหลัง animation จบ

Loading Spinner

@keyframes spin {
  to { transform: rotate(360deg); }
}

.spinner {
  width: 1.5rem;
  height: 1.5rem;
  border: 2px solid rgba(37, 99, 235, 0.2);
  border-top-color: #2563eb;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

Skeleton Loading

@keyframes shimmer {
  to { background-position: 200% center; }
}

.skeleton {
  background: linear-gradient(
    90deg,
    rgba(15, 23, 42, 0.06) 25%,
    rgba(15, 23, 42, 0.12) 50%,
    rgba(15, 23, 42, 0.06) 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s linear infinite;
  border-radius: 6px;
}

Reduced Motion (Accessibility)

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

เสมอใส่ reduced motion ใน global CSS ผู้ใช้ที่มีปัญหา vestibular disorder อาจรู้สึกไม่สบายกับ animation

View Transitions API

สำหรับ page transitions ใน Astro:

<!-- src/layouts/Layout.astro -->
import ClientRouter from 'astro/components/ClientRouter.astro';
<ClientRouter />
/* กำหนด animation สำหรับ page transition */
::view-transition-old(root) {
  animation: fade-out 0.2s ease-out;
}

::view-transition-new(root) {
  animation: fade-in 0.2s ease-in;
}

@keyframes fade-out {
  to { opacity: 0; }
}
@keyframes fade-in {
  from { opacity: 0; }
}

Tips สำหรับ Performance

/* บอก browser ล่วงหน้าว่า element จะ animate */
.card {
  will-change: transform;
}

/* ใช้หลัง animation จบ เพื่อปลด GPU memory */
.card:not(:hover) {
  will-change: auto;
}

ใช้ will-change เฉพาะเมื่อจำเป็น และ apply บน element จำนวนน้อย — ใช้มากเกินไปทำให้ memory ขึ้น

/* ใช้ transform แทน top/left เสมอ */
/* ❌ */
.modal { top: 0; left: 0; transition: top 0.3s; }

/* ✅ */
.modal { transform: translateY(0); transition: transform 0.3s ease-out; }