3D CSS Transform과 Perspective: 입체감 있는 UI 구현

디자인

CSS 3D TransformPerspective카드 뒤집기CSS 애니메이션3D UI

이 글은 누구를 위한 것인가

  • 카드 뒤집기, 3D 회전 등 입체감 있는 UI를 구현하려는 개발자
  • CSS만으로 3D 효과를 만들려는 팀
  • GPU 합성 레이어를 활용한 성능 최적화를 이해하려는 개발자

들어가며

CSS 3D Transform은 JavaScript 없이 입체감 있는 UI를 만든다. perspective로 소실점을 설정하고, rotateX/Y로 카드를 뒤집고, translateZ로 요소의 깊이를 조정한다. transform-style: preserve-3d로 자식 요소가 3D 공간을 공유하게 한다.

이 글은 bluefoxdev.kr의 CSS 3D Transform Perspective 가이드 를 참고하여 작성했습니다.


1. CSS 3D Transform 핵심 개념

[perspective]
  부모에 설정: perspective: 800px
  값이 작을수록 강한 원근감
  perspective-origin: 50% 50% (소실점 위치)

[transform-style]
  preserve-3d: 자식이 3D 공간 공유
  flat: 자식을 2D로 평탄화 (기본값)

[3D Transform 함수]
  rotateX(45deg): X축 기준 회전 (앞뒤 기울기)
  rotateY(180deg): Y축 기준 회전 (카드 뒤집기)
  rotateZ(45deg): Z축 기준 회전 (평면 회전)
  translateZ(50px): Z축 이동 (앞으로/뒤로)
  scale3d(1.1, 1.1, 1): 3D 스케일

[backface-visibility]
  hidden: 뒷면이 보일 때 숨김 (카드 뒤집기 필수)
  visible: 뒷면 표시

[성능 최적화]
  transform과 opacity만 GPU 합성
  will-change: transform (GPU 레이어 미리 생성)
  남용 주의: 메모리 증가

2. CSS 3D 구현

/* 카드 뒤집기 효과 */
.card-container {
  perspective: 800px;
  width: 300px;
  height: 200px;
}

.card {
  width: 100%;
  height: 100%;
  position: relative;
  transform-style: preserve-3d;
  transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}

.card-container:hover .card,
.card.flipped {
  transform: rotateY(180deg);
}

.card-front,
.card-back {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden; /* Safari */
  border-radius: 12px;
  padding: 24px;
}

.card-front {
  background: linear-gradient(135deg, #667eea, #764ba2);
  color: white;
}

.card-back {
  background: #1a1a2e;
  color: white;
  transform: rotateY(180deg); /* 처음엔 뒤집힌 상태 */
}

/* 3D 깊이감 호버 효과 */
.depth-card {
  transition: transform 0.3s ease, box-shadow 0.3s ease;
  transform-style: preserve-3d;
}

.depth-card:hover {
  transform: translateY(-8px) translateZ(20px) rotateX(5deg);
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
}

/* CSS 3D 캐러셀 */
.carousel-3d {
  position: relative;
  width: 300px;
  height: 200px;
  transform-style: preserve-3d;
  transition: transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}

.carousel-item {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}

/* 3개 아이템 캐러셀: 각 120도 간격 */
.carousel-item:nth-child(1) { transform: rotateY(0deg) translateZ(200px); }
.carousel-item:nth-child(2) { transform: rotateY(120deg) translateZ(200px); }
.carousel-item:nth-child(3) { transform: rotateY(240deg) translateZ(200px); }
// 인터랙티브 3D 카드 (마우스 기울기)
import { useRef } from 'react';

function TiltCard({ children }: { children: React.ReactNode }) {
  const cardRef = useRef<HTMLDivElement>(null);

  const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
    const card = cardRef.current;
    if (!card) return;
    const rect = card.getBoundingClientRect();
    const x = (e.clientX - rect.left) / rect.width - 0.5;  // -0.5 ~ 0.5
    const y = (e.clientY - rect.top) / rect.height - 0.5;

    card.style.transform = `perspective(800px) rotateY(${x * 20}deg) rotateX(${-y * 20}deg) scale(1.02)`;
  };

  const handleMouseLeave = () => {
    if (cardRef.current) {
      cardRef.current.style.transform = 'perspective(800px) rotateY(0) rotateX(0) scale(1)';
    }
  };

  return (
    <div
      ref={cardRef}
      onMouseMove={handleMouseMove}
      onMouseLeave={handleMouseLeave}
      style={{ transition: 'transform 0.1s ease', willChange: 'transform', borderRadius: 12, overflow: 'hidden' }}
    >
      {children}
    </div>
  );
}

// 3D 캐러셀 컨트롤
import { useState } from 'react';

function Carousel3D({ items }: { items: React.ReactNode[] }) {
  const [current, setCurrent] = useState(0);
  const angle = 360 / items.length;

  return (
    <div style={{ perspective: '600px', height: 250 }}>
      <div style={{ transformStyle: 'preserve-3d', transition: 'transform 0.8s ease', transform: `rotateY(${-current * angle}deg)`, position: 'relative', width: 300, height: 200, margin: '0 auto' }}>
        {items.map((item, i) => (
          <div key={i} style={{ position: 'absolute', inset: 0, backfaceVisibility: 'hidden', transform: `rotateY(${i * angle}deg) translateZ(200px)` }}>
            {item}
          </div>
        ))}
      </div>
      <div style={{ display: 'flex', justifyContent: 'center', gap: 12, marginTop: 16 }}>
        <button onClick={() => setCurrent((current - 1 + items.length) % items.length)}>←</button>
        <button onClick={() => setCurrent((current + 1) % items.length)}>→</button>
      </div>
    </div>
  );
}

마무리

CSS 3D Transform의 핵심은 perspective + transform-style: preserve-3d + backface-visibility: hidden 조합이다. 카드 뒤집기는 앞면/뒷면을 겹쳐 놓고 rotateY(180deg)로 전환한다. 마우스 추적 기울기는 실시간 perspective + rotateX/Y로 구현하되, will-change: transform으로 GPU 레이어를 미리 생성해 성능을 최적화한다.