カランのブログ

ソフトウェアエンジニア / 台湾人 / 福岡生活

今のモード ライト

前言

この記事は 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のheight)を表し、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で定義されており、ユーザーがコンテンツをスクロールする際に効率的にコンテンツを消費できるようにするために、overflow-anchorの仕組みが定義されています。

詳細なアルゴリズムの定義はこちらにあります。

  1. スクロールコンテンツSがスクロールするたびに、none以外の属性を持つ子要素からアンカーノードを選択します。
  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で実現できるという点は興味深い方法です。

作者

Kalan 頭像照片,在淡水拍攝,淺藍背景

愷開 | Kalan

Kalan です。台湾出身で、2019年に日本へ転職し、福岡に住んでいます。フロントエンド開発に精通しているだけでなく、IoT、アプリ開発、バックエンド、電子工作などの分野にも挑戦しています。 最近、エレキギターを始めました。ブログを通じて、より多くの人と交流できればと思っています。気軽に絡んでください