Implementing a Nice Scroller

Lona channels are right around the corner! I am currently rounding the last edges and writing the documentation. Channels will be a whole new subsystem, to make writing multi-user views more enjoyable to develop and more performant to run.

For the documentation I needed a nice show-case; A demo to show off how channels work, how easy they are to use, and how instant messages can flow through your application, so my first idea was to implement a multi-user chat.

It took me only half an hour and worked pretty much first-try... and was terrible to use: In pretty much every chat window that you see, you have a list of all previous messages at the top, and a text area for new messages at the bottom. When new messages arrive, they get appended to the bottom of the list of previous messages, and the list stays scrolled down, so you don't have to scroll to every message yourself. The auto-scrolling stops when you scroll yourself, so you can read older messages, and starts again when you scroll to the bottom.

Basically, this is what I want:

But how do you implement this? How do you even call this behavior? I googled around half an evening, trying to find a suitable name for a component like this. Basically, it's the behavior of any terminal, but I wanted a more generic name since I want to use it for chat messages, or something like an event log.

Hmm.. EventLog? EventAutoScroller? AlwaysStayScrolledDownEventLog? Lacking a better name, that describes all behavior I mentioned above, I will call these things Scroller from now on.

Implementation

I was a bit worried about how to implement something like this. I suspected that this behavior is out-of-scope for a web browser, and I would have to emulate it myself, making heavy use of JavaScript. Implementing special scroll behavior is hard, because of the sheer amount of scroll events you can encounter per second. Also, messing with the scroll behavior always has the potential for bad user-experience, when something breaks.

Turns out: The browser has support for behavior like this, and we can tell the browser where to anchor to when scrolling \o/

It's pretty simple. With these three snippets we tell the browser that:

<!-- scroller.html --> <!-- give .scroller a fixed height so .scroller-body can overflow it --> <div class="scroller" style="height: 30em"> <div class="scroller-body"> <!-- put your scrollable content here --> </div> <div class="scroller-anchor"></div> </div> /* scroller.css */ /* make scroller scrollable */ .scroller { overflow-y: scroll; overflow-x: auto; } /* disable all generic anchoring behavior */ .scroller * { overflow-anchor: none; } /* make body slightly to long for its parent so it can be scrolled to the bottom even it is empty */ .scroller .scroller-body { min-height: 101%; } /* enable anchoring for our anchor at the bottom */ .scroller .scroller-anchor { overflow-anchor: auto; height: 1px; } // scroller.js window.addEventListener('load', () => { document.querySelectorAll('.scroller').forEach(scroller => { scroller.scrollTo(0, scroller.scrollHeight); }); });

And that's it! The JavaScript part is optional, but it looks nicer when the scroller is scrolled down initially, in my opinion.

I made a nice and compact Lona component out of it and published it in lona-picocss.