Bài 13: Animation – Tạo hoạt ảnh CSS

Nếu Transition là hiệu ứng chuyển tiếp giữa hai trạng thái, thì Animationhoạt ảnh phức tạp với nhiều bước. Animation cho phép bạn tạo những chuyển động lặp lại, phức tạp hơn nhiều — từ loading spinner cho đến parallax effect.

Bài này sẽ dạy bạn cách sử dụng @keyframesanimation để tạo hoạt ảnh CSS tuyệt vời mà không cần JavaScript.

Animation là gì?

Animation cho phép bạn tạo hoạt ảnh từ từ của một phần tử, từ trạng thái đầu đến trạng thái cuối (hoặc nhiều trạng thái ở giữa).

  • 🎬 @keyframes: Định nghĩa các bước của hoạt ảnh
  • ⏱️ animation-duration: Thời gian hoạt ảnh
  • 🔄 animation-iteration-count: Số lần lặp lại
  • animation-delay: Trễ trước khi bắt đầu

@keyframes – Định nghĩa hoạt ảnh

@keyframes định nghĩa các bước của hoạt ảnh từ đầu (0%) đến cuối (100%).

/* Định nghĩa hoạt ảnh "slide" */
@keyframes slide {
  0% {
    /* Bắt đầu: ở vị trí trái */
    left: 0;
    background-color: red;
  }

  50% {
    /* Giữa: ở vị trí giữa, màu vàng */
    left: 250px;
    background-color: yellow;
  }

  100% {
    /* Kết thúc: ở vị trí phải, màu xanh */
    left: 500px;
    background-color: blue;
  }
}

/* Áp dụng hoạt ảnh */
.box {
  position: relative;
  width: 50px;
  height: 50px;
  animation: slide 3s;  /* Sử dụng hoạt ảnh "slide", thời gian 3 giây */
}

Cú pháp @keyframes

@keyframes tên-hoạt-ảnh {
  0% {
    /* Trạng thái đầu */
  }

  50% {
    /* Trạng thái giữa (tùy chọn) */
  }

  100% {
    /* Trạng thái cuối */
  }
}

/* Hoặc dùng from/to */
@keyframes slide {
  from {
    /* Giống 0% */
    left: 0;
  }

  to {
    /* Giống 100% */
    left: 500px;
  }
}

Animation – Áp dụng hoạt ảnh

Sau khi định nghĩa @keyframes, bạn dùng animation để áp dụng nó cho một phần tử.

Thuộc tínhChức năngGiá trị mặc địnhVí dụ
animation-nameTên hoạt ảnh (@keyframes)noneanimation-name: slide;
animation-durationThời gian hoạt ảnh0sanimation-duration: 3s;
animation-delayTrễ trước khi bắt đầu0sanimation-delay: 0.5s;
animation-iteration-countSố lần lặp lại (infinite = lặp vô hạn)1animation-iteration-count: infinite;
animation-timing-functionTốc độ (linear, ease, ease-in…)easeanimation-timing-function: linear;
animation-directionHướng (normal, reverse, alternate)normalanimation-direction: alternate;
animation-fill-modeTrạng thái sau hoạt ảnh (forwards, backwards)noneanimation-fill-mode: forwards;
/* Cách dài */
.box {
  animation-name: slide;
  animation-duration: 3s;
  animation-delay: 0.5s;
  animation-iteration-count: infinite;
  animation-timing-function: ease-in-out;
  animation-direction: alternate;
}

/* Shorthand */
.box {
  animation: slide 3s 0.5s infinite ease-in-out alternate;
  /* name duration delay iteration-count timing-function direction */
}

animation-direction – Hướng hoạt ảnh

Giá trịMô tảVí dụ
normal0% → 100% (trái sang phải)Mũi tên chạy từ trái sang phải
reverse100% → 0% (phải sang trái)Mũi tên chạy từ phải sang trái
alternate0% → 100% → 0% (chiều đi, chiều về)Mũi tên chạy qua lại
alternate-reverse100% → 0% → 100% (chiều về, chiều đi)Mũi tên chạy qua lại (bắt đầu ngược)

animation-fill-mode – Trạng thái sau hoạt ảnh

Giá trịMô tảKhi nào dùng
none (mặc định)Trở về trạng thái ban đầu sau hoạt ảnhHoạt ảnh lặp lại (infinite)
forwardsGiữ trạng thái cuối (100%) sau hoạt ảnhHoạt ảnh kết thúc, phần tử ở trạng thái cuối
backwardsGiữ trạng thái đầu (0%) trước hoạt ảnhÍt dùng
bothKết hợp forwards và backwardsHoạt ảnh có delay
/* Ví dụ: animation-fill-mode */

/* ❌ SAI: Phần tử quay lại vị trí ban đầu sau hoạt ảnh */
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.box {
  animation: fadeIn 1s;
  /* Sau 1s, opacity quay lại 0 */
}

/* ✅ ĐÚNG: Phần tử giữ opacity 1 sau hoạt ảnh */
.box {
  animation: fadeIn 1s forwards;
  /* Sau 1s, opacity vẫn là 1 */
}

Ví dụ 1 – Loading spinner (Biểu tượng tải)

<!-- HTML -->
<div class="spinner"></div>
<p>Đang tải...</p>
/* CSS */
@keyframes spin {
  0% {
    transform: rotate(0deg);
  }

  100% {
    transform: rotate(360deg);
  }
}

.spinner {
  width: 50px;
  height: 50px;
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

Ví dụ 2 – Fade in & Fade out

<!-- HTML -->
<div class="fade-in">Fade In</div>
<div class="fade-out">Fade Out</div>
<div class="bounce">Bounce</div>
/* Fade In */
@keyframes fadeIn {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

.fade-in {
  animation: fadeIn 2s ease-in forwards;
}

/* Fade Out */
@keyframes fadeOut {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}

.fade-out {
  animation: fadeOut 2s ease-out forwards;
}

/* Bounce (nhảy) */
@keyframes bounce {
  0%, 100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(-20px);
  }
}

.bounce {
  animation: bounce 1s ease infinite;
}

Ví dụ 3 – Pulse (Nhấp nháy)”

@keyframes pulse {
  0% {
    transform: scale(1);
    opacity: 1;
  }

  50% {
    transform: scale(1.1);
    opacity: 0.7;
  }

  100% {
    transform: scale(1);
    opacity: 1;
  }
}

.pulse {
  animation: pulse 2s ease-in-out infinite;
}

Ví dụ hoàn chỉnh – Trang web với animations

<!DOCTYPE html>
<html lang="vi">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>CSS Animation Demo</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>

  <!-- Header với fadeIn -->
  <header class="header">
    <h1 class="fade-in-title">🎬 CSS Animation</h1>
    <p class="fade-in-subtitle">Tạo hoạt ảnh tuyệt vời</p>
  </header>

  <!-- Loading spinner -->
  <div class="loading">
    <div class="spinner"></div>
    <p>Đang tải...</p>
  </div>

  <!-- Bounce items -->
  <section class="features">
    <h2>Tính năng</h2>
    <div class="feature-grid">
      <div class="feature-card bounce">
        <h3>⚡ Nhanh</h3>
        <p>Animation chạy mượt mà</p>
      </div>

      <div class="feature-card bounce" style="animation-delay: 0.2s;">
        <h3>💡 Dễ dùng</h3>
        <p>CSS thuần túy, không JS</p>
      </div>

      <div class="feature-card bounce" style="animation-delay: 0.4s;">
        <h3>✨ Đẹp</h3>
        <p>Hiệu ứng chuyên nghiệp</p>
      </div>
    </div>
  </section>

  <!-- Pulse button -->
  <div class="button-section">
    <button class="btn-pulse">Nhấp vào tôi</button>
  </div>

  <!-- Floating animation -->
  <div class="floating-box">
    <span>↑</span>
  </div>

</body>
</html>
/* FILE: styles.css */

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: Arial, sans-serif;
  background-color: #f5f5f5;
  color: #333;
}

/* ========== KEYFRAMES ========== */

/* Fade In Title */
@keyframes fadeInDown {
  from {
    opacity: 0;
    transform: translateY(-30px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Fade In Subtitle */
@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Loading spinner */
@keyframes spin {
  from {
    transform: rotate(0deg);
  }

  to {
    transform: rotate(360deg);
  }
}

/* Bounce animation */
@keyframes bounce {
  0%, 100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(-20px);
  }
}

/* Pulse animation */
@keyframes pulse {
  0%, 100% {
    transform: scale(1);
    box-shadow: 0 0 0 0 rgba(52, 152, 219, 0.7);
  }

  50% {
    transform: scale(1.05);
    box-shadow: 0 0 0 10px rgba(52, 152, 219, 0);
  }
}

/* Floating animation */
@keyframes float {
  0%, 100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(-15px);
  }
}

/* ========== STYLES ========== */

/* Header */
.header {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 60px 20px;
  text-align: center;
}

.fade-in-title {
  font-size: 48px;
  margin-bottom: 10px;
  animation: fadeInDown 0.8s ease;
}

.fade-in-subtitle {
  font-size: 20px;
  opacity: 0.9;
  animation: fadeInUp 0.8s ease 0.2s backwards;
}

/* Loading section */
.loading {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 200px;
  gap: 20px;
}

.spinner {
  width: 50px;
  height: 50px;
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

.loading p {
  color: #666;
  font-weight: bold;
}

/* Features section */
.features {
  max-width: 1200px;
  margin: 60px auto;
  padding: 0 20px;
}

.features h2 {
  text-align: center;
  margin-bottom: 40px;
  color: #2c3e50;
  font-size: 32px;
}

.feature-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 30px;
}

.feature-card {
  background: white;
  padding: 30px;
  border-radius: 8px;
  text-align: center;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  animation: bounce 1s ease infinite;
}

.feature-card h3 {
  font-size: 24px;
  margin-bottom: 15px;
  color: #667eea;
}

.feature-card p {
  color: #666;
  line-height: 1.6;
}

/* Button section */
.button-section {
  display: flex;
  justify-content: center;
  margin: 60px 0;
}

.btn-pulse {
  padding: 15px 40px;
  font-size: 18px;
  background-color: #3498db;
  color: white;
  border: none;
  border-radius: 50px;
  cursor: pointer;
  font-weight: bold;
  animation: pulse 2s ease-in-out infinite;
}

.btn-pulse:hover {
  animation-play-state: paused;
  /* Tạm dừng animation khi hover */
}

/* Floating box */
.floating-box {
  position: fixed;
  bottom: 30px;
  right: 30px;
  width: 50px;
  height: 50px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
  font-size: 24px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
  animation: float 3s ease-in-out infinite;
  z-index: 50;
}

Một số animation phổ biến

/* Slide in từ trái */
@keyframes slideInLeft {
  from {
    transform: translateX(-100%);
    opacity: 0;
  }

  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* Rotate in */
@keyframes rotateIn {
  from {
    transform: rotate(-200deg);
    opacity: 0;
  }

  to {
    transform: rotate(0);
    opacity: 1;
  }
}

/* Flip */
@keyframes flip {
  0% {
    transform: perspective(400px) rotateY(0);
  }

  100% {
    transform: perspective(400px) rotateY(360deg);
  }
}

/* Shake (rung) */
@keyframes shake {
  0%, 100% {
    transform: translateX(0);
  }

  10%, 30%, 50%, 70%, 90% {
    transform: translateX(-5px);
  }

  20%, 40%, 60%, 80% {
    transform: translateX(5px);
  }
}

/* Heartbeat (nhịp tim) */
@keyframes heartbeat {
  0%, 100% {
    transform: scale(1);
  }

  25% {
    transform: scale(1.3);
  }

  50% {
    transform: scale(1);
  }
}

animation-play-state – Tạm dừng/Tiếp tục animation

/* Tạm dừng animation khi hover */
.spinner {
  animation: spin 2s linear infinite;
}

.spinner:hover {
  animation-play-state: paused;
  /* Hoặc running để tiếp tục */
}

Tóm tắt

  • @keyframes: Định nghĩa hoạt ảnh (0%, 50%, 100%)
  • animation-name: Tên hoạt ảnh
  • animation-duration: Thời gian hoạt ảnh
  • animation-delay: Trễ trước khi bắt đầu
  • animation-iteration-count: Số lần lặp (infinite = vô hạn)
  • animation-timing-function: Tốc độ (linear, ease, ease-in…)
  • animation-direction: Hướng (normal, reverse, alternate)
  • animation-fill-mode: Trạng thái sau hoạt ảnh (forwards, backwards)
  • animation-play-state: Tạm dừng/tiếp tục (paused, running)
  • Shorthand: animation: name duration delay count timing direction fill-mode

🎯 Bài tập thực hành

Bài tập 1: Tạo loading spinner với animation:

  • Vòng tròn border (4px, màu xanh ở top)
  • Xoay 360 độ liên tục (animation: spin 1s linear infinite)
  • Thêm text “Đang tải…” bên dưới
  • Styling đẹp với background gradient

Bài tập 2: Tạo bounce card animation:

  • 3-4 thẻ (card) hiển thị với bounce animation
  • Mỗi card có animation-delay khác nhau (0s, 0.2s, 0.4s…)
  • Card: dịch lên, xuống (translateY) từ 0 đến -20px
  • Animation duration: 1s, infinite, ease
  • Mỗi card có icon/emoji, tiêu đề, mô tả

Bài tập 3: Tạo trang landing page với multiple animations:

  • Header: fadeInDown animation
  • Subtitle: fadeInUp animation với delay
  • Features: bounce animation với staggered delay (0.2s, 0.4s, 0.6s)
  • CTA button: pulse animation
  • Floating icon: float animation (moveY up-down)
  • Tất cả timing functions khác nhau

Chúc bạn thành công! 🚀

Để lại bình luận

Email của bạn sẽ không được hiển thị.