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

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

デモ

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

仕様

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

しくみと調整

カーソルが重なったら、カードを 12px 持ち上げながら、影を浅い 2 層から深い 2 層へ gsap.to で補間し、離れたら元の高さと影に戻します。ホバーはカードを包む動かない枠(リストの li)で拾い、中のカード(リンク)だけを動かします。カードそのもので拾うと、浮いた分だけ下端の近くでカーソルが外れて沈み、また重なって浮く、を繰り返してちらつきます。

影は、縁を締める近い影と、下に大きく落ちる遠い影の 2 層です。遠い影はぼかしを 8px から 56px、濃さを 0.08 から 0.28 へ広げ、広がりを −12px にして、影がカードの幅より内側に収まって下へ落ちる形にしています。浮くのは 0.5 秒の power3.out で速く、沈むのは 0.6 秒の power2.out で少しゆっくりにしています。

影を補間するには、休みとホバーで box-shadow の層の数と値の並び(x・y・ぼかし・広がり・色)をそろえます。そろっていれば、GSAP が数値ごとに補間します。途中で出入りしても今の位置から向きを変えられるよう、トゥイーンには overwrite を付けます。沈むトゥイーンは out 系の ease なので、隣のカードへ移ると前のカードはすぐ沈み始め、2 枚とも浮いたまま並びません。

キーボードで焦点がカードに入ったときも同じように浮かせ、動きを減らす設定ではほぼ一瞬で切り替えます。ホバーの無いタッチ端末では、タップで浮いたまま残らないよう、ホバーの動きはつなぎません。

浮く量を 4〜6px にすると控えめに、遠い影を濃くすると重く見えます。枠の中の画像だけを動かす形と違い、カードごと持ち上げて影で高さを見せるので、記事・商品・事例の一覧に向いています。

コード

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 サイト制作、端末ごとの見え方や表示速度の考え方はレスポンシブウェブサイトのページにまとめています。映像として作る動きをお探しの場合はモーショングラフィックスをご覧ください。