Card 3D Hover Effect bằng CSS Perspective: Hiệu ứng nghiêng theo chuột mượt mà

Hiệu ứng 3D hover giúp thẻ nội dung nghiêng nhẹ theo vị trí con trỏ chuột, tạo cảm giác chiều sâu và tương tác cao. Bài viết này hướng dẫn bạn xây dựng Card 3D bằng CSS Perspective kết hợp JavaScript tối giản, tương thích UIkit, hỗ trợ dark mode và tôn trọng tùy chọn giảm chuyển động của người dùng.

Card 3D Hover Effect bằng CSS Perspective: Hiệu ứng nghiêng theo chuột mượt mà

Bước 1: Tạo cấu trúc HTML của card

Chúng ta bọc thẻ card trong một container có perspective. Bên trong là một phần tử “stage” để xoay 3D và lớp “glare” mô phỏng ánh sáng phản chiếu.

<div class="im-tilt uk-card uk-card-default uk-card-body uk-border-rounded uk-box-shadow-medium"
     data-tilt data-tilt-max="14" data-tilt-scale="1.02">
  <div class="im-tilt-stage">
    <img class="uk-border-rounded uk-margin-small-bottom" src="thumb.jpg" alt="Ảnh minh họa" width="960" height="540">
    <h3 class="uk-margin-remove">Card 3D Hover</h3>
    <p class="uk-text-meta">CSS perspective + transform + glare</p>
    <div class="im-tilt-glare" aria-hidden="true"></div>
  </div>
</div>

Bước 2: Viết CSS perspective, các lớp 3D và glare

Container chỉ giữ nhiệm vụ cung cấp “điểm nhìn” (perspective). Phần tử stage bật transform-style: preserve-3d để nội dung xoay mượt. Lớp glare dùng gradient bán trong suốt di chuyển theo chuột.

/* ===== Card 3D Hover (UIkit-friendly) ===== */
.im-tilt {
  /* container đóng vai trò "máy ảnh" */
  perspective: 900px;
  border-radius: 16px;
  overflow: hidden; /* bo góc ảnh + glare */
}

.im-tilt-stage {
  position: relative;
  transform-style: preserve-3d;
  transition: transform 180ms ease, box-shadow 180ms ease;
  will-change: transform;
}

.im-tilt .im-tilt-glare {
  pointer-events: none;
  position: absolute;
  inset: -20%; /* phủ rộng để khi xoay không lộ mép */
  background: radial-gradient(ellipse at center,
              rgba(255,255,255,0.35) 0%,
              rgba(255,255,255,0.0) 60%);
  transform: translateZ(60px); /* nổi lên một chút để thật hơn */
  opacity: 0; /* sẽ điều khiển bằng JS */
}

/* Hiệu ứng “nổi” nhẹ khi hover (fallback nếu tắt JS) */
.im-tilt:hover .im-tilt-stage {
  transform: translateZ(10px);
}

/* Tối ưu hiển thị ảnh & nội dung */
.im-tilt img { width: 100%; height: auto; display: block; }
.im-tilt h3 { font-weight: 600; }
.im-tilt p.uk-text-meta { margin-top: 4px; }

/* Dark mode: nếu site dùng .uk-dark hoặc .dark thì dùng nền card tối hơn */
.uk-dark .im-tilt.uk-card-default,
.dark .im-tilt.uk-card-default {
  background: #1f1f1f;
  color: #eaeaea;
}

/* Tôn trọng người dùng giảm chuyển động */
@media (prefers-reduced-motion: reduce) {
  .im-tilt-stage { transition: none !important; }
}

Bước 3: Thêm JavaScript điều khiển góc nghiêng theo chuột

Ta đọc vị trí con trỏ so với bounding box của card, nội suy thành góc xoay X/Y và áp dụng bằng requestAnimationFrame để mượt. Khi rời chuột, card trở lại trạng thái ban đầu.

// 3D Tilt minimal driver (multi-card, UIkit-friendly)
(function () {
  const clamp = (v, min, max) => Math.max(min, Math.min(max, v));

  function initTilt(card) {
    const stage = card.querySelector('.im-tilt-stage');
    const glare = card.querySelector('.im-tilt-glare');
    if (!stage) return;

    // Tùy chỉnh qua data-attribute
    const max = Number(card.getAttribute('data-tilt-max') || 15);     // độ nghiêng tối đa (deg)
    const scale = Number(card.getAttribute('data-tilt-scale') || 1);  // scale khi hover
    let frame = null;
    let current = { rx: 0, ry: 0, s: 1 };

    function apply(rx, ry, s, gOpacity, gx, gy) {
      stage.style.transform =
        `rotateX(${rx}deg) rotateY(${ry}deg) translateZ(0) scale(${s})`;
      if (glare) {
        glare.style.opacity = gOpacity.toFixed(2);
        // di chuyển center của radial glare theo chuột
        glare.style.background = `radial-gradient(650px 350px at ${gx}% ${gy}%, 
          rgba(255,255,255,0.35) 0%,
          rgba(255,255,255,0.0) 60%)`;
      }
    }

    function compute(e) {
      const rect = card.getBoundingClientRect();
      const cx = rect.left + rect.width / 2;
      const cy = rect.top + rect.height / 2;
      const px = (e.clientX - cx) / (rect.width / 2);  // [-1, 1]
      const py = (e.clientY - cy) / (rect.height / 2); // [-1, 1]
      const ry = clamp(px * max, -max, max);
      const rx = clamp(-py * max, -max, max);
      const s = scale > 1 ? scale : 1;
      const gOpacity = 0.35 * (Math.abs(px) + Math.abs(py)) / 2;
      const gx = clamp((px + 1) * 50, 0, 100);
      const gy = clamp((py + 1) * 50, 0, 100);
      return { rx, ry, s, gOpacity, gx, gy };
    }

    function onMove(e) {
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
      const next = compute(e);
      if (frame) cancelAnimationFrame(frame);
      frame = requestAnimationFrame(() => {
        current = next;
        apply(next.rx, next.ry, next.s, next.gOpacity ?? 0, next.gx ?? 50, next.gy ?? 50);
      });
    }

    function onLeave() {
      if (frame) cancelAnimationFrame(frame);
      frame = requestAnimationFrame(() => {
        current = { rx: 0, ry: 0, s: 1 };
        apply(0, 0, 1, 0, 50, 50);
      });
    }

    card.addEventListener('mousemove', onMove);
    card.addEventListener('mouseleave', onLeave);
    // Touch (giữ hành vi nhẹ nhàng)
    card.addEventListener('touchmove', (ev) => {
      if (!ev.touches || !ev.touches[0]) return;
      onMove({ clientX: ev.touches[0].clientX, clientY: ev.touches[0].clientY });
    }, { passive: true });
    card.addEventListener('touchend', onLeave);
  }

  document.addEventListener('DOMContentLoaded', function () {
    document.querySelectorAll('[data-tilt]').forEach(initTilt);
  });
})();

Mẹo tối ưu và truy cập

  • Tắt hiệu ứng với prefers-reduced-motion: reduce.
  • Giới hạn góc xoay bằng data-tilt-max, thêm hiệu ứng scale bằng data-tilt-scale.
  • Tránh tạo quá nhiều shadow/blur nặng; dùng will-change: transform đúng chỗ.

Demo trực tiếp

Ảnh minh họa 1

Card 3D Hover

Nghiêng theo vị trí chuột · Max 14°

Ảnh minh họa 2

Card 3D Hover (Pro)

Nghiêng sâu hơn · Max 20° + Scale 1.05

Bình luận


  • Không có bình luận.

Init Toolbox

Nhấn Ctrl + \ trên máy tính, hoặc vuốt sang trái ở bất kỳ đâu trên mobile.

Đăng nhập





Đang tải...