GODRICH

カード フリップ アニメーション 9選【コピペ20行】

カード フリップ アニメーションとは、マウスを乗せたりクリックしたりするとカードがくるっと回転したり半分に折れたりして、裏側の内容が現れる効果のことです。コードが分からなくても、下のデモにマウスを乗せてみれば何が起きているか一目で分かります。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

9個は人気順ではなく、仕組みが単純なものから複雑なものへ、最後はアクセシビリティにつながる順番で並んでいます。もっとも基本のホバー反転(01)とクリック反転(02)から始めて、紙のように折って開くアイデア型の変形(03〜04)を経たあと、複数枚のカードを扱う実戦パターン(05〜08)に進み、同じカード反転をモーションを抑えて再生する09番で締めくくります。

01横に反転

カードにマウスを乗せるとY軸を中心に180度回転し、裏面が現れます。クリックしなくても、見るだけで裏面が気になるカードに使います。

rotateYperspectivebackface-visibility
.fy-card { perspective: 700px; }
.fy-card__inner {
  transform-style: preserve-3d;
  transition: transform $duration $easing;
}
.fy-card:hover .fy-card__inner { transform: rotateY(180deg); }
.fy-card__face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}
.fy-card__face--back { transform: rotateY(180deg); }

02縦に反転(クリック)

カードをクリックするとX軸を中心に縦に反転し裏面が表示されます。タッチ端末はホバーがないため、クリック・タップで発動させます。

rotateXaria-pressedJS 6줄
var btn = document.querySelector(".fx-card");
btn.addEventListener("click", function () {
  var open = btn.getAttribute("aria-pressed") === "true";
  btn.setAttribute("aria-pressed", String(!open));
  btn.classList.toggle("is-flipped", !open);
});

03半分に折る

カードが中央の線を基準に半分に折りたたまれ、内容が内側に隠れます。紙のカードを閉じるように、メニュー表や案内カードを畳むときに使います。

preserve-3drotateYtransform-origin
.fh-half--fold {
  transform-style: preserve-3d;
  transform-origin: left center;
  transition: transform $duration $easing;
}
.fh-half__face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}
.fh-half__face--back {
  background: rgba(23, 20, 26, .78);
  transform: rotateY(180deg);
}

04手紙を開く

折りたたまれていたカードが手紙を開くように上下2段が順番に広がり、中身が現れます。下の段だけ少し遅れて開くので、封筒を開ける感覚になります。

rotateXtransition-delaytransform-origin: top
.ul-flap--top { transform-origin: top center; }
.ul-flap--bottom { transform-origin: bottom center; }

@keyframes ul-open-top {
  0%, 25%   { transform: rotateX(0); }
  60%, 100% { transform: rotateX(-172deg); }
}
@keyframes ul-open-bottom {
  0%, 40%   { transform: rotateX(0); }
  75%, 100% { transform: rotateX(172deg); }
}
.ul-scene .ul-flap--top { animation: ul-open-top ($duration * 2.6) $easing infinite; }
.ul-scene .ul-flap--bottom {
  animation: ul-open-bottom ($duration * 2.6) $easing infinite;
  animation-delay: 120ms;
}

05山札をシャッフル

積み重なったカードの一番上が反転して後ろへ移動し、次のカードが見えるようになります。レビューやおすすめ商品のカードを1枚ずつめくって見せたいときに使います。

z-indexrotateYtranslateZ
.ss-card--2 { transform: translateZ(-16px) translateY($sp-2); opacity: .85; }
.ss-card--3 { transform: translateZ(-32px) translateY($sp-4); opacity: .65; }

@keyframes ss-shuffle {
  0%, 30%   { transform: translateZ(0) translateX(0) rotateY(0); opacity: 1; }
  70%       { transform: translateZ(-8px) translateX(-46px) rotateY(-150deg); opacity: .25; }
  71%, 100% { transform: translateZ(0) translateX(0) rotateY(0); opacity: 1; }
}
.ss-stack .ss-card--1 { z-index: 3; animation: ss-shuffle ($duration * 6) $easing infinite; }

06ページめくり

カードの右端を基準に本のページをめくるように回転し、下のカードが現れます。影も一緒に動くので、紙がふわっと持ち上がる感覚が出ます。

rotateYtransform-origin: rightbox-shadow
.bp-page {
  transform-style: preserve-3d;
  transform-origin: right center;
  transition: transform $duration $easing, box-shadow $duration $easing;
}
.bp-page__face { backface-visibility: hidden; }
.bp-page__face--back { transform: rotateY(180deg); }

@keyframes bp-turn {
  0%, 30%   { transform: rotateY(0); }
  70%, 100% { transform: rotateY(-155deg); }
}

07価格を見せるカード

カードにマウスを乗せると反転し、裏面に割引価格が現れます。定価と割引価格を1枚のカードで見比べたいときに使います。

rotateYbackface-visibility: hidden180ms
.rp-card__inner {
  transform-style: preserve-3d;
  transition: transform $duration $easing;
}
.rp-card:hover .rp-card__inner { transform: rotateY(180deg); }
.rp-card__face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}
.rp-card__face--back { transform: rotateY(180deg); }

084面キューブ

カード1枚が立方体のように4つの面を持ち、クリックするたびに1面ずつ回転します。画像・説明・価格・購入ボタンを1枚のカードにまとめたいときに使います。

preserve-3dtranslateZrotateY
var cube = document.querySelector(".cb-cube");
var step = 0;
cube.addEventListener("click", function () {
  step = (step + 1) % 4;
  cube.style.setProperty("--cb-step", String(step));
});

09モーション削減版

同じカード反転をprefers-reduced-motionが有効な環境で再生すると、回転の代わりに柔らかいクロスフェードに切り替わります。この項目はその切り替え自体を見せています。

prefers-reduced-motionopacitytransition
@media (prefers-reduced-motion: reduce) {
  .rm-card:hover .rm-card__inner { transform: none; transition: none; }
  .rm-card:hover .rm-card__face--front {
    opacity: 0;
    transition: opacity $dur-base $ease-out;
  }
  .rm-card:hover .rm-card__face--back {
    opacity: 1;
    transform: none;
    transition: opacity $dur-base $ease-out;
  }
}

カード フリップはどこで壊れるか

もっとも先に見つかった落とし穴は03番の半分に折るカードでした。折れる側の面にはbackface-visibility: hiddenを掛けてあるので裏面がこちら向きになった瞬間はきちんと消えますが、回転角度がちょうど90度に近づく直前のフレームでは面がまだ完全に垂直を超えておらず、遠近法で押しつぶされた極細の文字がうっすらと透けて見えます。hiddenは面が90度を超えてはじめて完全に消える仕組みで、それまでは元の角度のまま描画され続けるためです(MDNのbackface-visibilityドキュメントを参照)。実際に撮影したフレーム画像(回転角90度付近の1枚)でも、この薄いにじみをはっきり確認できました。08番の4面キューブにも似た落とし穴があり、4つの面をつなぎ合わせるときに使うtranslateZの値がカード1辺の半分と正確に一致していないと、面と面の間にすき間ができたり、逆に重なって見えたりします。110pxのカードならtranslateZは必ず55pxにしないと、面がずれて立方体に見えなくなります。どちらも一コマずつ確認してはじめて気づけた不具合で、通しで再生しただけでは見逃していました。9個のファイルが入ったzipの合言葉はzmahx9ysで、解凍するとvanillaフォルダとreactフォルダがそれぞれ入っています。

アクセシビリティ — 9個がreduced-motionで残すもの

システムで動作を減らす設定を入れている場合、項目ごとに反応が違います。01・02・07番はtransform: none !importantで反転自体が止まるため、マウスを乗せてもクリックしても裏面は一切見えず表面のままです — 裏面の情報が必要なら、別のボタンなど反転以外の手段を用意する必要があります。08番はクリックで状態が変わること自体はそのままに、なめらかな切り替えだけを止めるので、クリックすると一瞬でぱっと切り替わります。03・04・05・06番はそもそもホバーやクリックのトリガーがなく自動再生ループだけなので、動作を減らす設定と一緒に消える対象自体がありません。09番はこの切り替え自体がテーマなので、回転の代わりにopacityのクロスフェードだけを残す様子を常に見せるように作ってあります。

カード反転以外のフィードバック系アニメーションが気になる方は価格表アニメーション9選も参考になりますし、クリックで反応するUIはクリック・タッチのフィードバックアニメーション9選でも探せます。ほかのCSSアニメーションはCSSカテゴリーからまとめて見られます。

FAQ

カード フリップのCSSはどのブラウザでも同じように見えますか?

rotateY・rotateXとpreserve-3dは最新のChrome・Edge・Safari・Firefoxのすべてで動作します。ただしごく古いブラウザでは3D回転なしにカードの内容がそのまま切り替わって見えることがあるため、決済のような重要な画面では、クリックでも同じ情報を確認できる代替手段を一緒に用意しておくと安全です。

JSなしでカードを反転できますか?

はい、01・07番のように:hoverで発動するカードはCSSだけで十分です。反転した状態を覚えておく必要がある02・08番はクリックのたびに状態を保存しなければならないため、JavaScriptが6〜8行だけ必要になります。

zip内のvanillaとreactのコードは違いますか?

画面で見える動き自体は同じです。vanilla側はSCSSファイルの先頭でduration・easing・colorの3つの変数を直接書き換え、react側は同じ値をコンポーネントのpropsとして受け取り、--durationのようなCSSカスタムプロパティに渡している点だけが異なります。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。