Moving Rainbow Underlines

Avatar of Chris Coyier
Chris Coyier on

AWS Amplify - the fastest, easiest way to develop mobile and web apps that scale.

I absolutely love the design of the Sandwich site. Among many beautiful features are these headlines with rainbow underlines that move as you scroll. It’s not scroll-jacking — it’s just a minor design feature that uses scroll position to enact a little movement.

To draw the rainbows themselves, we could use a linear gradient with hard-stops, the same kinda concept as drawing stripes in CSS. That’s a big ol’ chunk of CSS, which is fine, but I see they’ve opted for a background-image instead. Here’s that as an SVG, which is 661 bytes (tiny tiny). We can make it look like an underline by setting the background-size to limit the height and position it along the bottom with background-position.

We’ll do it on an inline element so the underline breaks where the words break:

h1 {
  span {
    background-image: url(spectrum.svg);
    background-repeat: repeat-x;
    background-size: 100vw 0.2em;
    background-position: left bottom 5px; 

To animate it, we move the background-position-x. Not a particularly performant thing to animate, but we’re not really animating it anyway — it’s just moving based on scroll position. Rather than manually manipulate the background-position-x, we’ll set it with a custom property, then manipulate the custom property with JavaScript.

background-position-x: var(--scrollPos);

Updating that variable while the page scrolls is easy peezy:

window.addEventListener("scroll", e => {
  let scrollTop = document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop; 
  let newPos = scrollTop + "px";'--scrollPos', newPos);

Here it is working!

See the Pen
Rainbow Underlines
by Chris Coyier (@chriscoyier)
on CodePen.

See that kinda janky line where I’m either using document.body or document.documentElement? That’s a stupid cross-browser thing where the “scrolling element” is different in Safari versus everything else.

While doing this I learned that you can use document.scrollingElement instead to avoid the pain there. I’ll leave a comment in the code about that, but leave the original line for posterity.