オーバーフローアンカーによるボトムコンポーネントへのピンの実装

作成者:カランカラン
💡

質問やフィードバックがありましたら、フォームからお願いします

本文は台湾華語で、ChatGPT で翻訳している記事なので、不確かな部分や間違いがあるかもしれません。ご了承ください

前書き

この記事は、Implementing a pin-to-bottom scrolling element with only CSSを読んだ後、JavaScriptを使った方法を整理したものです。

最近、ウェブページでは新しいコンテンツを追加するたびに、スクロール位置を一番下に調整するケースが増えてきています。例えば、Twitchのコメントセクション:

またはYouTubeのコメントセクション:

日本語にはこのような行動を表す特定の用語はまだ存在しないようなので、とりあえず「pin to bottom」と呼ぶことにします。

[JavaScript] 実装方法は?

function append(comment) {
  const comments = document.querySelector(".comments")
  comments.appendChild(comment)
  comments.scrollTop = comments.scrollHeight
}

まず、コンテナの高さを固定し、overflow-y: autoを追加します。これにより、内容がコンテナの高さを超えた場合、スクロール可能になります。

新しい要素を追加するたびに、comments.scrollTop = comments.scrollHeightを呼び出すことで、scrollTopが常にscrollHeightの高さと等しくなるようにします。

考慮すべき問題

この実装は非常にシンプルですが、ユーザーが他のコンテンツを見ようとしてスクロールした際に、新しいコメントが現れると強制的に一番下にスクロールされてしまうという問題が発生します。

この問題を解決するために、まずコンテナの現在のscrollTopを確認します。もしscrollHeightとの間に一定の差があれば、ユーザーのスクロールを妨げないようにします。

if (
  comments.offsetHeight < comments.scrollHeight &&
  comments.scrollTop + comments.offsetHeight + 150 > comments.scrollHeight
) {
  comments.scrollTop = comments.scrollHeight - comments.offsetHeight
}

ここでのoffsetHeightはコンテナの高さ(CSSの高さ)を表し、scrollHeightはコンテナのスクロールの総高さ、scrollTopは現在のスクロール位置です。

  1. もしまだoffsetHeightを超えていない場合は、下にスクロールする動作を定義する必要はありません。
  2. ユーザーが150px以上スクロールした場合は、自動的にユーザーをスクロールさせません。

さらに、ユーザーを自動的にスクロールさせない場合、通常はボタンやインジケーターを追加して、ユーザーが最新のコメントに戻れるようにします。

次に、コメントリストが非常に長い場合、パフォーマンスの問題が発生する可能性があります。その対策はいくつかあります:

  1. 最新の500件のコメントのみを保持する(数は実際のニーズに応じて調整)。
  2. windowingのような手法で最適化する(ただし、高さが不定な場合に適用できるかは不明です)。

overflow-anchor

ここで紹介するのは、overflow-anchorを使用して類似の効果を達成する方法です。このプロパティは、Implementing a pin-to-bottom scrolling element with only CSSの記事で見つけたもので、overflow-anchorを使って上記の効果を実現しています。

このプロパティはCSS Scroll Anchoring Moduleに定義されており、効率的にユーザーがコンテンツを消費できるように設計されています。この草案では、リサイズやスクロールが発生した際に、必要なコンテンツを自動的にアンカーポイントとして検出します。

詳細なアルゴリズム定義はこちらです。

  1. スクロールコンテンツSが毎回移動する際、プロパティがnoneでない子要素の中から1つのアンカーノードを選択します。
  2. NがスクロールコンテンツS内にあり、かつ見えない場合は、操作を実行しません(原文ではexcluded subtreeおよびfully clippedと記載されていますが、簡単に言えばスクロールボックス内で見えない状態です)。何も行いません。
  3. NがスクロールコンテンツS内に可視の場合は、Nをアンカーポイントとして選択します。
  4. NがスクロールコンテンツS内で半可視の場合、子要素に対して上記のアルゴリズムを繰り返し適用してアンカーポイントを見つけます。

実際にはこの草案には多くの動作が定義されていますので、興味がある方はぜひご確認ください。ただ、何ができるのかを大まかに理解するだけで大丈夫です。

まず、コンテナ内にアンカーポイントを追加します:

<ul class="comments">
  <li class="anchor"></li>
</ul>

ここでのanchorにはCSSプロパティoverflow-anchor: autoを定義して、ブラウザにそれをアンカーポイントとして認識させます。

.comments {
  & > .comment {
    overflow-anchor: none; // アンカーポイントとして扱わない
  }

  .anchor {
    overflow-anchor: auto; // アンカーポイントとして扱う
  }
}

次に、コメント部分をinsertBeforeを使用して、アンカーの前に挿入するように変更します。

実測結果

左側は一般的な方法での実装、右側はoverflow-anchorを使った実装です。ただし、アンカーポイントの動作が正しく機能するためには、少しスクロールが必要なことに気付きました。

ちなみに、内部のテキストは以前に伊坂幸太郎の「ガソリン生活」を読んだ後に書いた記事からの抜粋で、便宜上いくつかの段落をコピー&ペーストしました。

現在のブラウザのサポート状況は、Firefox 66およびChrome 56で、まだあまり普及していません。しかし、今後は類似の機能を実装するための新しい方法が登場するかもしれません。欠点は、追加のアンカーポイントが必要であることですが、純粋なCSSで実現できるのは非常に興味深い方法です。

この記事が役に立ったと思ったら、下のリンクからコーヒーを奢ってくれると嬉しいです ☕ 私の普通の一日が輝かしいものになります ✨

Buy me a coffee