Fade in and out Content through CSS Pseudo Element

Introduction

Recently, I found the reading experience of my online resume a bit off, and in the worst cases, the text was cut off, leading to no indication that scrolling down is possible. You can refer to the CodePen example🔗. This article describes how to achieve a fade-in style for scrollable elements using CSS pseudo elements.

Before and After
Before and After Comparison

Implementation

Method 1: Gradient Mask

To create a fade-in and fade-out effect at the top and bottom of a scrollable element, one method is to add mask-image🔗 directly to the element: CSS Div Fade Scroll Styling - stack overflow🔗. This achieves a true fade effect, but this method relies on JS to dynamically detect and adjust whether to apply the fade-in style and uses more modern syntax.

#content {
overflow-y: auto;
-webkit-mask-image: linear-gradient(to bottom, transparent 0, black var(--top-mask-size, 0), black calc(100% - var(--bottom-mask-size, 0)), transparent 100%);
mask-image: linear-gradient(to bottom, transparent 0, black var(--top-mask-size, 0), black calc(100% - var(--bottom-mask-size, 0)), transparent 100%);
--top-mask-size: 0px;
--bottom-mask-size: 0px;
}
#content.is-top-overflowing {
--top-mask-size: 48px !important;
}
#content.is-bottom-overflowing {
--bottom-mask-size: 48px !important;
}
function setClasses(el) {
const isScrollable = el.scrollHeight > el.clientHeight;
// GUARD: If element is not scrollable, remove all classes
if (!isScrollable) {
el.classList.remove('is-bottom-overflowing', 'is-top-overflowing');
return;
}
// Otherwise, the element is overflowing!
// Now we just need to find out which direction it is overflowing to (can be both).
// One pixel is added to the height to account for non-integer heights.
const isScrolledToBottom = el.scrollHeight < el.clientHeight + el.scrollTop + 1;
const isScrolledToTop = isScrolledToBottom ? false : el.scrollTop === 0;
el.classList.toggle('is-bottom-overflowing', !isScrolledToBottom);
el.classList.toggle('is-top-overflowing', !isScrolledToTop);
}
document.querySelector('#content').addEventListener('scroll', (e) => {
const el = e.currentTarget;
setClasses(el);
});
setClasses(document.querySelector('#content'));

It can be expected that maintenance can be quite troublesome. Experience also tells me that styling issues should ideally be solved through modern CSS, as involving JS often leads to overengineering and more problems (compatibility, performance, etc.).

Method 2: Gradient Pseudo Elements Covering Content

In fact, “detecting whether the content touches the edges” is not necessary. As long as “the element’s padding is greater than the gradient size,” it is easy to create pseudo elements positioned inside the padding, which will naturally overlap when scrolled to the bottom or top.

Gradient Pseudo Element Covering Content
Gradient Pseudo Element (Red Line) Covering Content

<div class="box-wrapper">
<div class="box">
Hello World
</div>
</div>
.box {
padding: var(--box-padding);
width: 300px;
height: 300px;
overflow: auto;
color: black;
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
}
.box-wrapper {
position: relative;
}
.box-wrapper::before,.box-wrapper::after {
content: "";
position: absolute;
left: 0;
width: 100%;
height: var(--box-padding);
z-index: 1;
pointer-events: none;
}
.box-wrapper::before {
top: 0;
background: linear-gradient(to bottom, var(--background), transparent);
}
.box-wrapper::after {
bottom: 0;
background: linear-gradient(to top, var(--background), transparent);
}

Conclusion

By using simple CSS pseudo elements to create purely decorative elements and positioning them using absolute within the padding, we can elegantly avoid implementing it through complex JS. I applied this effect in my online resume🔗 for reference.