Scroll area shadow
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.