本文へスキップ
HELLO!
HIGASHIYUKI®.COM
0   /   100

カード

ホバーでカードが浮き上がり、影が深くなる

デモ

操作:カーソルを重ねる。 「さわってみる」を押すと自動再生が止まり、マウスで動かせます。

仕様

用途
カード
動作
DOM
種類
レシピ
動作確認
three 0.186.0・gsap 3.15.0
公開日

しくみと調整

カーソルを重ねたカードは12px持ち上がり、真下に落ちる影が大きく、濃くなります。影は、縁を引き締める近い影と、下に大きく落ちる遠い影を重ねたもので、変化が大きいのは遠い影のほうです。ぼかしと濃さを大きく広げながら、広がりの値はマイナスにしました。影がカードの幅より内側に収まったまま、真下へ落ちる形になります。

ホバーを受け取るのは、カードを包んでいる動かない li で、動くのは中のカード(リンク)だけです。カード自身で受け取ると、持ち上がった分だけ下端の近くでカーソルが外れ、元の高さへ下がってはまた持ち上がる、を繰り返してちらつきます。キーボードでフォーカスが入ったときも同じように持ち上げ、ホバーのないタッチ端末では、タップしたカードが浮いたまま残らないよう、ホバーの動きをつないでいません。

持ち上がるのは0.5秒の power3.out、戻るのは0.6秒の power2.out で、戻りを少しゆっくりにしています。どちらも動き出しの速い out 系なので、隣のカードへ移ると前のカードはすぐ下がり始め、両方が浮いたまま並ぶことはありません。影まで補間できるのは、通常時とホバー時で box-shadow の層の数と値の並び順をそろえてあるからで、GSAP が数値を一つずつ対応させて動かします。途中で出入りしても、トゥイーンに付けた overwrite が前の動きを止め、いまの位置から向きを変えます。

浮く量を控えめにすると落ち着いた反応に、遠い影を濃くすると重たいカードに見えます。中の画像だけを動かすのではなく、カードごと持ち上げて影で高さを伝えるので、記事や商品、制作事例の一覧のように、カードそのものがリンクになっている場面に合う動きです。

コード

HTML
<section class="notes" aria-labelledby="notes-title">
  <div class="notes__head">
    <div>
      <p class="notes__label">JOURNAL</p>
      <h2 class="notes__title" id="notes-title">制作ノート</h2>
    </div>
    <a class="notes__all" href="/journal/">すべての記事を見る</a>
  </div>

  <ul class="notes__list">
    <!-- ホバーは動かない li で受け取り、中のカード(リンク)を浮かせる。カードで受け取ると、浮いた分だけ下端でカーソルが外れてちらつく -->
    <li class="post">
      <!-- リンクの名前は題だけにし、分類と著者・日付は説明として読ませる -->
      <a class="post__card" href="/journal/usable-screens/" aria-labelledby="post-1-title" aria-describedby="post-1-tag post-1-meta">
        <img class="post__image" src="/img/journal-screens.jpg" alt="" width="640" height="400">
        <span class="post__tag" id="post-1-tag">デザイン</span>
        <h3 class="post__title" id="post-1-title">迷わず使える画面をつくる、3つの考え方</h3>
        <span class="post__author" id="post-1-meta">
          <img class="post__avatar" src="/img/nakamura.jpg" alt="" width="64" height="64">
          <span><span class="post__name">中村 優花</span><span class="post__date"><time datetime="2026-09-08">9月8日</time>・6分で読めます</span></span>
        </span>
      </a>
    </li>

    <li class="post">
      <a class="post__card" href="/journal/forty-releases/" aria-labelledby="post-2-title" aria-describedby="post-2-tag post-2-meta">
        <img class="post__image" src="/img/journal-releases.jpg" alt="" width="640" height="400">
        <span class="post__tag" id="post-2-tag">開発</span>
        <h3 class="post__title" id="post-2-title">1年で40回のリリースを重ねてわかったこと</h3>
        <span class="post__author" id="post-2-meta">
          <img class="post__avatar" src="/img/tanaka.jpg" alt="" width="64" height="64">
          <span><span class="post__name">田中 蓮</span><span class="post__date"><time datetime="2026-08-29">8月29日</time>・8分で読めます</span></span>
        </span>
      </a>
    </li>

    <li class="post">
      <a class="post__card" href="/journal/four-day-week/" aria-labelledby="post-3-title" aria-describedby="post-3-tag post-3-meta">
        <img class="post__image" src="/img/journal-four-day.jpg" alt="" width="640" height="400">
        <span class="post__tag" id="post-3-tag">働き方</span>
        <h3 class="post__title" id="post-3-title">週休3日を1年続けてみて</h3>
        <span class="post__author" id="post-3-meta">
          <img class="post__avatar" src="/img/yamamoto.jpg" alt="" width="64" height="64">
          <span><span class="post__name">山本 あかり</span><span class="post__date"><time datetime="2026-08-14">8月14日</time>・5分で読めます</span></span>
        </span>
      </a>
    </li>
  </ul>
</section>

CSS
.notes {
  --ground: #f2f1ed;  /* 背景色 */
  --surface: #ffffff; /* カードの背景色 */
  --ink: #262626;     /* 文字 */
  --mute: #6b6b6b;    /* ラベル・分類・日付 */
  --line: rgba(38, 38, 38, 0.12); /* 分類の枠線 */
  --lift: 12px;       /* 浮き上がる量 */
  /* 影は「近い影, 遠い影」の 2 層。層の数と値の並びをそろえると、GSAP が数値ごとに補間する */
  --shadow-rest: 0 1px 2px 0 rgba(38, 38, 38, 0.06), 0 2px 8px -2px rgba(38, 38, 38, 0.08);
  --shadow-hover: 0 4px 8px -2px rgba(38, 38, 38, 0.1), 0 28px 56px -12px rgba(38, 38, 38, 0.28);
  padding: 64px clamp(16px, 6vw, 96px);
  background: var(--ground);
  color: var(--ink);
}

.notes__head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-end;
  gap: 16px 32px;
  margin-bottom: 48px;
}

.notes__label { margin: 0; color: var(--mute); font-size: 13px; font-weight: 700; line-height: 1; letter-spacing: 0.14em; }
.notes__title { margin: 16px 0 0; font-size: clamp(32px, 5vw, 44px); font-weight: 800; line-height: 1.1; letter-spacing: -0.02em; }
.notes__all { color: inherit; font-size: 15px; font-weight: 600; letter-spacing: 0.04em; text-decoration-thickness: 1px; text-underline-offset: 0.4em; }

.notes__list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 32px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* 浮くカード。影は角丸 4px のカードに付ける */
.post__card {
  display: flex;
  flex-direction: column;
  height: 100%;
  box-sizing: border-box;
  min-height: 416px;
  padding: 16px 16px 24px;
  border-radius: 4px;
  background: var(--surface);
  box-shadow: var(--shadow-rest);
  color: inherit;
  text-decoration: none;
}

.post__card:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }

.post__image { display: block; width: 100%; height: auto; aspect-ratio: 8 / 5; object-fit: cover; background: #d9d6cc; }

/* 分類のチップ(和文の小さなラベル) */
.post__tag {
  align-self: flex-start;
  margin-top: 24px;
  padding: 0 12px;
  border-radius: 999px;
  box-shadow: inset 0 0 0 1px var(--line);
  color: var(--mute);
  font-size: 12px;
  font-weight: 700;
  line-height: 24px;
  letter-spacing: 0.06em;
}

/* 文節で折り返し、1 語だけの行を作らない */
.post__title { margin: 16px 0 24px; font-size: 20px; font-weight: 700; line-height: 1.3; letter-spacing: -0.01em; word-break: auto-phrase; text-wrap: balance; }

.post__author { display: flex; align-items: center; gap: 12px; margin-top: auto; }
.post__avatar { flex: none; width: 32px; height: 32px; border-radius: 50%; object-fit: cover; background: #d9d6cc; }
.post__name { display: block; font-size: 14px; font-weight: 600; line-height: 1.4; letter-spacing: 0.04em; }
.post__date { display: block; margin-top: 2px; color: var(--mute); font-size: 13px; line-height: 1.4; letter-spacing: 0.04em; font-variant-numeric: tabular-nums; }

JavaScript
import { gsap } from "gsap";

// タッチ端末ではホバーの動きを付けない(タップで浮いたまま残る)。キーボードのフォーカスにはどの端末でも応える
const canHover = window.matchMedia("(hover: hover)").matches;
// 動きを減らす設定では、ほぼ一瞬で切り替える
const speed = window.matchMedia("(prefers-reduced-motion: reduce)").matches ? 20 : 1;

document.querySelectorAll(".post").forEach((slot) => {
  const card = slot.querySelector(".post__card");
  const style = getComputedStyle(slot);
  const lift = parseFloat(style.getPropertyValue("--lift"));
  const rest = style.getPropertyValue("--shadow-rest").trim();
  const raised = style.getPropertyValue("--shadow-hover").trim();

  // 浮くのは速く、元の位置へ戻るのは少しゆっくり。overwrite で、途中で出入りしても今の位置から向きを変える
  const rise = () => {
    gsap.to(card, { y: -lift, boxShadow: raised, duration: 0.5 / speed, ease: "power3.out", overwrite: "auto" });
  };
  const settle = () => {
    // カーソルもキーボードのフォーカスもカードから外れたときだけ元の位置へ戻す
    if ((canHover && slot.matches(":hover")) || card.matches(":focus-visible")) return;
    gsap.to(card, { y: 0, boxShadow: rest, duration: 0.6 / speed, ease: "power2.out", overwrite: "auto" });
  };

  if (canHover) {
    slot.addEventListener("pointerenter", rise);
    slot.addEventListener("pointerleave", settle);
  }
  card.addEventListener("focus", rise);
  card.addEventListener("blur", settle);
});

インタラクションデザインについて、
お気軽にご相談ください。

ここに並べているのは、Web サイトの中で動く UI のサンプルです。新しく作るサイトに組み込むことも、いま公開中のサイトに、ボタンやヒーロー、スクロールの演出だけを足すこともできます。設計から実装までの進め方はWeb サイト制作、端末ごとの見え方や表示速度の考え方はレスポンシブウェブサイトのページにまとめています。映像として作る動きをお探しの場合はモーショングラフィックスをご覧ください。