Scroll area shadow
| 4 min read
Adding a shadow to the scroll area to indicate overflow area.
function updateShadows() {
const scrollLeft = content.scrollLeft;
const scrollWidth = content.scrollWidth;
const clientWidth = content.clientWidth;
leftShadow.style.opacity = scrollLeft > 0 ? "1" : "0";
rightShadow.style.opacity =
scrollLeft + clientWidth < scrollWidth - 1 ? "1" : "0";
}
Result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
This can be also used in vertical scroll by using scrollTop instead of scrollLeft.
A complete util vanilla javascript function is shown below.
function createScrollShadow(wrapperEl) {
if (!wrapperEl) return;
const contentEl = wrapperEl.querySelector(".scroll-content");
if (!contentEl) {
console.warn("Missing .scroll-content inside wrapper");
return;
}
const leftShadow = document.createElement("div");
const rightShadow = document.createElement("div");
leftShadow.classList.add("scroll-shadow", "left");
rightShadow.classList.add("scroll-shadow", "right");
wrapperEl.appendChild(leftShadow);
wrapperEl.appendChild(rightShadow);
function updateShadows() {
const scrollLeft = contentEl.scrollLeft;
const scrollWidth = contentEl.scrollWidth;
const clientWidth = contentEl.clientWidth;
leftShadow.style.opacity = scrollLeft > 0 ? "1" : "0";
rightShadow.style.opacity =
scrollLeft + clientWidth < scrollWidth - 1 ? "1" : "0";
}
contentEl.addEventListener("scroll", updateShadows);
window.addEventListener("resize", updateShadows);
updateShadows(); // initial check
return {
destroy() {
contentEl.removeEventListener("scroll", updateShadows);
window.removeEventListener("resize", updateShadows);
leftShadow.remove();
rightShadow.remove();
},
update: updateShadows,
};
}
or this headless react hook version which gives you state which scrollable direction is available.
import { useState, useEffect, useCallback, RefObject } from "react";
type ScrollState = {
isScrollableTop: boolean;
isScrollableBottom: boolean;
isScrollableLeft: boolean;
isScrollableRight: boolean;
};
/**
* A hook to monitor the scrollable state of an HTML element,
* provides states to know which direct (top/bottom) are scrollable
*/
export const useScrollableArea = (
element: RefObject<HTMLDivElement>,
): ScrollState => {
const [scrollState, setScrollState] = useState<ScrollState>({
isScrollableTop: false,
isScrollableBottom: false,
isScrollableLeft: false,
isScrollableRight: false,
});
const checkScrollableArea = useCallback(() => {
if (!element.current) return;
const {
scrollTop,
scrollHeight,
clientHeight,
scrollLeft,
scrollWidth,
clientWidth,
} = element.current;
const isScrollableTop = scrollTop > 0;
const isScrollableBottom = scrollTop + clientHeight < scrollHeight;
const isScrollableLeft = scrollLeft > 0;
const isScrollableRight = scrollLeft + clientWidth < scrollWidth;
setScrollState({
isScrollableTop,
isScrollableBottom,
isScrollableLeft,
isScrollableRight,
});
}, [element]);
useEffect(() => {
if (!element.current) return;
// Initial check
checkScrollableArea();
// Attach scroll event listener
element.current.addEventListener("scroll", checkScrollableArea);
// Cleanup
return () => {
element.current?.removeEventListener("scroll", checkScrollableArea);
};
}, [element, checkScrollableArea]);
return scrollState;
};
A shadow helps indicate that the area is scrollable.