"Raise the curtains" is what I call an effect where the background goes from dark to light on scroll, and the content on top also goes from light to dark while in a sticky position.
Here's an example where I used the effect on a real-life project:
Want to know how it's done? I will take you behind the curtain and show you how to raise it, with nothing but HTML and CSS.
Let's start with the HTML
What we're making is sort of a simplified "raise the curtain" effect like this:
The background and text both change color while scrolling over the element.
I'm keeping things simple for the sake of clarity, but we can stub this out with three elements:
First, we need a container for the curtain, which we'll give a
.curtain class. Then, inside the
.curtain, we have the an
.invert child element that will serve as our "sticky" box. And, finally, we have the content inside this box – a good old-fashioned
<h2> element for this specific example.
Let's set up some CSS variables
There are three values we know we'll need upfront. Let's make CSS variables out of them so it's easy to write them into our styles and easily change them later if we need to.
--minh -- The height of the container
--color1 -- The light color
--color2 -- The dark color
Time to draw the curtain
Next, we can define our
.curtain element using the following techniques:
linear-gradient for the "split" background
min-height for the extra space at the bottom of the container
We use the
::after pseudo-element to add the extra space to the bottom. This way, our "sticky" content will actually stick to the container while scrolling past the
::after element. It's an illusion.
background-image: linear-gradient(to bottom, var(--color2) 50%, var(--color1) 50%);
Making sticky content
Next up, we need to make our content "sticky" in the sense that it sits perfectly inside the container as the background and text swap color values. In fact, we already gave the
.curtain's child element an
.invert class that we can use as the sticky container.
Stay with me for a moment – here's how this is going to play out:
position: sticky and
top define the stickiness and where it sticks.
mix-blend-mode: difference blends the color of the content inside the
<h2> element into the
.curtain's background gradient.
display: flex centers the content for presentation.
min-height defines the height of the container and allows for the extra space at the bottom.
color sets the color of the
Now to put that into CSS code!
There are many things going on here, so let's explain each one of them.
First, we have a sticky position that is self-explanatory and flexbox to help center the content. Nothing new or particularly tricky about this.
The content's height is set using CSS variable and the value is the same height value as the
mix-blend-mode: difference declaration blends our content with the background. The
difference value is complicated, but you might visualize it like inverted text color against the background. Here's a nice demo from the CSS-Tricks Almanac showing off the different
To make the blending work, we need to set the color of our heading. In this case, we're assigning a light color value (
wheat) to the
"Raise the Curtains" Demo
I experienced a few problems while working out the details of the "raise the curtain" effect. If you want to add images to the "sticky" content, for example, avoid using images that don't look good when their colors are inverted. Here's a quick demo where I made a simple SVG and transparent PNG image, and it looks good.
Another gotcha: there's no way to set
mix-blend-mode: difference on specific child elements, like headings, while avoiding the effect on images. I discovered there are several reasons why it doesn't work, the first of which is that
position: sticky cancels the blending.
The same goes when using something like
transform: skewY on the container to add a little "tilt" to things. I suspect other properties don't play well with the blending, but I didn't go that far to find out which ones.
Here's the demo without scrolling that removes the troubling properties:
I enjoyed building this component, and I always love it when I can accomplish something using only HTML and CSS, especially when they work smoothly on every browser.
What will make with it? Is there a different way you would approach a "raise the curtain" effect like this? Let me know in the comments!