How to make a “Raise the Curtains” effect in CSS
This article was originally published at CSS Tricks.
"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-gradientfor the "split" background
min-heightfor 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.
/** create the "split" background **/
background-image: linear-gradient(to bottom, var(--color2) 50%, var(--color1) 50%);
/** add extra space to the bottom (need this for the "sticky" effect) **/
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:
topdefine the stickiness and where it sticks.
mix-blend-mode: differenceblends the color of the content inside the
<h2>element into the
.curtain's background gradient.
display: flexcenters the content for presentation.
min-heightdefines the height of the container and allows for the extra space at the bottom.
colorsets the color of the
Now to put that into CSS code!
/** make the content sticky **/
/** blend the content with the contrast effect **/
/** center the content **/
/** set the minimum height of the section **/
/** set the color of the text **/
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!