이 글은 누구를 위한 것인가
- 카드 뒤집기, 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 레이어를 미리 생성해 성능을 최적화한다.