React Scroll Hook with Shadows
Published: 2022-05-06 22:56:10

---
title: React Scroll Hook with Shadows
published: true
description: 
tags: 
//cover_image: https://direct_url_to_image.jpg
---

Have you ever thought of spicing up your scrollable areas with nice-looking shadows? Yes, all browsers have the scroll bar, but we can make the scrollable 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".

If you’ve had the same thought (I’ll guess so since you’re already reading this) but have not figured out a solution yet, you should read on.

##The Scroll Hook

Recently, I made this hook that adds inner shadows when scrolling an element that have overflowing content. We’ll break down the code throughout the post, see how it works, and see how we use it in our code.

```
import { useState } from 'react';

export function useScrollWithShadow() {
  const [scrollTop, setScrollTop] = useState(0);
  const [scrollHeight, setScrollHeight] = useState(0);
  const [clientHeight, setClientHeight] = useState(0);

  const onScrollHandler = (event) => {
    setScrollTop(event.target.scrollTop);
    setScrollHeight(event.target.scrollHeight);
    setClientHeight(event.target.clientHeight);
  };

  function getBoxShadow() {
    const isBottom = clientHeight === scrollHeight - scrollTop;
    const isTop = scrollTop === 0;
    const isBetween = scrollTop > 0 && clientHeight < scrollHeight - scrollTop;

    let boxShadow = 'none';
    const top = 'inset 0 8px 5px -5px rgb(200 200 200 / 1)';
    const bottom = 'inset 0 -8px 5px -5px rgb(200 200 200 / 1)';

    if (isTop) {
      boxShadow = bottom;
    } else if (isBetween) {
      boxShadow = `${top}, ${bottom}`;
    } else if (isBottom) {
      boxShadow = top;
    }
    return boxShadow;
  }

  return { boxShadow: getBoxShadow(), onScrollHandler };
}
```

###Handling scroll events

Firstly, let’s go through the scroll-logic part of the useScrollWithShadow-hook.

```
import { useState } from "react";

export function useScrollWithShadow() {
  const [scrollTop, setScrollTop] = useState(0);
  const [scrollHeight, setScrollHeight] = useState(0);
  const [clientHeight, setClientHeight] = useState(0);

  const onScrollHandler = (event) => {
    setScrollTop(event.target.scrollTop);
    setScrollHeight(event.target.scrollHeight);
    setClientHeight(event.target.clientHeight);
  };

  return { onScrollHandler };
}
```

`onScroll` is a React property that we can get on any 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’, () => /* do something*/)`

The `onScrollHandler`-method (also exported from the hook) we can pass as the value to the `onScroll`-property of an element. When scrolling the element, the `onScrollHandler`-method triggers, then we 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qs7tws6tig7jsqqcvjib.png)

* **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.

###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 will have shadows on the _top_ and _bottom_ of the element when we’re not at the _top_ of the scroll or the _bottom_.

```
const isBottom = clientHeight === scrollHeight - scrollTop;
const isTop = scrollTop === 0;
const isBetween = 
     scrollTop > 0 && clientHeight < scrollHeight - scrollTop;
```

* **isBottom**: `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**: `true` when we’ve not scrolled yet, or `scrollTop` equals 0 pixels.
* **isBetween**: `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.

Below is an example of an element that has 400 pixels inner height, and we have scrolled 150 pixels from the top; we should then get that **isBetween** is `true`:

```
// Some element inner height: 400px
// Totalt height of the visible and hidden content: 700px
// Pixels scrolled from top of the element: 150px

const isBottom = 400 === 700 - 150;           // false
const isTop = 150 === 0;                      // false
const isBetween = 150 > 0 && 400 < 700 - 150; // true
```

###Determine shadow type based on the calculated position

Our `getBoxShadow()`-function handles the logic for returning the correct CSS `box-shadow` value as a string. This value we want to set to the target element as we scroll. Here we will use the variables of the calculation to determine when we should use the different shadows:

```
function getBoxShadow() {
    const isBottom = clientHeight === scrollHeight - scrollTop;
    const isTop = scrollTop === 0;
    const isBetween = 
     scrollTop > 0 && clientHeight < scrollHeight - scrollTop;    let boxShadow = "none";
    const top = "inset 0 8px 5px -5px rgb(200 200 200 / 1)";
    const bottom = "inset 0 -8px 5px -5px rgb(200 200 200 / 1)";

    if (isTop) {
      boxShadow = bottom;
    } else if (isBetween) {
      boxShadow = `${top}, ${bottom}`;
    } else if (isBottom) {
      boxShadow = top;
    }
    return boxShadow;
}
```

We want to set the _bottom_ shadow if we are at the top of the element. 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.

##Using the Scroll Hook

To use the hook, we need to create a component that can use it. 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.

```
import { useScrollWithShadow } from "./hooks/useScrollWithShadow";

export function App() {
  const { boxShadow, onScrollHandler } = useScrollWithShadow();

  return (
    <div onScroll={onScrollHandler} style={{ boxShadow }}>
      /* ... Scroll area here ... */
    </div>
  );
}
```

Without some CSS, this would not work very well. See 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)):

{% embed https://codesandbox.io/embed/react-scroll-hook-with-shadows-zgyyo5?fontsize=14&hidenavigation=1&theme=dark&view=preview %}

Thank you for reading! Code for shizzle

(This article was [originally posted on Medium](https://marius-ibsen.medium.com/react-scroll-hook-with-shadows-9ba2d47ae32) )