Lenisで慣性スクロールの導入方法

はじめに

スクロール演出を入れたいとき、つい派手なアニメーションから考えがちですが、実際には「移動そのものが気持ちいいか」のほうが体験に効くことがあります。ホイール操作の角ばった感じや、要素が多いページでのぎこちなさが減るだけでも、ページ全体の印象はかなり変わります。

Lenis は、スムーズスクロールのための軽量なライブラリです。公式 README では、軽量で堅牢、パフォーマンスを意識した smooth scroll library と説明されています。

導入時に押さえておきたいのは、Lenis が単に「ゆっくり動かす」だけのライブラリではないことです。現在の公式ドキュメントでは、次のような機能がまとまって提供されています。

  • autoRaf による基本導入
  • scrollTo() によるプログラム制御
  • anchors によるアンカーリンク対応
  • preventdata-lenis-prevent によるネストスクロールの制御
  • lenis/reactlenis/vue などの関連パッケージ

まずはページ全体のスクロール感を整える用途から始めて、必要な画面だけ細かい制御を足していくのが扱いやすいです。

インストール方法

PowerShell
npm i lenis

CSS も一緒に読み込むのが公式の推奨です。Lenis 側で必要なスタイルが入るため、最初から入れておくとハマりどころを減らせます。

JavaScript
import Lenis from "lenis"
import "lenis/dist/lenis.css"

基本の使い方

最初は、ページ全体に Lenis を適用して、ナビゲーションから各セクションへ気持ちよく移動する基本形です。autoRaf: true を使えば、自前で requestAnimationFrame を回さなくても動かせます。

HTML

HTML
<header class="page-nav">
  <a href="#intro">Intro</a>
  <a href="#feature">Feature</a>
  <a href="#contact">Contact</a>
</header>

<main>
  <section id="intro" class="panel">Intro</section>
  <section id="feature" class="panel">Feature</section>
  <section id="contact" class="panel">Contact</section>
</main>

CSS

CSS
.page-nav {
  position: sticky;
  top: 0;
  display: flex;
  gap: 16px;
  padding: 16px;
  background: rgb(255 255 255 / 88%);
  backdrop-filter: blur(8px);
}

.panel {
  min-height: 100vh;
  padding: 40px 24px;
}

JavaScript

JavaScript
import Lenis from "lenis"
import "lenis/dist/lenis.css"

const lenis = new Lenis({
  autoRaf: true,
  anchors: true
})

lenis.on("scroll", ({ scroll, progress, velocity }) => {
  console.log({ scroll, progress, velocity })
})

この構成だけでも、アンカー移動が自然になり、スクロール量や進捗もイベントから受け取れます。まずは autoRafanchors だけで体感を見てから、細かい調整に進むのがおすすめです。

また、JavaScript から scrollTo() を呼べるので、目次UI、ヒーロー下のCTA、ステップ型レイアウトのナビゲーションとも組み合わせやすいです。素の scrollIntoView() より offset や duration をまとめて管理しやすいので、固定ヘッダーがある画面では特に差が出ます。

応用コード

次は、固定ヘッダーぶんの offset を考慮しつつ、ボタンで任意のセクションへ移動し、モーダル内部だけはネイティブスクロールを残す例です。実務ではこのあたりまで押さえておくと、導入後の困りごとがかなり減ります。

HTML

HTML
<header class="site-header">
  <button type="button" data-scroll-target="#plan">料金を見る</button>
  <button type="button" data-scroll-target="#faq">FAQへ移動</button>
</header>

<main>
  <section id="hero" class="section">Hero</section>
  <section id="plan" class="section">Plan</section>
  <section id="faq" class="section">FAQ</section>
</main>

<div class="modal" data-lenis-prevent>
  <div class="modal__body">
    <p>この中はネイティブスクロールのままにしたい領域です。</p>
  </div>
</div>

CSS

CSS
.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  gap: 12px;
  padding: 12px 16px;
  background: rgb(255 255 255 / 88%);
  backdrop-filter: blur(8px);
}

.section {
  min-height: 100vh;
  padding: 40px 24px;
}

.modal {
  position: fixed;
  right: 24px;
  bottom: 24px;
  width: min(360px, calc(100vw - 32px));
  max-height: 240px;
  overflow: auto;
  padding: 16px;
  border: 1px solid #d7dde6;
  border-radius: 16px;
  background: #fff;
  box-shadow: 0 16px 40px rgb(15 23 42 / 12%);
}

JavaScript

JavaScript
import Lenis from "lenis"
import "lenis/dist/lenis.css"

const header = document.querySelector(".site-header")
const headerOffset = header?.offsetHeight ?? 0

const lenis = new Lenis({
  autoRaf: true,
  anchors: {
    offset: headerOffset + 16
  },
  prevent: (node) => node?.closest?.(".modal") != null
})

document.querySelectorAll("[data-scroll-target]").forEach((button) => {
  button.addEventListener("click", () => {
    const target = button.getAttribute("data-scroll-target")
    if (!target) return

    lenis.scrollTo(target, {
      offset: -(headerOffset + 16),
      duration: 1.1
    })
  })
})

ポイントは、アンカーと scrollTo() の両方で offset の考え方を揃えることです。固定ヘッダーがあるのにこの値がバラバラだと、画面によって止まる位置がずれて使いづらくなります。モーダルやドロワーの中は data-lenis-preventprevent でネイティブスクロールを残しておくと、操作感が安定します。

注意点

Lenis を入れると、アンカーリンクはそのままだと期待どおりに動かないことがあります。公式ドキュメントでも、アンカーを使う場合は anchors: true もしくはオプション付きで有効化する前提が案内されています。

また、ネストしたスクロール領域を雑に放置すると、モーダルやサイドバーの中で意図しない挙動が出やすいです。公式では allowNestedScroll: true もありますが、パフォーマンス上の注意があるため、まずは data-lenis-preventprevent を優先するほうが安全です。

そのほか、公式 README では次の制約も案内されています。

  • CSS の scroll-snap はそのままでは非対応で、必要なら lenis/snap を使う
  • iframe 上ではホイールイベントが届かず、スムーズスクロールが効かないことがある
  • syncTouch は iOS 16 未満で不安定になることがある

スクロールが不自然に見えたら、まずは推奨CSSを読み込んでいるか、autoRaf: truelenis.raf() のどちらかが正しく動いているかを確認すると切り分けしやすいです。

まとめ

Lenis は、スクロール演出を盛るための道具というより、ページ全体の移動体験を整えるための土台として優秀です。autoRaf で気軽に始められ、必要になったら scrollTo()、アンカー、ネストスクロール制御まで同じライブラリで広げていけます。

長いページほど効果が見えやすいので、まずは特集ページや記事ページのような読み物系の画面で試すのがおすすめです。見た目を大きく変えなくても、触ったときの印象はかなり良くなります。

ポイント

  • Lenis は「派手な演出」より「スクロール体験の土台作り」に向いている
  • 最初は autoRaf: true と推奨CSSだけでも導入しやすい
  • 固定ヘッダーがある画面では anchorsscrollTo() の offset を揃えると扱いやすい
  • モーダルやサイドバーの中は data-lenis-preventprevent でネイティブスクロールを残す
  • scroll-snap や iframe まわりは制約を先に把握しておくと困りにくい

参考リンク

read next