GODRICH

CSS 카드 효과 9가지 — 호버 인터랙션 모음

CSS 카드 효과는 마우스를 올리거나 카드를 선택했을 때 상품·포스트 카드가 살짝 움직이면서 정보를 더 보여주는 짧은 시각 변화입니다. 상품·포스트 카드에 바로 붙일 수 있는 아홉 가지를 실제로 렌더링해서 모았고, 전부 자바스크립트 없이 CSS만으로 움직입니다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

아홉 개는 인기 순서가 아니라 카드 한 장을 눈으로 훑어볼 때 실제로 거치는 순서로 놓았습니다. 먼저 이미지가 확대되며 존재감을 드러내고(01), 커서가 머무는 위치에 따라 기울고(02), 궁금해서 더 보면 뒷면이 뒤집히고(03), 초점이 맞았다는 걸 프레임과 뱃지와 밑줄이 몸짓으로 보여주고(04~06), 잠깐 멈추면 정보 패널이 뜨고(07), 프리미엄한 유리 표면으로 눈길을 붙잡다가(08), 마지막으로 링 하나가 그려지며 실제 선택을 확정합니다(09).

번호 이름 무엇이 움직이나 JS
01 이미지 확대 + 캡션 슬라이드 이미지 scale, 캡션 translateY 없음
02 구역별 3D 틸트 카드 rotateX·rotateY 없음
03 뒷면 플립 공개 카드 rotateY(180deg) 없음
04 모서리 브래킷 프레임 브래킷 opacity·위치 없음
05 뱃지 탄성 팝인 뱃지 scale 오버슈트 없음
06 CTA 밑줄 스윕 밑줄 scaleX 없음
07 앵커 위치 미니 패널 패널 opacity·위치 없음
08 글래스모피즘 블러 backdrop-filter·그림자 없음
09 선택 링 드로잉 SVG stroke-dashoffset 없음

01이미지 확대 + 캡션 슬라이드

카드에 마우스를 올리면 안쪽 이미지가 1.08배로 천천히 확대되고, 동시에 아래에 숨어 있던 캡션이 위로 슬라이드해 겹쳐집니다. 여행지·상품 소개 카드나 블로그 썸네일처럼 이미지가 주인공인 자리에 씁니다.

transform: scaletranslateYoverflow: hidden
.card__media {
  transform: scale(1);
  transition: transform $duration $easing;
}
.card__caption {
  transform: translateY(60%);
  opacity: 0;
  transition: transform $duration $easing, opacity $duration $easing;
}
.card:hover .card__media,
.card.is-demo .card__media { transform: scale(1.08); }
.card:hover .card__caption,
.card.is-demo .card__caption { transform: translateY(0); opacity: 1; }

02구역별 3D 틸트

카드를 4등분한 보이지 않는 구역 중 커서가 올라간 곳에 따라 :has() 선택자가 다른 기울기 값을 고릅니다. 좌표를 읽는 JS 없이도 카드가 커서 쪽으로 기울어, 기능 소개 카드에 쓰기 좋습니다.

:has() 선택자rotateX/rotateYperspective
.card__inner {
  transform-style: preserve-3d;
  transition: transform $duration $easing;
}
.card:has(.zone--tl:hover) .card__inner { transform: rotateX(8deg) rotateY(-10deg); }
.card:has(.zone--tr:hover) .card__inner { transform: rotateX(8deg) rotateY(10deg); }
.card:has(.zone--bl:hover) .card__inner { transform: rotateX(-8deg) rotateY(-10deg); }
.card:has(.zone--br:hover) .card__inner { transform: rotateX(-8deg) rotateY(10deg); }

03뒷면 플립 공개

카드에 올리면 Y축으로 180도 돌아 뒷면이 나타나고, 앞에서 다 못 보여준 상세 설명이나 버튼이 뒷면에 있습니다. 가격 카드의 세부 조건이나 팀원 소개처럼 정보가 두 겹인 카드에 맞습니다.

perspectiverotateY(180deg)backface-visibility
.card__flip {
  transform-style: preserve-3d;
  transition: transform $duration $easing;
}
.card__face--back {
  backface-visibility: hidden;
  transform: rotateY(180deg);
}
.card:hover .card__flip,
.card:focus-visible .card__flip {
  transform: rotateY(180deg);
}

04모서리 브래킷 프레임

카드 네 귀퉁이에 짧게 접힌 브래킷이 평소엔 안쪽에 작게 숨어 있다가, 호버하면 테두리 쪽으로 자라나 카메라 포커스처럼 카드를 감쌉니다. 여러 장 중 하나를 고르는 갤러리 그리드에 어울립니다.

transform: scaleopacity.corner 4개
.corner {
  opacity: 0;
  transition: transform $duration $easing, opacity $duration $easing;
}
.corner--tl { transform: translate($sp-2, $sp-2); }
.card:hover .corner,
.card:focus-visible .corner {
  opacity: 1;
  transform: translate(0, 0);
}

05뱃지 탄성 팝인

평소엔 숨어 있던 'NEW'·'SALE' 뱃지가 호버하면 튕기는 이징으로 순간 커졌다가 살짝 줄며 자리를 잡습니다. 할인 상품이나 신규 항목처럼 시선을 한 번 더 끌어야 하는 카드에 씁니다.

@keyframescubic-bezier 오버슈트transform: scale
.badge {
  transform: scale(0);
  transition: transform $duration $ease-pop;
}
.card:hover .badge,
.card:focus-visible .badge {
  transform: scale(1);
}

06CTA 밑줄 스윕

'자세히 보기' 글자 자체에 올리지 않고 카드 어디에 올려도, 부모 .card:hover가 자식 밑줄을 골라 왼쪽에서 오른쪽으로 그립니다. 블로그 카드의 더 보기 링크나 랜딩 페이지 CTA에 붙이면 클릭 범위 전체가 신호를 보냅니다.

부모 :hover → 자식 선택transform: scaleXtransform-origin
.cta__underline {
  transform: scaleX(0);
  transform-origin: left;
  transition: transform $duration $easing;
}
.card:hover .cta__underline,
.card:focus-visible .cta__underline {
  transform: scaleX(1);
}

07앵커 위치 미니 패널

카드에 anchor-name을 붙이고 CSS 앵커 포지셔닝으로 그 옆(또는 아래)에 미니 정보 패널을 띄우면, 호버했을 때 패널이 카드에 자동으로 붙어 나타납니다. 재고·스펙을 미리 보여주는 상품 목록에 맞습니다.

anchor-nameposition-anchorposition-area
.card { anchor-name: --c7; }
.panel {
  position: absolute;
  position-anchor: --c7;
  position-area: block-end;
  position-try-fallbacks: flip-block;
  opacity: 0;
  transition: opacity $duration $easing, transform $duration $easing;
}
.card:hover ~ .panel { opacity: 1; transform: translateY(0); }

08글래스모피즘 블러

반투명 카드 배경에 backdrop-filter: blur()가 걸려 있다가, 호버하면 블러와 채도가 더 짙어지며 유리 표면이 빛을 받은 것처럼 밝아집니다. 다크 배경 위 프리미엄 카드나 대시보드 위젯에 씁니다.

backdrop-filter: blurrgba 배경saturate()
.card {
  backdrop-filter: blur(6px) saturate(120%);
  transition: backdrop-filter $duration $easing, transform $duration $easing;
}
.card:hover {
  backdrop-filter: blur(14px) saturate(160%);
  transform: translateY(-4px);
}

09선택 링 드로잉

고를 수 있는 카드를 호버하거나 선택하면 테두리를 감싼 SVG 링이 stroke-dashoffset으로 한 바퀴 그려지며 '선택됨'을 알립니다. 플랜 선택이나 라디오형 카드 선택 UI에 맞는 마무리 동작입니다.

SVG stroke-dashoffsetstroke-dasharray:focus-visible
.ring__rect {
  stroke-dasharray: 500;
  stroke-dashoffset: 500;
  transition: stroke-dashoffset $duration $easing;
}
.card:hover .ring__rect,
.card:focus-visible .ring__rect {
  stroke-dashoffset: 0;
}

어디서 깨지는가 — 함정 하나

08 글래스모피즘을 처음 렌더링했을 때 측정값이 이상했습니다. 카드 배경 전체가 눈에 보이게 흐려지는데도 강도_평균이 0.0, 움직인 프레임이 23장 중 0장으로 나왔습니다. 렌더는 실제 재생이 아니라 Web Animations API로 currentTime을 프레임마다 강제로 옮기며 스크린샷을 찍는 방식인데, 헤드리스 크롬이 backdrop-filter만 바뀌는 합성 레이어를 이 방식으로는 프레임 사이사이마다 제대로 다시 칠하지 않는 것으로 보였습니다. 같은 keyframes 안에 transform: translateY(-4px) 하나만 더 넣어 블러와 함께 카드가 살짝 떠오르게 했더니, 같은 조건에서 강도_평균 53.9·움직인 프레임 12장으로 정상 측정됐습니다. 압축 파일 비밀번호는 tvyct8zb이고, 이 기록이 없었다면 08은 "안 움직이는 카드"로 계속 남아 있었을 겁니다. 블러·채도처럼 합성 레이어에만 걸리는 값은 transform이나 opacity를 곁들여야 측정도 되고 사람 눈에도 또렷합니다.

접근성

아홉 개 전부 prefers-reduced-motion: reduce에서 전환·애니메이션만 끄고 마지막 상태는 남깁니다. 01은 캡션이 늘 펼쳐진 채 이미지 확대만 순간적으로 전환되고, 02는 기울기가 전환 없이 순간적으로 적용되고, 03은 뒤집힘이 순간적으로 일어나고, 04는 브래킷이 즉시 켜지고 꺼지고, 05는 뱃지가 튀지 않고 바로 자리를 잡고, 06은 밑줄이 즉시 그어지고, 07은 패널이 순간적으로 뜨고 사라지고, 08은 블러 단계 전환 없이 고정되고, 09는 링이 순간에 완성됩니다. 마우스로 올렸을 때뿐 아니라 키보드로 넘어왔을 때와 손끝으로 눌렀을 때도 똑같은 모습이 나오도록 세 상태를 한 규칙에 묶어 뒀습니다. :has() 지원 범위는 MDN 문서에서, CSS 앵커 포지셔닝 지원 범위는 caniuse에서 확인됩니다. 다른 카드 기법이 궁금하면 이전에 올린 카드 호버 모음(여기)과 글래스모피즘 모음(여기)도 함께 보면 좋습니다.

FAQ

02 구역별 틸트는 :has()를 지원하지 않는 브라우저에서 어떻게 됩니까?

:has()가 없으면 그 셀렉터를 포함한 규칙 자체가 매치되지 않아 카드가 그냥 평평한 상태로 멈춥니다. 레이아웃이 깨지거나 에러가 나지 않는 안전한 성능 저하이고, 최신 Chrome·Safari·Firefox는 이미 지원합니다.

07 앵커 위치 패널은 모든 브라우저에서 똑같이 뜹니까?

Chrome·Edge는 뜨지만 이 글을 쓰는 시점의 Firefox·Safari는 CSS 앵커 포지셔닝을 지원하지 않아 패널이 아예 나타나지 않습니다. 지원하지 않는 브라우저에서도 카드 자체(이미지·제목·가격)는 정상적으로 보이므로 핵심 정보 손실은 없습니다.

React 판에서 09 선택 링은 상태를 어떻게 관리합니까?

SelectRingCard 컴포넌트는 별도의 state 없이 CSS의 :hover·:focus-visible·:active만으로 링을 그립니다. 실제 폼 선택처럼 클릭 후에도 계속 선택된 상태를 남기고 싶다면 부모 컴포넌트에서 aria-pressed와 함께 별도 클래스를 하나 더 얹으면 됩니다.

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.