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

ナビゲーション

本文を読み終えると、ページが上へ抜けて後ろのフッターが現れる

デモ

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

仕様

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

しくみと調整

本文を最後まで読み進めると、ページがそのまま上へ抜けていき、後ろに控えていた暗いフッターが下から見えてきます。ロゴやメニュー、大きな「LET'S TALK」は、見えた割合に合わせて少しずつ上がりながら、はっきりしていきます。本文の下の角は丸めて、持ち上がっていくページの縁が見えるようにしました。

本文は z-index で上に重ね、フッターは本文の直後に置いて、position: sticky と bottom: 0 で画面の下に止めています。sticky の要素は、本来の位置が画面より下にある間は画面の下端に留まります。だからフッターはずっと本文の後ろにいて、本文の下端が上へ抜けた所から見えていくわけです。position: fixed で重ねる方法もありますが、フッターが文書の流れから外れるので、本文の下にフッターの高さぶんの余白を計算して足さなければなりません。

動きは ScrollTrigger の scrub で、本文の下端が画面の下端に着いてから、フッターの高さぶん送りきるまでの間に結び付けました。フッターの中身全体を下から少し遅れて追わせ、そのうえで項目を0.06秒ずつずらして上げています。大きな語は1字ずつ span に分けたので、左の字から順に立ち上がります。追う量を増やすほど奥からせり上がる感じが強くなり、0にすると、止まったフッターがただ見えてくるだけになります。

フッターは見えていない間も本文の後ろにあるので、Tab でフッターのリンクにフォーカスが移っても、そのままでは画面に映りません。フォーカスが入ったら最後まで送るようにしました。大きな語は字に分けたあとも、リンクの aria-label で1語として読ませています。なお、フッターは画面の高さより低く作ってください。高いと、上の段のメニューが送りきる直前まで本文の後ろに隠れたままになります。

コード

HTML
<!-- 本文(.reveal-page)を上に重ね、フッターは position: sticky で本文の後ろに止めておく。本文を最後まで送ると、後ろのフッターが見えてくる -->
<main class="reveal-page" id="top">
  <div class="reveal-page__bar">
    <a class="reveal-page__logo" href="/">Hi! I'm HIGASHIYUKI®</a>
    <nav aria-label="メイン">
      <ul class="reveal-page__links">
        <li><a href="/about/">私たちについて</a></li>
        <li><a href="/service/">サービス</a></li>
        <li><a href="/works/">制作実績</a></li>
        <li><a href="/contact/">お問い合わせ</a></li>
      </ul>
    </nav>
  </div>
  <p class="reveal-page__eyebrow">Design Studio</p>
  <h1 class="reveal-page__title">ブランドの声を、<br>かたちにする。</h1>
  <p>ここに本文が入ります。ここに本文が入ります。ここに本文が入ります。ここに本文が入ります。</p>
  <p>ここに本文が入ります。ここに本文が入ります。ここに本文が入ります。ここに本文が入ります。</p>
  <p>ここに本文が入ります。ここに本文が入ります。ここに本文が入ります。ここに本文が入ります。</p>
  <p>ここに本文が入ります。ここに本文が入ります。ここに本文が入ります。ここに本文が入ります。</p>
  <p>ここに本文が入ります。ここに本文が入ります。ここに本文が入ります。ここに本文が入ります。</p>
  <p>ここに本文が入ります。ここに本文が入ります。ここに本文が入ります。ここに本文が入ります。</p>
</main>

<footer class="reveal-footer">
  <div class="reveal-footer__inner">
    <div class="reveal-footer__top">
      <a class="reveal-footer__item reveal-footer__logo" href="/">Hi! I'm HIGASHIYUKI®</a>
      <nav aria-label="フッター">
        <ul class="reveal-footer__links">
          <li class="reveal-footer__item"><a href="/about/">私たちについて</a></li>
          <li class="reveal-footer__item"><a href="/service/">サービス</a></li>
          <li class="reveal-footer__item"><a href="/works/">制作実績</a></li>
          <li class="reveal-footer__item"><a href="/contact/">お問い合わせ</a></li>
        </ul>
      </nav>
    </div>
    <p class="reveal-footer__label">Contact</p>
    <a class="reveal-footer__word" href="/contact/">LET'S TALK</a>
    <div class="reveal-footer__bottom">
      <small>© 2026 Hi! I'm HIGASHIYUKI®</small>
      <a href="#top">ページの先頭へ ↑</a>
    </div>
  </div>
</footer>

CSS
.reveal-page,
.reveal-footer {
  --paper: #f2f1ed;
  --ink: #262626;
  --mute: #6b6b6b;
  --night: #111111;
  --night-ink: #eeece6;
  --night-mute: #a8a6a0;
  --night-line: rgba(238, 236, 230, 0.14);
  --gutter: clamp(16px, 7.5vw, 96px);
}

/* 本文はフッターより上に重ね、下の角を丸めて、抜けていく縁を見せる */
.reveal-page {
  position: relative;
  z-index: 1;
  padding: 32px var(--gutter) 160px;
  border-radius: 0 0 32px 32px;
  background: var(--paper);
  color: var(--ink);
}

.reveal-page__bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 16px 32px; min-height: 48px; }
.reveal-page__logo, .reveal-footer__logo { font-size: 18px; font-weight: 700; letter-spacing: 0.12em; white-space: nowrap; }
.reveal-page__links, .reveal-footer__links { display: flex; flex-wrap: wrap; gap: 12px 32px; margin: 0; padding: 0; list-style: none; }
.reveal-page a, .reveal-footer a { color: inherit; font-size: 14px; font-weight: 600; letter-spacing: 0.04em; text-decoration: none; white-space: nowrap; }
.reveal-page__eyebrow, .reveal-footer__label { margin: 120px 0 0; color: var(--mute); font-size: 13px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; }
.reveal-page__title { margin: 24px 0 48px; font-size: clamp(40px, 8vw, 72px); font-weight: 800; letter-spacing: -0.02em; line-height: 1.05; }
.reveal-page p:not([class]) { max-width: 36em; margin: 0 0 24px; font-size: 18px; line-height: 1.8; letter-spacing: 0.04em; }

/* 本文の後ろで、画面の下に止まっている。本文が上へ抜けた所から見えてくる。画面の高さより低く作る */
.reveal-footer {
  position: sticky;
  bottom: 0;
  z-index: 0;
  overflow: clip;
  background: var(--night);
  color: var(--night-ink);
}

.reveal-footer__inner { padding: 72px var(--gutter) 0; }
.reveal-footer__top { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 24px 40px; }
.reveal-footer__label { margin-top: 80px; color: var(--night-mute); }

/* 大きな語。字は script.js が 1 字ずつ span に分ける */
.reveal-footer .reveal-footer__word {
  display: block;
  width: fit-content;
  margin: 16px 0 0 -0.035em;
  font-size: clamp(44px, 13vw, 172px);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.08;
}

.reveal-footer__word span { display: inline-block; white-space: pre; }

.reveal-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 12px 24px;
  margin-top: 40px;
  padding: 18px 0 32px;
  box-shadow: inset 0 1px 0 var(--night-line);
  color: var(--night-mute);
}

.reveal-footer__bottom small, .reveal-footer__bottom a { font-size: 13px; }
.reveal-page a:focus-visible, .reveal-footer a:focus-visible { outline: 2px solid currentColor; outline-offset: 4px; }

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

gsap.registerPlugin(ScrollTrigger);

const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

document.querySelectorAll(".reveal-footer").forEach((footer) => {
  const page = footer.previousElementSibling; // 本文(フッターの直前に置く)
  const inner = footer.querySelector(".reveal-footer__inner");
  const word = footer.querySelector(".reveal-footer__word");

  // 大きな語を 1 字ずつ span に分ける。スクリーンリーダーには aria-label の 1 語として読ませる
  const text = word.textContent.trim();
  word.setAttribute("aria-label", text);
  word.replaceChildren(...[...text].map((ch) => {
    const span = document.createElement("span");
    span.textContent = ch;
    span.setAttribute("aria-hidden", "true");
    return span;
  }));

  // フッターは本文の後ろに隠れているので、Tab でフォーカスが入ったら最後まで送って見えるようにする
  footer.addEventListener("focusin", () => {
    const end = document.documentElement.scrollHeight - innerHeight;
    if (scrollY < end - 1) window.scrollTo({ top: end, behavior: reduceMotion ? "auto" : "smooth" });
  });

  // 動きを減らす設定では、フッターの中身をはじめから表示したままにする
  if (reduceMotion) return;

  // 本文の下端が画面の下端に着いてから、フッターの高さぶん送りきるまで(フッターが見えていく間)に動かす
  gsap
    .timeline({
      defaults: { ease: "power2.out" },
      scrollTrigger: { trigger: page, start: "bottom bottom", end: () => `+=${footer.offsetHeight}`, scrub: true, invalidateOnRefresh: true },
    })
    .fromTo(inner, { y: () => footer.offsetHeight * 0.27 }, { y: 0, duration: 1, ease: "none" }, 0) // 中身ごと少し遅れて上がる
    .fromTo(footer.querySelectorAll(".reveal-footer__item"), { y: 36, opacity: 0.15 }, { y: 0, opacity: 1, duration: 0.5, stagger: 0.06 }, 0.3)
    .fromTo(footer.querySelector(".reveal-footer__label"), { opacity: 0.15 }, { opacity: 1, duration: 0.4, ease: "none" }, 0.3)
    .fromTo(word.children, { yPercent: 36, opacity: 0.1 }, { yPercent: 0, opacity: 1, duration: 0.5, stagger: 0.035 }, 0.2)
    .fromTo(footer.querySelector(".reveal-footer__bottom"), { y: 24, opacity: 0.15 }, { y: 0, opacity: 1, duration: 0.4 }, 0.55);
});

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

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