はじめに
スクロール連動の演出を入れたいとき、CSSだけで頑張ると調整が大変になりがちです。特に「カードごとに少しだけ動きや透明度を変える」ようなUIは、実装が増えるほど管理コストが上がります。
そんなときに扱いやすいのが Motion です。scroll() の進捗値を使えば、JavaScript側で演出ロジックをまとめて管理できます。必要なら animate() を組み合わせて、初期表示の演出も同じライブラリ内で完結できます。
この記事では、Motion の基本から入り、実務で使いやすい「パララックスカード」の実装までをまとめます。
機能やライブラリの概要
Motion は、React だけでなくバニラ JavaScript でも使えるアニメーションライブラリです。2024年11月に独立プロジェクトとして Motion に統合され、現在は motion.dev でドキュメントが管理されています。
今回使う中心機能は次の2つです。
animate(): 単発のアニメーションを簡潔に書けるscroll(): スクロール進捗 (0〜1) に値を連動できる
スクロール演出を「CSSアニメーションの寄せ集め」にしないで、ひとつのロジックとして整理できるのが実務で強いポイントです。
インストール方法
まずはパッケージを追加します。既存のNode環境があるなら次のどれかでOKです。
npm install motionpnpm add motionyarn add motion読み込みは次の形です。
JavaScript
import { animate, scroll } from "motion"基本の使い方
まずは animate() の最小構成です。カード一覧の初期表示にフェードアップを入れます。
See the Pen Motion scroll() by watanabe (@web-sourcecode) on CodePen.
HTML
<section class="news-list" id="js-news-list">
<article class="news-card">記事カード A</article>
<article class="news-card">記事カード B</article>
<article class="news-card">記事カード C</article>
</section>JavaScript
import { animate } from "motion"
const cards = document.querySelectorAll(".news-card")
animate(
cards,
{ opacity: [0, 1], y: [16, 0] },
{ duration: 0.45, delay: (index) => index * 0.08, easing: "ease-out" }
)この形だけでも、初期表示の読みやすさがかなり上がります。特にカードUIは、同時表示より少しだけ段差をつけたほうが視線誘導しやすいです。
便利な使いどころ
scroll() は次のような実装で効果を出しやすいです。
- 記事カードのパララックス演出
- 固定ヘッダーの縮小や透明度制御
- セクション見出しの進捗インジケーター
- 横スクロール領域の進捗可視化
共通点は「派手な演出」より「読ませるための微調整」です。scroll() はこの粒度のUI改善にちょうど良いです。
応用コード
ここではカードごとに係数を変え、奥行き感のあるパララックスを付けます。prefers-reduced-motion も入れて、動きを減らしたい環境では演出を弱めます。
See the Pen Motion scroll() 2 by watanabe (@web-sourcecode) on CodePen.
CSS
.news-list {
display: grid;
gap: 16px;
}
.news-card {
padding: 20px;
border-radius: 14px;
background: #ffffff;
border: 1px solid #e5e7eb;
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.07);
will-change: transform, opacity;
}JavaScript
import { animate, scroll } from "motion"
const cards = [...document.querySelectorAll(".news-card")]
const prefersReduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches
animate(
cards,
{ opacity: [0, 1], y: [16, 0] },
{ duration: 0.45, delay: (index) => index * 0.08, easing: "ease-out" }
)
scroll((progress) => {
const p = Math.min(Math.max(progress, 0), 1)
cards.forEach((card, index) => {
const intensity = prefersReduced ? 0.2 : 1
const depth = (index + 1) * 8 * intensity
const translateY = (0.5 - p) * depth
const opacity = 1 - Math.abs(0.5 - p) * 0.18 * intensity
card.style.transform = `translateY(${translateY}px)`
card.style.opacity = String(Math.max(0.82, opacity))
})
})この実装だと、スクロール位置の調整は depth と opacity の係数を触るだけで済みます。デザイン調整の会話がしやすく、あとから保守する人にも優しい構成です。
注意点
- 1画面で動く要素を増やしすぎない
パララックスは効かせすぎると可読性が落ちます。まずは主要カードだけに限定するのが安全です。 will-changeの乱用を避ける
有効な場面は多いですが、全要素に付けると逆効果になることがあります。動かす要素だけに絞ります。- 減速設定を最初に入れる
prefers-reduced-motionを最初から入れておくと、アクセシビリティ対応の後戻りが減ります。
まとめ
Motion の scroll() は、JavaScript主導でスクロール演出を整理したいときにとても相性が良いです。CSSだけで分散しがちなロジックを1か所に寄せられるので、実務運用がかなり楽になります。
まずはカードやヘッダーのような小さな領域から導入し、効果が確認できたら他セクションへ広げる進め方がおすすめです。
ポイント
scroll()の進捗値で演出を一元管理すると保守しやすい- カードUIは「少しだけ動かす」設計のほうが読みやすい
prefers-reduced-motionを同時実装すると運用が安定する