本文へスキップ
HELLO!
HIGASHIYUKI®.COM
0   /   100
ナビゲーション

スクロールでヘッダーが縮み、背景が付いて影が落ちる

デモ

操作:スクロール。 「さわってみる」を押すと自動再生が止まり、マウスで動かせます。 触っている間は、ホイールでデモの中がスクロールします。

仕様

動作
DOM
種類
レシピ
使うもの
ScrollTrigger
動作確認
three 0.186.0・gsap 3.15.0
公開日

しくみと調整

ページの上に止めたヘッダーを、スクロールに合わせて低くし、白い地と下の罫線・影を付けて、ロゴも小さくします。ScrollTrigger で、先頭から 150px 送るあいだの量にそのまま結ぶので(scrub)、途中で止めれば途中の形で止まり、戻せば同じだけ広がります。

高さ・地・罫線・影・ロゴの倍率は、どれも CSS の変数 --shrink(0 から 1)ひとつから計算しています。JS が動かすのはこの値だけなので、色や大きさは CSS だけで直せます。高さは 112px から 64px(ナビの上下の余白が 32px から 8px)、ロゴは 0.72 倍で、デザインの決まりのロゴの大きさに収まります。150px で縮めきると、見出しを読み始める前に落ち着きます。

下の罫線は border ではなく内側の影で引き、落ちる影と 1 つにまとめます。border で引くと、線の太さのぶん中身の上下の中央が 0.5px ずれます。ロゴは scale で縮め、原点を左端に置きます。文字の大きさを動かすと並びが計算し直され、ナビの位置までずれます。本文の先頭には高いときのヘッダーの分の余白を取っておくと、縮んでも本文は動きません。

動きを減らす設定では、途中を見せずに、150px を越えたところで縮んだヘッダーへ切り替えます。スクロールに連動させず、一定量を越えたら 0.35 秒ほどで縮める形にしたいときは、同じ変数を ScrollTrigger の出入りで動かします。項目が並びきらない幅ではナビを外し、ロゴとボタンだけにしています。

EC・ブランドサイト・メディアの共通ヘッダーに向いています。先頭では大きなロゴと余白で雰囲気を見せ、読み始めたら画面を本文に返せます。

コード

HTML
<header class="site-header">
  <a class="site-header__logo" href="/">Hi! I'm HIGASHIYUKI®</a>

  <nav class="site-header__nav" aria-label="メイン">
    <ul>
      <li><a href="/shop/" aria-current="page">商品一覧</a></li>
      <li><a href="/rooms/">お部屋</a></li>
      <li><a href="/materials/">素材</a></li>
      <li><a href="/journal/">読みもの</a></li>
      <li><a href="/showrooms/">ショールーム</a></li>
    </ul>
  </nav>

  <a class="site-header__cart" href="/cart/">カート(2)</a>
  <a class="site-header__cta" href="/visit/">来店を予約</a>
</header>

<!-- 本文の先頭は、高いときのヘッダーの分だけ空けておく(縮んでも本文は動かない) -->
<main class="shop">
  <p class="shop__label">Spring collection · 2026</p>
  <h1 class="shop__title">深呼吸したくなる、<br>そんな部屋を。</h1>
  <p class="shop__lead">ここに説明文が入ります。</p>

  <img class="shop__hero" src="/img/living-room.jpg" alt="" width="1600" height="600">

  <h2 class="shop__heading">今季の新作</h2>
  <ul class="shop__grid">
    <li>
      <img src="/img/arc-lounge-chair.jpg" alt="" width="800" height="560">
      <h3>アーチ型ラウンジチェア <span>¥168,000</span></h3>
      <p>オーク・ブークレ</p>
    </li>
    <li>
      <img src="/img/dune-table-lamp.jpg" alt="" width="800" height="560">
      <h3>陶器のテーブルランプ <span>¥42,900</span></h3>
      <p>陶器・リネンのシェード</p>
    </li>
    <li>
      <img src="/img/pebble-vase-set.jpg" alt="" width="800" height="560">
      <h3>丸い花器 <span>¥19,800</span></h3>
      <p>炻器・3点セット</p>
    </li>
  </ul>
</main>

CSS
:root {
  --header-tall: 112px; /* 先頭にいるとき。ナビ(高さ 48px)の上下に 32px */
  --header-short: 64px; /* 縮んだとき。上下に 8px */
  --logo-small: 0.72; /* 縮んだときのロゴの倍率(25px → 18px) */
}

/* --shrink(0 = 高い、1 = 縮んだ)ひとつから、高さ・地・罫線・影・ロゴの大きさを決める。JS はこの値だけを動かす */
.site-header {
  --shrink: 0;
  --surface: 255 255 255; /* 縮んだときの地(rgb) */
  --shade: 38 38 38; /* 罫線と影の色(rgb) */
  --ink: #262626;
  --mute: #6b6b6b;
  --on-ink: #ffffff; /* 主ボタンの文字 */
  position: fixed;
  inset: 0 0 auto;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 32px;
  box-sizing: border-box;
  height: calc(var(--header-tall) - (var(--header-tall) - var(--header-short)) * var(--shrink));
  padding: 0 clamp(16px, 7.5vw, 96px);
  background-color: rgb(var(--surface) / var(--shrink));
  /* 下の罫線は inset の影で引き、落ちる影と 1 つにまとめる(border で引くと、中身の上下の中央が 0.5px ずれる) */
  box-shadow:
    inset 0 -1px 0 rgb(var(--shade) / calc(0.12 * var(--shrink))),
    0 14px 34px -12px rgb(var(--shade) / calc(0.3 * var(--shrink)));
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.04em;
  white-space: nowrap;
}

.site-header a { color: inherit; text-decoration: none; }
.site-header a:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }

/* ロゴは左端を軸に縮める。scale は並びを動かさないので、ナビの位置は変わらない */
.site-header__logo {
  flex: none;
  font-size: 25px;
  font-weight: 700;
  letter-spacing: 0.12em;
  transform-origin: 0 50%;
  scale: calc(1 - (1 - var(--logo-small)) * var(--shrink));
}

.site-header__nav { flex: 1; }
.site-header__nav ul { display: flex; justify-content: center; gap: 32px; margin: 0; padding: 0; list-style: none; }
.site-header__nav a { color: var(--mute); }
.site-header__nav [aria-current="page"] { color: var(--ink); }

.site-header .site-header__cta {
  flex: none;
  height: 40px;
  padding: 0 24px;
  border-radius: 999px;
  background-color: var(--ink);
  color: var(--on-ink);
  line-height: 40px;
}

.shop {
  padding: calc(var(--header-tall) + 32px) clamp(16px, 7.5vw, 96px) 96px;
  background-color: #f2f1ed;
  color: #262626;
  font-size: 18px;
  line-height: 1.8;
  letter-spacing: 0.04em;
}

.shop__label { margin: 0 0 24px; color: #6b6b6b; font-size: 13px; font-weight: 700; line-height: 1; letter-spacing: 0.14em; text-transform: uppercase; }
.shop__title { margin: 0 0 24px; font-size: clamp(32px, 5vw, 64px); font-weight: 800; line-height: 1.05; letter-spacing: -0.02em; }
.shop__lead { margin: 0; word-break: auto-phrase; }
.shop__hero { display: block; width: 100%; height: auto; aspect-ratio: 16 / 5; margin-top: 48px; border-radius: 4px; background-color: #e0d2bf; object-fit: cover; }
.shop__heading { margin: 80px 0 24px; font-size: clamp(32px, 4vw, 44px); font-weight: 800; line-height: 1.1; letter-spacing: -0.02em; }
.shop__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 32px; margin: 0; padding: 0; list-style: none; }
.shop__grid img { display: block; width: 100%; height: auto; aspect-ratio: 10 / 7; border-radius: 4px; background-color: #e6ddd0; object-fit: cover; }
.shop__grid h3 { display: flex; justify-content: space-between; gap: 16px; margin: 16px 0 4px; font-size: 20px; line-height: 1.3; letter-spacing: -0.01em; word-break: auto-phrase; }
.shop__grid h3 span { font-size: 16px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.shop__grid p { margin: 0; color: #6b6b6b; font-size: 14px; line-height: 1.7; }

/* 項目が並びきらない幅では、ナビを外して(案件ではメニューのボタンへ)ロゴとボタンだけにする */
@media (max-width: 1180px) {
  .site-header__nav { display: none; }
  .site-header__logo { margin-right: auto; }
}

@media (max-width: 560px) {
  :root { --header-tall: 80px; --header-short: 56px; --logo-small: 0.875; }
  .site-header { gap: 16px; padding: 0 16px; }
  .site-header__logo { font-size: 16px; }
  .site-header__cart { display: none; }
  .site-header .site-header__cta { padding: 0 16px; }
}

JavaScript
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";

gsap.registerPlugin(ScrollTrigger);

const SHRINK = 150; // 先頭からこの量(px)を送るあいだに縮めきる
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

// 見た目は CSS が --shrink(0〜1)から決めるので、JS はこの値だけを動かす
document.querySelectorAll(".site-header").forEach((header) => {
  if (reduceMotion) {
    // 動きを減らす設定では、途中を見せずに、縮めきる量を越えたところで切り替える
    ScrollTrigger.create({
      start: SHRINK,
      end: "max",
      onEnter: () => gsap.set(header, { "--shrink": 1 }),
      onLeaveBack: () => gsap.set(header, { "--shrink": 0 }),
    });
    return;
  }

  gsap.fromTo(
    header,
    { "--shrink": 0 },
    {
      "--shrink": 1,
      ease: "none", // 送った量に比例させる。戻せば同じだけ広がる
      scrollTrigger: {
        start: 0,
        end: SHRINK,
        scrub: true, // スクロール量にそのまま連動させる(数値の scrub は実時間で追うので遅れる)
      },
    }
  );
});

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

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