Happy Employees == Happy ClientsCAREERS AT DEPT®
DEPT® Engineering BlogCSS

Making a dynamically-expanding video in CSS

How I used clip-path to help achieve a dynamically-expanding video in CSS.

With two weeks to go on my project: rebuilding the website for The Tony Blair Institute for Global Change (TBI), the design team from BASIC/DEPT® realized that if this site were going to be an exciting and engaging user experience, we’d need a hero that wasn’t just your classic (boring) old video hero.  

We needed something that felt both inviting and intriguing. That would tease something more, forcing the user to lean in to see it. So they devised an idea to use an expanding window pane animation that would open as the user scrolls–and asked us to build it at the 11th hour.

Getting fancy with no time to spare

Of course, for anyone that’s ever built a website, or, well… anything with a deadline, there are always tasks lingering as you approach the end. In the case of TBI’s new site, we were still cleaning up the articles that had been ported over from the old Drupal site, tidying up page transitions, had a nav that was buggy and unpolished, and search functionality that needed tuning and testing.

On top of all that, one of our developers was off to Hawaii for a long-planned trip that coincidentally lined up perfectly with launch week, and the engineering team was feeling the heat to produce something that didn’t feel chock-full of bugs. But alas, after speaking with the client at length, we all agreed that launching an eye-catching site that felt exciting was more important than some lingering bugs buried deeper inside the other parts of the site. We’d go back and fix those later.

I was handed the following comp and told,

“As the user begins to scroll, the video widens as the scroll happens until it reaches maximum width. Then they can carry onwards down the page.”

It doesn’t seem too crazy, right?

That’s what I thought too. It’s just a couple of `div`s on the sides of the screen that shrink in width as the user scrolls. We’ll use fixed position to stick the video to the top of the page until we hit the bottom of our arbitrary scroll depth and then “release” it by positioning it absolutely to the bottom of the hero. This is just using position sticky, but because the video is nested and isn’t at the top level of the dom, position sticky won’t work here, so we can use a little JS and watch the scroll depth to flip it between “fixed” and “absolute.”

I spent the next hour or so coding this up, plopping in the video, tweaking the sizing of it (videos want to keep their aspect ratio, so making this fill the page responsively was a task all its own), properly positioning the titles and eyebrows and making the “explore” button at the bottom of the page work so that when a user clicks it, they automatically scroll to the bottom.

I got damn close and figured I was just about over the finish line when it looked like this:

It turns out I was not close.

I spent the next couple of hours banging my head against a wall trying to get the white text to show up between the window panes while the black text appeared on the outside. I tried all sorts of things, like using “mix-blend-mode,” a CSS property that “​​sets how an element's content should blend with the content of the element's parent and the element's background.” Still, it was a matter of layer order, and no matter what I did with z index and positioning, it wouldn’t work. After many attempts to track the z index of each individual element on the page or to try some funky technique to make some layers transparent with others opaque, I had to step away to the physical world to figure out my issue.

By definition, if those two text components were going to be in the same position on the page, one would have to be on top of the other. And that meant that with this approach, I would always have white or black text. There was no way to get the white text on the video and the black text on the window panes.

I even made myself a little physical example to figure out how the layers could be ordered to see if it would work. Spoiler alert: it does not.

So it was back to the drawing board, or in my case, to the place where I do my best thinking: the shower. As I stood there, trying to wash away my failure, frustrated that on a project with so little time left, I’d just burnt a few precious hours trying an approach that wouldn’t work and now seriously wondering if I’d have to tell the design team that their idea is impossible, I pondered what else I could do. Then I had what alcoholics and overworked devs both refer to as “a moment of clarity” and realized there was a better way to do it.

My old friend “clip-path.”

For those unfamiliar with clip-path, it is like a cookie cutter for elements on a webpage. It lets you cut out different shapes from elements, like circles or polygons, so that only the parts within the shape are visible.

It's a way to create exciting and unique designs by hiding or revealing specific areas of an element based on the shape you choose. Think of it as using different cookie cutters to create cookies in different shapes and sizes. Clip path provides flexibility when designing and shaping elements, allowing for more creative and visually appealing website layouts. Modern web browsers support it and it can be implemented using CSS properties or SVG (Scalable Vector Graphics) paths.

First things first, I had to figure out what the path would look like that I could use to cut out the white background and show through to the video behind it. Using a clip path generator, I played around with a few ways of doing it but used a frame-type shape where I pushed everything to the edge.

It started out looking like this:

And then, after pushing everything to the outside, it looked like this when static:

And here’s what the static path looks like in code; it’s color-coded so that you can see which value corresponds to which point on the image:

Creating this performant scroll effect

After figuring out how to cut out the window from an element on the page, I had to actually animate the thing and do it on scroll without absolutely trashing the performance (there is a video playing in the background, after all).

To create this performant scroll effect that would update the clip path property as the user progressed down the page, I used a library called framer-motion, which helps with all sorts of frontend animations. I recommend it to anyone doing semi-advanced animations or transitions on the page. You can do a lot with it and don’t have to deal with overloading the page until you are doing WAY too much.

Finally, the code

I can’t share the client repo in this post, but here’s a crude recreation of the effect in a code sandbox.

This is missing several styles that we used on the site, and it’s not responsive, but it does the job of communicating what exactly is happening. More or less, what’s happening is that as the user scrolls down the page, I adjust the inner corners of the clip path frame to slowly approach the edge until they reach 0 or 100%, respectively.

This has the effect of opening up the window panes. Sandbox: Framer motion transition

After all this effort, and as we continued sprinting to the finish line, a member of our client’s board told us, “I just don’t get it” when it came to the scroll effect, and we had to switch to something that would happen automatically rather than on scroll. It still looks pretty awesome, but, at least in my mind, it does not have quite the same allure as the scroll effect would have had. Such is life; you deliver what the client wants and what makes them happy.

If you made it this far, thanks for reading.

Here are some good resources for a bunch of the things mentioned above:

Z Index and clip path from the mozilla docs:

A clip path generator

The framer-motion library

The Tony Blair Institute for Global Change (where you can see the effect live)

Code sandbox of the effect