Creating Scroll-Triggered Animations by combining Scroll-Driven Animations, Custom Properties, Style Queries, and Transitions

Thanks to the Scroll-Driven Animations Level 1 Specification it is now possible to drive CSS/WAAPI animations by scroll. Not included in that spec are Scroll-Triggered Animations: animations that run when you reach a certain scroll offset.

However, when you combine Scroll-Driven Animations with Custom Properties, Style Queries, and Transitions you can hack your way into creating Scroll-Triggered Animations.

~

# The Code

If you’re here for just the code, here it is. You can see this code in action in the demo.

@keyframes flipthebit {
	from {
		--bit: 0;
	}
	to {
		--bit: 1;
	}
}

:has(> .revealing-image) {
	display: block;
	animation: flipthebit linear both;
	animation-timeline: view();
	animation-range: contain 25% contain 25%;
}

@container style(--bit: 0) {
	.revealing-image {
		opacity: 0;
		clip-path: inset(0% 60% 0% 50%);
	}
}
@container style(--bit: 1) {
	.revealing-image {
		opacity: 1;
		clip-path: inset(0% 0% 0% 0%);
	}
}

.revealing-image {
	transition: all 0.5s ease-in-out;
}

If you want to know how it works, read on …

~

# How it works

The code is pretty big, and is a few things working together:

  1. A Scroll-Driven Animation which toggles a Custom Property --bit between 0 and 1
  2. A Style Query responding to that Custom Property, applying different styles based the Custom Property value
  3. CSS Transitions to “animate” between the two states.

Let’s go over each part individually …

~

1. The Animation

@keyframes flipthebit {
	from {
		--bit: 0;
	}
	to {
		--bit: 1;
	}
}

:has(> .revealing-image) {
	display: block;
	animation: flipthebit linear both;
	animation-timeline: view();
	animation-range: contain 25% contain 25%;
}

The animation is a set of keyframes flipthebit that change a custom property from 0 to 1. It is linked to View Timeline (animation-timeline: view();) tracking the parent element of the element that needs to animate.

To trigger the animation at a certain point in space, the animation-range is set. Its start and end are the same, as the bit flipping needs to happen in an instant.

~

2. The Style Query

@container style(--bit: 0) {
	.revealing-image {
		opacity: 0;
		clip-path: inset(0% 60% 0% 50%);
	}
}
@container style(--bit: 1) {
	.revealing-image {
		opacity: 1;
		clip-path: inset(0% 0% 0% 0%);
	}
}

With the --bit flipping between 0 and 1, the animation target can respond to that using a Style Query. When the value is 0, one set of styles apply. When the value is 1, different styles apply.

Basically the styles for style(--bit: 0) are the from styles, and the styles for style(--bit: 0) are the to styles.

UPDATE: Reader Ana Tudor pointed out you can do without a Style Query, by using --bit directly into calc() to get the result.

I’ve described this Binary Custom Properties technique before but must admit I’m not a big fan of it as it makes the code harder to read and doesn’t play nice with non-numeric values. Yes, you could move on to using Space Toggles in the latter case, but then again the code still is complicated to read/understand.

~

3. The Transition

.revealing-image {
	transition: all 0.5s ease-in-out;
}

To create the illusion of an animation, CSS Transitions are used. The set transition-duration of 0.5s is used to determine the “animation”’s duration.

~

# Demo

Here’s a demo that has it all together. If you don’t see any animation, that’s because your browser doesn’t support all necessary requirements. Check the Browser support section to see which browsers are supported.

See the Pen Scroll-Triggered Animations with Scroll-Driven Animations, Style Queries, and Transitions by Bramus (@bramus) on CodePen.

~

# Known limitations

While this approach works, it’s kinda nasty and has a few limitations that I see:

  1. These are no true animations but transitions.
  2. It requires a parent element to hook the scroll-driven animation onto.
  3. Requires Style Queries with Custom Properties to be implemented as well.
  4. The animations also run in reverse when scrolling back up. This is not always feasible.

Most likely there are more limitations which I’m currently overlooking.

~

# In Closing

This was a fun experiment to do. However, it’s only an experiment and to me makes the case that we still need proper Scroll-Triggered Animations in the future – maybe something to work one for scroll-animations-2? 😉

~

# Browser support

This is supported by all browsers that support Scroll-Driven Animations and Style Queries. Currently, at the time of writing, this are only Chromium-based browsers (Google Chrome, Microsoft Edge, Brave, Arc, …) versions 115 and up.

~

# Spread the word

To help spread the contents of this post, feel free to retweet its announcement tweet:

~

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Unless noted otherwise, the contents of this post are licensed under the Creative Commons Attribution 4.0 License and code samples are licensed under the MIT License

Join the Conversation

1 Comment

  1. Geat way and much cleaner than the same with JS.
    But without the support of FF the usage makes no sense for me 😐

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.