Strange scrollIntoView behavior in Chrome

November 15, 2022 at 1:15 PM CST

Set up the carousel

I am making a carousel using progressive enhancement principles. The HTML is simple. Each carousel slide has an id, and CSS is used to arrange the slides horizontally in a scrollable container. The browser knows how to scroll vertically and horizontally when you click an anchor link to view a specific slide, so you don't need to use Javascript to animate anything. Smooth scrolling and snap scrolling are controlled with CSS. The user can navigate through the slides by scrolling horizontally or by clicking the slide links above or below the carousel.

:root {
scroll-behavior: smooth;
.carousel-items {
display: flex;
flex-wrap: nowrap;
gap: 1rem;
overflow-y: auto;
overscroll-behavior-x: contain;
scroll-behavior: smooth;
scroll-snap-type: x mandatory;
scroll-snap-stop: always;
.carousel-item {
min-width: 100%;
scroll-snap-align: center;
padding: 1rem 2rem;

Add functionality with Javascript

The Javascript layer adds additional functionality. It's mainly used control how far the page scrolls vertically before scrolling horizontally. The slide link's click event is intercepted and scrollIntoView is used instead of the browser default functionality. scrollIntoView offers more control over scroll behavior than is possible with pure CSS. The default options are { block: 'start', inline: 'nearest' }, which will scroll vertically until the element is at the top of the viewport, then scroll horizontally to the selected slide. Setting block to nearest will scroll vertically until the entire element is in the viewport, if it isn't already. This is the desired option for the carousel I'm creating. The other possible values are center and end.

el.scrollIntoView({ block: 'nearest', inline: 'nearest' });

The problem

This works great except for one issue in Chrome. If :root has scroll-behavior: smooth;, any clicks after the first one will no longer scroll the carousel horizontally. This means you can no longer change slides after the first click. This happens with or without scrollIntoView. Pure CSS scrolling has the same problem.

But if you scroll the page up or down before clicking again, it will work as expected. It will also work as expected if :root has scroll-behavior set to anything other than smooth.

This only happens in Chrome. Other browsers work as expected. Manually scrolling horizontally through the slides also works as expected.

A solution I don't like

One workaround is to temporarily disable smooth scrolling on :root (or never use it in the first place), but I prefer smooth scrolling and so does the client. This is not the solution I ended up using, but I figured I should mention it. = 'auto';
el.scrollIntoView({ block: 'nearest', inline: 'nearest' }); = 'smooth';

A solution I like

The solution I went with is to use scrollBy to quickly scroll the window up and down by one pixel before attempting scrollIntoView. This happens fast enough to be invisible to the user, and it seems to jostle the viewport enough so that the horizontal scrolling works as expected.

window.scrollBy({ behavior: 'instant', top:  1, left: 0 });
window.scrollBy({ behavior: 'instant', top: -1, left: 0 });
el.scrollIntoView({ block: 'nearest', inline: 'nearest' });


Try it for yourself. If the carousel slides are completely inside the viewport and block mode is set to nearest, everything works great. If part of the slides are are off the bottom of the viewport when you click a link, that first click will be fine, but subsequent clicks won't move to the appropriate slide. If you turn on the "jostle" hack, things work great again.

scrollIntoView demo

We need some content here to give the page more length.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus fermentum hendrerit erat viverra congue. Phasellus ac sapien tristique nunc ullamcorper tempus. Quisque facilisis nibh gravida arcu condimentum, id pharetra massa dignissim. Nunc non augue tellus. Nam volutpat fringilla finibus. Sed nec hendrerit elit. Pellentesque at molestie elit, id ornare ligula.

Nam mi elit, gravida in neque sed, aliquet feugiat sapien. Sed lobortis lorem augue, eu dignissim nulla vulputate quis. Nullam varius vehicula quam. In aliquam hendrerit nibh. Donec luctus vehicula odio eget ullamcorper. Praesent vel mi ut odio mattis cursus eu vitae urna. Pellentesque ac tincidunt purus, non varius tortor. Duis orci neque, dapibus ut fermentum in, hendrerit quis risus. Curabitur in tellus risus. Integer commodo purus consectetur, mollis tortor euismod, feugiat nisi. In massa tellus, feugiat ut posuere ac, congue in arcu. Cras pretium ipsum et purus sagittis ornare.

Morbi a mauris vestibulum purus consectetur accumsan. Nam posuere tincidunt molestie. Mauris auctor aliquam neque et tempor. Proin ornare venenatis dolor, in tempor nibh elementum sed. Sed laoreet, tellus sit amet accumsan tincidunt, sem enim varius quam, quis tristique lacus enim ut turpis. Donec sed leo in diam volutpat sodales. Cras vestibulum varius ipsum sed tempus. Nam non facilisis velit. Integer id nulla in odio viverra sodales. Phasellus non hendrerit lacus, sit amet ultricies augue. Phasellus sit amet mi in nulla molestie vulputate. Mauris sit amet consequat tellus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam consectetur hendrerit ligula at rhoncus. Integer porta risus et felis gravida, eget fringilla ante placerat.

Nullam tortor felis, fermentum eu velit vel, consequat dictum urna. Phasellus nec ultrices leo, gravida gravida tortor. Morbi tortor felis, luctus dignissim lacinia suscipit, faucibus in justo. Praesent blandit ut ex aliquam fermentum. Proin pulvinar urna non tempor sodales. Cras metus urna, bibendum et viverra sit amet, dapibus eget lectus. Phasellus quis magna sagittis, fringilla purus vel, rhoncus nibh.

Nullam vitae tortor id augue tincidunt varius. Cras id eros vel magna vehicula venenatis. Mauris malesuada tristique ipsum, in molestie neque pretium condimentum. Duis sed dolor facilisis, sodales sem a, auctor lacus. Nam gravida mattis augue consequat vehicula. Sed sit amet sagittis nulla, lacinia pharetra dui. Ut elementum sapien vel lorem varius tempor. Donec efficitur pulvinar tellus, non sollicitudin turpis semper et. Fusce aliquam varius mauris egestas interdum. Aenean at enim arcu. Nullam lacinia felis odio, nec rhoncus ligula feugiat ac.

Previous: Nothing before this
Next: Nothing after this