Yet another scroll shadows implementation
Can this content be scrolled or not? Try and you can find out. It's not always obvious that some content can be scrolled without trying to scroll first. The article, "perfectly-cropped", illustrates this concern. Another example is with forms, if not all mandatory fields can be seen without scrolling, the user will have to submit the form and read the error message to figure out what was missing. In smallcase apps, we let users submit forms only after passing the validations.
A common solution is to use shadows, if there is content left to scroll, show a shadow. When there is no content left to scroll, remove the shadow. It's a solved problem. Some implementations are:
- https://lea.verou.me/blog/2012/04/background-attachment-local/ (opens in a new tab)
- https://css-tricks.com/books/greatest-css-tricks/scroll-shadows/ (opens in a new tab)
- https://css-tricks.com/scroll-shadows-with-javascript/ (opens in a new tab)
After reading all these solutions, I created the implementation that suited my needs best.
CSS-only solution
The CSS-only solution makes use of the background-attachment (opens in a new tab) property. The idea is explained in css-tricks (opens in a new tab). The shadows are backgrounds, which means they show behind any content in the scrollable area. We can't use this solution when certain elements don't have transparent backgrounds as content. The shadow will appear behind images, divs with their background, form elements etc. as shown in the codepen example.
With javascript
We can utilize the scroll event or IntersectionObserver
. Using the scroll event lets us know how much content is left and use that information to vary the intensity as seen in this pen (opens in a new tab). Too much code listening to scroll events can degrade performance. Take care not to overdo it and to implement debounce. My requirement didn't need varying intensity so I went ahead with the IntersectionObserver
. The gradient backgrounds won't help as the shadow should come over the content. A box-shadow
also doesn't cover elements that are expected to have pointer-events like <input />
. However, we can use gradient backgrounds on pseudo-elements and layer them on top of the content. The pseudo-elements should not prevent content from receiving pointer events either. The implementation can be understood from the cushion app blog (opens in a new tab).
- This works by adding two divs inside the scrollable container, one at the start of the content and one at the end. Let's call the top edge and bottom edge respectively.
- An
IntersectionObserver
is created with the container as root. The observer then observes the top edge and bottom edge. - When any of the edges interact with the container, it's evident that the edge of content has been scrolled and the shadow can be removed from that side of the container.
- The shadows are created from pseudo-elements with position: sticky and a linear gradient for the background. When an edge intercepts, we can choose to hide the pseudo element on that side.
- In the above example, the pseudo-element visibility is controlled by changing the opacity property. A transition is also defined on the property to be nice.