· 5 min read

Implementing a Pin-to-Bottom Component Using overflow-anchor

This article was auto-translated from Chinese. Some nuances may be lost in translation.

Introduction

This article summarizes what I learned after reading Implementing a pin-to-bottom scrolling element with only CSS, combined with an introduction to implementing it using JavaScript.

Nowadays, it is becoming increasingly common on the web to automatically adjust the scroll position to the bottom whenever new content is added. For example, Twitch chat:

Or YouTube’s live chat:

There doesn’t seem to be a specific Chinese term to describe this behavior, so let’s just refer to it as “pin to bottom.”

[JavaScript] How to Implement It?

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

First, set the container to a fixed height and apply overflow-y: auto. This ensures that content turns into a scrollable view once it exceeds the container’s height.

Whenever a new element is appended, we simply call comments.scrollTop = comments.scrollHeight, ensuring that scrollTop always matches the height of scrollHeight.

Considerations & Edge Cases

While this implementation is straightforward, an issue arises: if a user scrolls up to read earlier content, any incoming new comment will still forcefully scroll them back down to the bottom.

To address this, we can first check the container’s current scrollTop. If it deviates from scrollHeight by a certain threshold, we avoid automatically scrolling for the user.

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

Here, offsetHeight represents the container’s rendered height (CSS height), while scrollHeight represents the total scrollable height of the container, and scrollTop is the current scroll position.

  1. If the content has not yet exceeded offsetHeight, we do not need to trigger any downward scrolling behavior.
  2. If the user has scrolled up by more than 150px, do not automatically scroll down for them.

Additionally, when auto-scrolling is paused, implementations usually display a button or indicator allowing users to quickly jump back down to the latest messages.

Furthermore, if the comment list grows very long, performance issues may occur. A few ways to address this include:

  1. Keeping only the latest 500 messages (adjusting the exact limit based on actual needs).
  2. Optimizing with techniques like windowing. (Although I am not sure how well it handles dynamic, variable item heights.)

overflow-anchor

Today, I want to introduce using overflow-anchor to achieve a similar effect. I came across this property in the article Implementing a pin-to-bottom scrolling element with only CSS, which demonstrates achieving the behavior described above purely through overflow-anchor.

This property is defined in the CSS Scroll Anchoring Module. To help users consume content efficiently, this draft specifies scroll anchoring: when a resize or scroll occurs, it automatically detects which content should act as an anchor.

The detailed algorithm specification can be found here.

  1. Whenever scrolling content S scrolls, an anchor node is chosen from child elements whose property is not none.
  2. If N is within the scrolling content S and is not visible (described in the spec as an excluded subtree and fully clipped—simply put, invisible within the scrollport), nothing is done.
  3. If N is fully visible within scrolling content S, N is selected as the anchor.
  4. If N is partially visible within scrolling content S, the algorithm recurses on its child elements to find an anchor.

The draft actually defines many other behaviors, which you can read through if interested, but understanding the general mechanism is enough for our use case.

First, add an anchor element inside the container:

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

Here, the .anchor element must have the CSS property overflow-anchor: auto set so the browser treats it as the anchor.

.comments {
  & > .comment {
    overflow-anchor: none; // Do not use as anchor
  }

  .anchor {
    overflow-anchor: auto; // Use as anchor
  }
}

Then, modify the comment insertion logic to use insertBefore, ensuring new comments are always inserted before the anchor element.

Live Demo

The left side uses the standard approach, while the right side is implemented using overflow-anchor. However, I noticed that you seem to need to scroll a bit first for the anchoring behavior to function properly.

As a side note, the text inside is from an article I wrote after reading Kotaro Isaka’s novel A Life—I just randomly copied and pasted a few paragraphs for convenience.

Currently, browser support is limited to Firefox 66 and Chrome 56, so it is not yet widely supported. Still, it provides a novel approach for implementing similar functionality down the road. Although having to add an extra anchor element is a downside, achieving this purely through CSS is a rather interesting technique.

Related Posts

Explore Other Topics