Intersection Observer APIを使ったトップに戻るボタン

スクロールすると出てきて、トップに戻ったり少し戻ったりすると消えるやつ。

スクロール監視で作られていたトップに戻るボタンの改修をしていた。Intersection Observer APIを使うと10行に満たないJavaScriptで実装できた。グローバル・ヘッダーが見えなくなったらボタンを出す、という条件でのみだが、実用性はありそう。

HTML

HTMLではbody要素直下の最後にでもbutton要素を置いておく。最後の方に置くのは重なり順のため。位置はCSSで行うので、あまり考える必要はない。イベントの発生までラグがあるかもしれないので、デフォルトでhidden属性は追加しておく。

<body>
  <header class="global-header">
    ...
  </header>

  ...

  <button id="to-top" hidden>↑ Back to Top</button>
</body>

CSS

ボタンをビューポートの右下に配置する。位置を固定するだけ。z-indexプロパティーが必要なら細心の注意を払う。

#to-top {
  bottom: 1rem;
  position: fixed;
  right: 1rem;
}

JavaScript

ボタンを押すとスクロールするコードと、グローバル・ヘッダーが見えなくなったらボタンを表示するコードを書く。スクロールするコードはwindow.scrollTo(0, 0)を使う。

IntersectionObserverオブジェクトを作る時に渡すコールバック関数で、表示の切り替えを行う。引数から監視するグローバル・ヘッダーの位置情報が手に入るので、そのうちの見えるか見えないかだけを真偽値で返してくれるisIntersectingプロパティーを利用する。ボタンのhidden属性へコピーするだけで、グローバル・ヘッダーとボタンのどちらかのみが表示されるようになるはず。

var toTop = document.getElementById("to-top");
toTop.addEventListener("click", function () {
  window.scrollTo(0, 0);
});
(new IntersectionObserver(function (entries) {
  toTop.hidden = entries[0].isIntersecting;
})).observe(document.querySelector(".global-header"));

位置を計算して、見えなくなったかどうかを判定する必要がなくなると、これだけ簡単な実装になる。ボタンにアイコンを使いたい! とか、ボタンを徐々に表示したい! とか、スクロールをアニメーションしたい! とか、グローバル・ヘッダーが見えなくなってから半ページくらいスクロールしたらボタンを表示したい! とか、要件が増えると、ちょっとややこしくなる。

そういった要件も、様々な標準技術が実装されている現在なら、それぞれに最適な技術の選定さえできれば、基本を変えることなく追加できる。ボタンならインラインSVGの採用、表示のアニメーションならtransitionプロパティーの利用、スクロールのアニメーションならrequestAnimationFrame()での制御、スクロール判定のあそびならrootMarginthresholdの調節、それぞれで達成できるだろう。


コード・ハイライトを復活させた。highlight.jsを使って、HTMLファイルを作成する時にハイライトしている(RSSやデータでは生のまま)。カラー・スキームは、ダーク・モードで切り替えずに済むように、Solarizedのアクセント色を流用した。もうちょっと抑え気味でも良さそう。