スムーススクロール時の遷移先を、cssだけで意図した位置に設定する方法[scroll-margin]

スムーススクロール時の遷移先を、意図した位置にする方法[scroll-margin]
ページ内で、スムーススクロールを実装した時。
たとえばページ上部にあるリンクを押すと、ページ内の特定のidを持つセクションに遷移する場合など。

その際に、ページ上部におおいかぶさる(追従してくる)ヘッダなどがあった場合。

そんな場合に何の対策もしていないと、遷移した時に
その追従してくるヘッダ分、要素が隠れて、見切れてしまうことになります。

この様な場合、レガシーな対応方法としては、アンカーの場所をあえて少し上に用意したりするケースが
回避策としてとられていたかと思います。

今回の記事では、それを1行のcssのみで調整できるプロパティ、
scroll-margin, scroll-padding について書いていきます。

こちらの scroll-margin, scroll-padding 、昨年2022年の中頃までは
ご存知の通り、「とある青くて丸いアイコンのブラウザ」のために実務での投入は二の足を踏む存在だったのですが

現在は、実務での使用が問題なくできるようになっています。

それでは早速、scroll-margin, scroll-padding の使い方について、次のセクションより書いていきたいと思います。


htmlとcssの基本構造

例としては、単純明快です。
サンプルとして、ページの上の方に、テキストリンクが並んでいるとしましょう。
そしてそれのいずれかをクリックあるいはタップすると、指定したページ内リンクに遷移すると。

そんな至ってシンプルな構造があったとします。
1〜12行目までがヘッダ。14行目〜末尾までが、ヘッダ内のメニューから移動する遷移先です。

<header class="header-container">
  <h1>なんかすごいロゴ</h1>
  <nav>
    <ul class="menu-list">
      <li class="list-item"><a href="#firstSection">セクション1</a></li>
      <li class="list-item"><a href="#secondSection">セクション2</a></li>
      <li class="list-item"><a href="#thirdSection">セクション3</a></li>
      <li class="list-item"><a href="#fourthSection">セクション4</a></li>
      <li class="list-item"><a href="#fifthSection">セクション5</a></li>
    </ul>
  </nav>
</header>

<div class="main-container">
  <div id="firstSection" class="target-section">要素1</div>
  <div id="secondSection" class="target-section">要素2</div>
  <div id="thirdSection" class="target-section">要素3</div>
  <div id="fourthSection" class="target-section">要素4</div>
  <div id="fifthSection" class="target-section">要素5</div>   
</div>

上記の基本的なcssは次の様な感じです。サンプルなので、特段これといった装飾はしておらず、必要最低限のcssです。

html {
  scroll-behavior: smooth;
}
body {
  height: 10000px;
}
.header-container {
  align-items: center;
  background: #fff;
  box-sizing: border-box;
  display: flex;
  height: 80px;
  justify-content: space-between;
  left: 0;
  padding: .5rem 1rem;
  position: static;
  top: -80px;
  transition: top .5s;
  width: 100%;
  z-index: 100;
}
.header-container.is-active {
  box-shadow: 0 1px 4px rgba(0, 0, 0, .25);
  left: 0;
  position: fixed;
  top: 0;
}
.header-menu-list {
  display: flex;
  gap: 0 1rem;
  list-style-type: none;
}
.main-container {
  height: 100%;
}
.main-container.is-active {
  height: 100%;
  padding: 80px 0 0;
}
.target-section {
  margin: 10rem auto 0;
  width: 1200px;
}
.target-section:first-of-type {
  margin: 20rem auto 0;
}

そして、ページを少しスクロールすると、次のJavaScriptによってヘッダが追従してくるものとします。
スクロール値が200pxを超えたらヘッダがスライドインします。

<script>
const headerContainer = document.querySelector('.header-container');
const mainContainer = document.querySelector('.main-container');
const triggerPosition = 200;
let nowPosition;

window.addEventListener('scroll', () => {
  nowPosition = Math.round(window.scrollY);
  if (nowPosition > triggerPosition) {
    headerContainer.classList.add('is-active');
    mainContainer.classList.add('is-active');
  } else {
    headerContainer.classList.remove('is-active');
    mainContainer.classList.remove('is-active');
  }
});
</script>

↑これはもちろん、7行目に記述してあるscrollイベントを使う方法ではなく、 Intersection Observer などを使うともっとスマートに管理できるのですが、サンプルということで簡易的な仕組みで追従させています。

このままだと追従ヘッダの高さの分だけ、要素が隠れてしまう

さて、このときに、このままだと各セクションに移動した際、追従ヘッダに遷移先の要素が隠れてしまうわけですね。

そこで登場するのが、scroll-margin, scroll-padding (今回の例では scroll-margin)というわけです。 どう記述するかというと、先ほどのcssに、次の様に追記します。
追記箇所は40行目の .target-section の部分。
marginが定義されている次の行に、
scroll-margin-top: 100px;
とします。追従してくるヘッダの高さ(80px)よりも、すこし少し大きめに設定します。

.target-section {
  margin: 10rem auto 0;
  scroll-margin-top: 100px;
  width: 1200px;
}

このように、遷移先の要素に対して scroll-margin を設定しておくことで、
ページ内リンクの遷移先が隠れてしまう事象を、cssだけで簡単に解決することが可能というわけです!

留意点など。使えるのはcssで定義されたページ内リンクの場合。

ここまでのセクションで、上記の様に scroll-margin-top を使用することで、ページ内リンクの遷移先で、上にかぶさる何らかの要素があった時にも、遷移先の位置調整をcss1行だけで簡単に調整できることがわかったかと思います。

ただし、1つ覚えておいて頂きたいのは、scroll-margin, scroll-padding が有効なのは
JavaScriptによるプログラム制御でのページ内リンク「ではない」場合のみ に有効ということです。

今回のサンプルでは、cssの最初の方で

html {
  scroll-behavior: smooth;
}
...(中略)...

↑このように、 scroll-behavior: smooth; をつかって
css制御でのスムーススクロールを設定、
スムーススクロール付きのページ内リンクを作っています。

JavaScriptによるアプローチでも、これはよくある例だと
window.scrollTobehavior: smooth を設定する方法でも実装が可能です。
↑このように JavaScript によって実装されているページ内リンクの場合だと、
基本的には scroll-margin, scroll-padding は無効 だと思ってください。

それはそのはずで、JavaScriptで実装されている場合は
遷移先の位置も自在にJavaScript側で設定が可能(というかJavaScript側での制御が必要になる)なので、当然scroll-margin, scroll-padding は無効化されるということです。

JavaScriptによるアプローチで、ページ内リンクを実装する方法についても、
いずれまた記事を書きたいと思ってます。

MDN内の解説など

この記事で登場した、用語などに関するMDN内の詳しい解説は次のとおりとなります。
[scroll-margin]
scroll-margin – CSS: カスケーディングスタイルシート | MDN

今回は取り扱いませんでしたが、scroll-margin と同じ手法で使うことができる scroll-padding に関するリンクは次のとおり。
[scroll-padding]
scroll-padding – CSS: カスケーディングスタイルシート | MDN
こちらの scroll-padding は、主に親要素に対して効かせる使い方が中心になるかと思います。
margin は要素の外側の余白、 padding は要素の内側に余白を設けたい場合、
という使い分けの観点から考えると、確かにそうなるよね、というところです。

↑そしてこちらの「MDN」ですが、何かWeb技術で知りたい事がある時、ひとまず読んでおけば基本的な知見が得られるとても良質なリファレンスサイトです(Mozilla ≒ Webブラウザの挙動そのものの仕様を決めてる人達、が運営してるので当然と言えば当然ですが)。ぜひ一読することをおすすめします。

この記事のまとめ

今回は、Webサイトでページ内リンクをつくった時に
たとえば追従ヘッダがあるサイトなどで、追従ヘッダの高さによって遷移先の要素が隠れてしまう様な場合。
それを1行のcssのみで調整できるプロパティ、
scroll-margin, scroll-padding について記事を書きました。

記事の中段でも書きましたが、
この scroll-margin, scroll-padding によるアプローチ方法は、JavaScript制御でのページ内リンクをつくった際には無効化される方法ではありますが、そうではない場合には、本当にcssたった1行で調整が効くため
結構使い所がある手法じゃないかなと感じています(実際に、私も実務でもよく使います)。

この記事が皆さんのより良いWeb制作体験につながれば、嬉しく思います。

※この記事は内容の品質向上を目的に、随時更新を行う場合があります。

この記事をシェアする: