Motionを使ってスクロール連動のヘッダーを滑らかに動かす方法

はじめに

JavaScriptでアニメーションを実装したいとき、CSSだけで頑張るか、重量級ライブラリを入れるかで悩む場面は多いです。特に「スクロール量に連動してUIを少し変化させたい」くらいの要件だと、実装を重くしすぎたくありません。

そんなときに扱いやすいのが Motionです。animate() で基本アニメーションを書きつつ、scroll() でスクロール進捗を拾えるので、実務のUIモーションをシンプルに組み立てやすくなります。

この記事では、まず基本の animate() から入り、次にスクロール連動ヘッダーを作る実践コードまでをまとめます。あわせて prefers-reduced-motion を使った配慮も入れて、運用しやすい形にします。

機能やライブラリの概要

Motion は JavaScript でアニメーションを作るためのライブラリです。公式ドキュメントでは animate()scroll() が中心機能として整理されています。

実務で嬉しいポイントは次の3つです。

  • 単発アニメーションを短い記述で書ける
  • スクロール進捗を 0 から 1 の値で扱える
  • 既存のHTML/CSS構造を大きく崩さず導入しやすい

「UI全体を演出する」より、「ヘッダーやカードに小さく効かせる」設計と特に相性がいいです。

インストール方法

実務では npmpnpm で入れる形が扱いやすいです。既存のNode環境があるなら、まずは次のどれかでOKです。

PowerShell
npm install motion
PowerShell
pnpm add motion
PowerShell
yarn add motion

ビルド環境がない検証用途ならCDNでも試せます。ただし、本番運用では依存管理しやすいパッケージ導入をおすすめします。

基本の使い方

最初は、ボタン押下でカードをフェードインさせる基本例です。animate() だけで十分に書けます。

See the Pen Motion by watanabe (@web-sourcecode) on CodePen.

HTML

HTML
<button id="js-open">詳細を見る</button>
<section id="js-panel" hidden>
  <h2>今週の更新</h2>
  <p>モーション改善の検証メモをまとめました。</p>
</section>

JavaScript

JavaScript
import { animate } from "motion"

const openButton = document.getElementById("js-open")
const panel = document.getElementById("js-panel")

openButton.addEventListener("click", () => {
  panel.hidden = false

  animate(
    panel,
    { opacity: [0, 1], y: [12, 0] },
    { duration: 0.35, easing: "ease-out" }
  )
})

このコードでは、opacityy を同時に変化させて「少し浮き上がる」見せ方にしています。UIの初回表示や軽い強調にちょうどよいパターンです。

便利な使いどころ

Motion は「常時動かす」より「必要な瞬間にだけ動かす」用途で効きます。例えば次のような場面です。

  • スクロール時にヘッダーをコンパクト化する
  • フィルターパネル開閉時に自然な移動を入れる
  • カード一覧の初回表示を段階的に見せる
  • ページ内ジャンプ時に視線誘導を追加する

特にスクロール連動は、デザイン面の効果が大きいわりに実装が複雑になりやすい部分です。scroll() を使うと、進捗値をもとにロジックを書けるのでメンテしやすくなります。

応用コード

ここからは、スクロール量に応じてヘッダーを縮める実務例です。さらに、prefers-reduced-motion が有効な環境ではアニメーションを弱める分岐を入れています。

See the Pen Motion 2 by watanabe (@web-sourcecode) on CodePen.

HTML

HTML
<header id="js-site-header" class="site-header">
  <h1 class="site-header__title">Site Demo Blog</h1>
</header>

CSS

CSS
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  height: 88px;
  display: grid;
  align-items: center;
  padding-inline: 20px;
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid #e5e7eb;
}

.site-header__title {
  font-size: 24px;
  transition: font-size 0.15s linear;
}

JavaScript

JavaScript
import { animate, scroll } from "motion"

const header = document.getElementById("js-site-header")
const title = header.querySelector(".site-header__title")
const prefersReduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches

const setHeaderState = (progress) => {
  const clamped = Math.min(Math.max(progress, 0), 1)

  const height = 88 - 20 * clamped
  const titleSize = 24 - 4 * clamped
  const shadowOpacity = 0.06 + 0.1 * clamped

  header.style.height = `${height}px`
  title.style.fontSize = `${titleSize}px`
  header.style.boxShadow = `0 10px 24px rgba(15, 23, 42, ${shadowOpacity})`
}

if (prefersReduced) {
  // 動きを減らしたい環境では、見た目変化だけ最小限にする
  scroll((progress) => {
    setHeaderState(progress * 0.35)
  })
} else {
  scroll((progress) => {
    setHeaderState(progress)
  })

  animate(
    header,
    { opacity: [0.98, 1] },
    { duration: 0.45, easing: "ease-out" }
  )
}

この形にしておくと、スクロール連動のロジックが setHeaderState() にまとまるので、微調整がしやすくなります。デザイナーと値を詰めるときも、heighttitleSize の係数を見るだけで済みます。

注意点

  • 動かしすぎない
    スクロールに毎フレーム反応させる設計は、派手にしすぎると読みづらさにつながります。まずは「ヘッダーのサイズ」「影」「透明度」くらいから始めるのが安全です。
  • モーション配慮を先に入れる
    prefers-reduced-motion を前提に分岐しておくと、後からアクセシビリティ改修が必要になりにくいです。
  • 役割を分離する
    アニメーション計算関数、DOM参照、イベント起点を分けると保守しやすくなります。今回の例だと setHeaderState() の切り出しがその役目です。

まとめ

Motion は、JavaScriptアニメーションを軽く始めたいときに扱いやすい選択肢です。animate() で基本動作を作り、scroll() で進捗連動を足す流れが素直なので、実務のUI改善にそのまま載せやすいです。

特に、スクロール連動ヘッダーのような「少しだけ気持ちよくしたい」要件で効果が出やすいです。まずは一部分に限定して導入し、必要に応じて展開する進め方が失敗しにくいと思います。

ポイント

  • JavaScriptアニメーションは animate()scroll() を分けると実装が整理しやすい
  • スクロール連動は値計算関数を切り出すと調整と保守が楽になる
  • prefers-reduced-motion を先に入れておくと後工程の修正コストが下がる

参考リンク

read next