커버플로

Coverflow

가운데 카드는 정면으로 크게, 양옆 카드는 원근감 있게 기울어져 작아지는 갤러리 레이아웃. 옛 iTunes의 앨범 커버 넘기기 UI로 유명해졌습니다.

다른 이름: Cover flowPerspective carousel
···
html
<div class="cfwrap"><div class="cf" id="cf"></div></div>
css
.cfwrap{perspective:900px;width:88%;height:70%;display:grid;place-items:center}
.cf{position:relative;width:100%;height:100%}
.card{position:absolute;top:50%;left:50%;width:clamp(46px,20vmin,90px);height:clamp(66px,28vmin,128px);
  margin:calc(clamp(66px,28vmin,128px) / -2) 0 0 calc(clamp(46px,20vmin,90px) / -2);
  border-radius:8px;background:var(--surface);border:1px solid var(--line);box-shadow:0 10px 24px rgba(0,0,0,.18);
  transition:transform .6s cubic-bezier(.2,.8,.2,1), opacity .6s;
  display:grid;place-items:center;font:800 18px/1 sans-serif;color:var(--muted)}
js
const cf = document.getElementById('cf');
const N = 5;
const cards = [];
for (let i = 0; i < N; i++) {
  const c = document.createElement('div');
  c.className = 'card';
  c.textContent = String(i + 1);
  cf.appendChild(c);
  cards.push(c);
}
let active = 0;
function render() {
  cards.forEach((c, i) => {
    const off = i - active;
    const abs = Math.abs(off);
    const rot = Math.max(-1, Math.min(1, off)) * 48;
    const tx = off * 44;
    const tz = -abs * 60;
    const sc = abs === 0 ? 1 : 0.72;
    c.style.transform = 'translateX(' + tx + 'px) translateZ(' + tz + 'px) rotateY(' + (-rot) + 'deg) scale(' + sc + ')';
    c.style.opacity = abs > 2 ? '0' : String(1 - abs * 0.22);
    c.style.zIndex = String(100 - abs);
    c.style.color = abs === 0 ? 'var(--accent)' : 'var(--muted)';
  });
}
render();
setInterval(() => {
  active = (active + 1) % N;
  render();
}, 1500);

카드들을 하나의 활성 인덱스를 기준으로 offset(= 카드 인덱스 - 활성 인덱스)을 계산해 배치합니다. offset이 0인 카드는 rotateY(0)·scale(1)로 정면을 보고, offset이 ±1, ±2로 멀어질수록 rotateY(±45~60도)로 기울고 translateX로 옆으로 밀리며 scale이 작아지고 opacity도 옅어집니다. 부모에 perspective를 주는 것이 필수입니다 — 없으면 rotateY가 그냥 좌우로 눌린 사각형으로 보입니다.

핵심은 "겹치는 순서(z-index)"입니다 — 가운데에 가까운 카드가 항상 위에 그려져야 카드가 서로 자연스럽게 겹쳐 보입니다. translateZ(-|offset| × 값)로 깊이를 주면 각 카드가 실제로 카메라에서 멀어지므로 브라우저가 알아서 올바른 순서로 그려줍니다.

인덱스가 바뀔 때 모든 카드의 transform을 한 번에 CSS transition으로 갱신하면 "넘기는" 애니메이션이 저절로 생깁니다 — 개별 카드마다 애니메이션을 따로 걸 필요가 없습니다.

언제 쓰나

앨범·포트폴리오처럼 "지금 보는 것 하나 + 다음/이전을 살짝 보여주는" 갤러리에 씁니다. 항목이 많고 빠르게 훑어봐야 하면 그리드가 더 낫습니다 — 커버플로는 한 번에 하나씩만 진지하게 보여줍니다.