Anton Korzunov

@antonkorzunov

How to train a your Scroll

So, JFYI, you just scrolled a page down to this place. You were able to do it. The problem is – sometimes you should not. And the question you better ask:

Why???!!!! Why I shall not scroll?!

I glad you asked, and here are 2 different answers, I would provide.

First one – because you shall not. You just opened modal, light box, focused task, or something else, which gonna to grab all your attention. You might not even see the page itself anymore, but being able to scroll no more accessible or visible body thought the modal.

Second one – scroll is a parasite. Your ability to scroll everything around is a side effect. The default browser behaviour, you could not agree with.

And you might wish to disable it.

As result, just after creating a modal, you will google about “scroll locks”, and “focus locks”.

Focus locks are used to not let html focus escape some area, which is crucial for WAI-ARIA modals. Focus lock is way more important that scroll, and had to be implemented first. Here is another article for you to read:

But! Good modals also has a requirement to make rest of the page to be inert to user input. There is even a special HTML attribute – inert – which could disable any user interactions with the node. But it is not ready yet. And too powerful.

Usually focus lock plus some “shade”(lightbox) around a modal is far more acceptable solution.

Scroll locks are used to mitigate the last thing – you shall not being able to scroll though modal, and thought shade.

How to lock a scroll?

First of all, let me point you to an article with quite deep explanation for any scroll related moments, you might face. The article you are reading now is a some sort of continuation of “Martian solution”, as I gonna to name it further.

You might not read this article right now, but please have a look on animations inside it to jump into the common “scroll” problems.

In short — there are 2 common ways to “lock scroll”:

  1. Disable scroll bars. Obliviously! Just set overflow:hidden on the body and that’s done!

Work pretty well for OSX of Mobile users, as long there is no fixed scroll bars in their browsers, but will ruin Windows users expirence, as long page will change it width just after fixed scroll bars will disapear.

Martians: What 15px change in width could change A LOT!
  1. 5. (Martian solution) You might just hide scroll bars, spreading OS X experience to the windows users. But this is not always acceptable.
.container::-webkit-scrollbar {   display: none; }

.container { -ms-overflow-style: none; }

2. Or you could disable the scroll event itself. Bingo?!

Yeah, huge win, but scroll event is “unpreventable”.

To prevent scroll you have to prevent the origin of a scroll – wheel or touch event.

I hope you heard that doing it, event attach handlers to document to listen for scroll(group) event, is not a good idea from scrolling performance prospective. It will make scroll slooooow. To be more concrete — not as fast as it could be.
That is not the case,
as long there is no scroll, if you “lock” the scroll.

The only question is “how to prevent scroll on body, but not on modal view”. Or even better to rephrase it – “how to prevent scroll from happening outside of modal node”. Or even better to rephrase it – “how to block any events outside the target node”.

We need reverse-inert.

And that is not a problem actually – just attach event listeners to the DOM, and preventDefault/stopPropagation all event you can catch.

  • Prevent mouseDown, and no click will happen — nor button click, nor click on a link. Even text selection will be disabled.
  • Prevent keyDown, and you will unable to enter any text, switch anything, or Tab to next element.
  • Prevent wheel and touch — and you will disable ability to scroll the page.
PS: and you will unable to Tab “back”, and you will not block page scroll in real — only “mouse” way to do it.

Don’t block anything!

But next you might want to block not all those events, but only outside the modal. Again doable — just check that modal.contains(event.target), ie event has an origin inside “allowed” node.

Only line of code, one check, and… it will not work for scroll.

Martian: Scroll chaining visualized
“The scroll” first will scroll node-under-mouse, and then, when you will reach “the bottom” — it will scroll anything else scrollable — parent node, or page itself.

Martians have provided two possible solutions, second one with CSS, just to disable “overscroll” behavior, but the first one was made in JavaScript!

function handleOverscroll(event) {  
const delta = -event.deltaY;
const targetScroll = elem.offsetHeight - delta;
const allowedScroll = elem.scrollHeight - elem.scrollTop;
 if (targetScroll > allowedScroll) {  
// you are scrolling too far
event.preventDefault();
}
}

Just detect “overscroll” and prevent it. This is how good “scroll-locks”, like react-scroll-captor, works. But to be the best, that logic should be contain a few more lines, to accumulate all the “delta” allowed to be applied. Cos you might have scrollable inside a scrollable, and that scrollable could be hidden inside a Lock.

The solution

React-locky, or, to be short 🔒-y, — is the solution I could propose for you, the solution to lock the scroll, or to disable any user interactions outside the allowed space. That reverse-inert we are looking for.

1 — to cast inert for everything outside

2 — to just “scroll lock” some node

But better is to touch it. First double check that you can scroll everything, and scroll everything. And then — activate Locky, to test how it “should be”.

Locky, when enabled, will not allow you to click on the button outside, or scroll the page. It will enable the behavior you customer deserves.

You have trained your Scroll well. Live in a peace now. Together. He is a friend, not a foe.

Try it by yourself. Give your site a better UX. Make your customers happier.

This is literally 2kb of code, and one component to wrap you modal. Easy Win.

React-Locky would block only events, but not hide body scroll bar (which exists only on Windows). To remove any ability to scroll at all I would recommend:

  • React-scroll-locky — “locky” based scroll remover. Could isolate your components, which may be good.. or bad..
  • React-remove-scroll — “react-compatible” scroll remover, friendly with portals, which handles only scroll events. Small and safe to use
  • React-Focus-On — complete solution for modals. Just everything in one place
Yes — I’ve invested some time in scroll locking :)

More by Anton Korzunov

Topics of interest

More Related Stories