React Scroll Hook with Shadows

Marius Ibsen
Dfind Consulting
Published in
5 min readMay 6, 2022

--

Photo by: https://unsplash.com/@asci_en

Have you ever thought of spicing up your scrollable content with nice-looking shadows? Yes, all browsers have the scroll bar, but we can make our content shine even better and, at the same time, improve usability. I think shadows add a smooth and excellent feel to our content and help by showing the users that “there is more content down here” or “there is more content up here”.

TLDR: You will find the final source code at the bottom of the article

The Scroll Hook

Recently, I’ve been working on a hook that adds inner shadows when scrolling an element with overflowing content. It is not very complicated, and it’s fun to make. We’ll break down the code throughout the post, look into how it works, and finally use it in our code.

Handling scroll events

First, let’s go through the roll logic part of the hook. It’s not complicated, and I did not have to create three separate states for each event. But it is done for simplicity instead of holding an object with all the values.

onScroll is a React property that we can get on any scrollable HTML element for listening on scroll events, such as a div. It takes a function and returns an event of the target element. In native JavaScript, this would be somewhat equivalent to:

element.addEventListener(‘scroll’, (event) => /* do something*/)

The onScrollHandler-method (exported from the hook) we can pass as the value to the onScroll-property of our element. When scrolling the element, the onScroll-property fires and the onScrollHandler-method triggers. Then we can pick the properties scrollTop, scrollHeight and clientHeight from the event target and set them as values for their respective states.

The onScrollHandler-method (exported from the hook) we can pass as the value to the onScroll-property of our element. When scrolling the element, the onScroll-property fires scroll events, and the onScrollHandler-method receives these events. We can then pick the properties scrollTop, scrollHeight and clientHeight from the event target and set them as values for their respective states.

Illustration of scrollHeight and clientHeight on an element with overflowing children elements
  • clientHeight (visible content) is the inner height of an element, including padding. We can calculate it by CSS height + CSS padding. If a horizontal scrollbar is present, the inner height includes the height of the scrollbar.
  • scrollHeight (visible + hidden content) is the height of an element’s visible content, including hidden content not visible on the screen for an overflowing element.
  • scrollTop is the number of pixels scrolled vertically on the content of an element. Measuring the value takes the distance from the top of an element to the topmost of visible content.

(Read more about scrollTop, scrollHeight and clientHeight on MDN)

Calculate shadow position

We have to do some calculations to determine if and when we should show shadows on the top, bottom, or both. We want to show shadows concurrently when we’re neither at the top of the scroll nor the bottom.

  • isBottom is true when the inner height of the visible element equals the total height of the visible and hidden content minus (-) pixels scrolled from the top of the element.
  • isTop is true when we’ve not scrolled yet, or scrollTop equals 0 pixels.
  • isBetween is true when we’ve scrolled more than 0 pixels from the top, and the inner height of the visible element is less (<) than the visible plus (+) hidden content height minus pixels scrolled from the top.

We can show this by example: we could have an element that has 400 pixels inner height [clientHeight], that is scrolled 150 pixels from the top [scrollTop]; we should then get that isBetween is true:

Determine shadow type based on the calculated position

Our getBoxShadow()-function handles the logic for determining and returning the correct CSS box-shadow value as a string. This value we want to set to the target element reactive as we scroll. We’re here using the variables — isBottom, isTop and isBetween —from earlier to determine which shadow to use:

We want to add shadow to the edge with overflowing content because it makes sense to the eye watching it. So, if we are at the top of the element, we show shadow at the bottom. If we are between (as we calculated in the previous code example), we want to show both top and bottom shadows, or else if we are at the bottom of the element, we want to show the top shadow.

Illustration of inner box shadow top and bottom on an element with overflowing children elements

Using the Scroll Hook

Since this is a React hook, we need to create a component that can use it, and it should be straightforward since as there is to do is connect the props to the target element.

In the code block below, we have a component called App. We import the useScrollWithShadow-hook, destruct the onScrollHandler()-method and the boxShadow variable. We pass onScrollHandler() as the value of the div’ onScroll property and set boxShadow as an inline style.

Without some content that overflows the element, this would not look or work very well. All it is left is to add some content to the div and maybe some static height so we can get an overflow. We could also add CSS transitions for even more smoothening. But from here on, I will leave the rest to your imagination.

Look at the final result for the styled solution with content and source code on Codesandbox.

Final result

Here is the final result. Click “toggle shadows” to switch between with and without shadows (source code on Codesandbox):

https://codesandbox.io/s/upbeat-sun-zgyyo5

Thanks for reading! (feel free to clap and share if you liked this article)

Code foh shizzle

--

--