Within the final article, we created a CSS-only star ranking part utilizing the CSS masks
and border-image
properties, in addition to the newly enhanced attr()
perform. We ended with CSS code that we are able to simply regulate to create part variations, together with a coronary heart ranking and quantity management.
This second article will research a special strategy that offers us extra flexibility. As an alternative of the border-image
trick we used within the first article, we’ll depend on scroll-driven animations!
Right here is identical star ranking part with the brand new implementation. And since we’re treading in experimental territory, you’ll need to view this in Chrome 115+ whereas we anticipate Safari and Firefox help:
Do you notice the distinction between this and the ultimate demo within the first article? This time, I’m updating the colour of the celebrities based mostly on what number of of them are chosen — one thing we can’t do utilizing the border-image
trick!
I extremely advocate you learn the primary article earlier than leaping into this second half should you missed it, as I can be referring to ideas and methods that we explored over there.
Yet one more time: On the time of writing, solely Chrome 115+ and Edge 115+ absolutely help the options we can be utilizing on this article, so please use both a kind of as you comply with alongside.
Why scroll-driven animations?
You may be questioning why we’re speaking about scroll-driven animation when there’s nothing to scroll to within the star ranking part. Scrolling? Animation? It’s much more complicated if you learn the MDN explainer for scroll-driven animations:
It means that you can animate property values based mostly on a development alongside a scroll-based timeline as a substitute of the default time-based doc timeline. This implies that you would be able to animate a component by scrolling a scrollable ingredient, fairly than simply by the passing of time.
However should you preserve studying you will notice that now we have two sorts of scroll-based timelines: scroll progress timelines and view progress timelines. In our case, we’re going to use the second; a view progress timeline, and right here is how MDN describes it:
You progress this timeline based mostly on the change in visibility of a component (generally known as the topic) inside a scroller. The visibility of the topic contained in the scroller is tracked as a share of progress — by default, the timeline is at 0% when the topic is first seen at one fringe of the scroller, and 100% when it reaches the alternative edge.
You may take a look at the CSS-Methods almanac definition for view-timeline-name
when you’re at it for one more rationalization.
Issues begin to make extra sense if we contemplate the thumb ingredient as the topic and the enter ingredient as the scroller. In spite of everything, the thumb strikes inside the enter space, so its visibility modifications. We are able to monitor that motion as a share of progress and convert it to a worth we are able to use to model the enter ingredient. We’re basically going to implement the equal of doc.querySelector("enter").worth
in JavaScript however with vanilla CSS!
The implementation
Now that now we have an thought of how this works, let’s see how every little thing interprets into code.
@property --val {
syntax: "<quantity>";
inherits: true;
initial-value: 0;
}
enter[type="range"] {
--min: attr(min sort(<quantity>));
--max: attr(max sort(<quantity>));
timeline-scope: --val;
animation: --val linear each;
animation-timeline: --val;
animation-range: entry 100% exit 0%;
overflow: hidden;
}
@keyframes --val {
0% { --val: var(--max) }
100% { --val: var(--min) }
}
enter[type="range"]::thumb {
view-timeline: --val inline;
}
I do know, this can be a lot of unusual syntax! However we’ll dissect every line and you will notice that it’s not all that advanced on the finish of the day.
The topic and the scroller
We begin by defining the topic, i.e. the thumb ingredient, and for this we use the view-timeline
shorthand property. From the MDN web page, we are able to learn:
The
view-timeline
CSS shorthand property is used to outline a named view progress timeline, which is progressed by way of based mostly on the change in visibility of a component (generally known as the topic) inside a scrollable ingredient (scroller).view-timeline
is about on the topic.
I feel it’s self-explanatory. The view timeline identify is --val
and the axis is inline
since we’re working alongside the horizontal x-axis.
Subsequent, we outline the scroller, i.e. the enter ingredient, and for this, we use overflow: hidden
(or overflow: auto
). This half is the best but in addition the one you’ll overlook probably the most so let me insist on this: don’t overlook to outline overflow on the scroller!
I insist on this as a result of your code will work effective with out defining overflow, however the values gained’t be good. The reason being that the scroller exists however can be outlined by the browser (relying in your web page construction and your CSS) and more often than not it’s not the one you need. So let me repeat it one other time: bear in mind the overflow
property!
The animation
Subsequent up, we create an animation that animates the --val
variable between the enter’s min
and max
values. Like we did within the first article, we’re utilizing the newly-enhanced attr()
perform to get these values. See that? The “animation” a part of the scroll-driven animation, an animation we hyperlink to the view timeline we outlined on the topic utilizing animation-timeline
. And to have the ability to animate a variable we register it utilizing @property
.
Word the usage of timeline-scope
which is one other difficult characteristic that’s straightforward to miss. By default, named view timelines are scoped to the ingredient the place they’re outlined and its descendant. In our case, the enter is a mum or dad ingredient of the thumb so it can’t entry the named view timeline. To beat this, we improve the scope utilizing timeline-scope
. Once more, from MDN:
timeline-scope
is given the identify of a timeline outlined on a descendant ingredient; this causes the scope of the timeline to be elevated to the ingredient thattimeline-scope
is about on and any of its descendants. In different phrases, that ingredient and any of its descendant components can now be managed utilizing that timeline.
Always remember about this! Generally every little thing is appropriately outlined however nothing is working since you overlook concerning the scope.
There’s one thing else you may be questioning:
Why are the keyframes values inverted? Why is the
min
is about to100%
and themax
set to0%
?
To grasp this, let’s first take the next instance the place you’ll be able to scroll the container horizontally to disclose a purple circle inside it.
Initially, the purple circle is hidden on the correct aspect. As soon as we begin scrolling, it seems from the correct aspect, then disappears to the left as you proceed scrolling in direction of the correct. We scroll from left to proper however our precise motion is from proper to left.
In our case, we don’t have any scrolling since our topic (the thumb) won’t overflow the scroller (the enter) however the principle logic is identical. The start line is the correct aspect and the ending level is the left aspect. In different phrases, the animation begins when the thumb is on the correct aspect (the enter’s max
worth) and can finish when it’s on the left aspect (the enter’s min
worth).
The animation vary
The final piece of the puzzle is the next essential line of code:
animation-range: entry 100% exit 0%;
By default, the animation begins when the topic begins to enter the scroller from the correct and ends when the topic has utterly exited the scroller from the left. This isn’t good as a result of, as we mentioned, the thumb won’t overflow the scroller, so it would by no means attain the beginning and the tip of the animation.

To rectify this we use the animation-range
property to make the beginning of the animation when the topic has utterly entered the scroller from the correct (entry 100%
) and the tip of the animation when the topic begins to exit the scroller from the left (exit 0%
).

To summarize, the thumb ingredient will transfer inside enter’s space and that motion is used to regulate the progress of an animation that animates a variable between the enter’s min
and max
attribute values. We have now our alternative for doc.querySelector("enter").worth
in JavaScript!
What’s happening with all of the
--val
cases in all places? Is it the identical factor every time?
I’m intentionally utilizing the identical --val
in all places to confuse you somewhat and push you to attempt to perceive what’s going on. We often use the dashed ident (--
) notation to outline customized properties (additionally known as CSS variables) that we later name with var()
. That is nonetheless true however that very same notation can be utilized to call different issues as effectively.
In our examples now we have three various things named --val
:
- The variable that’s animated and registered utilizing
@property
. It comprises the chosen worth and is used to model the enter. - The named view timeline outlined by
view-timeline
and utilized byanimation-timeline
. - The keyframes named
--val
and known as byanimation
.
Right here is identical code written with totally different names for extra readability:
@property --val {
syntax: "<quantity>";
inherits: true;
initial-value: 0;
}
enter[type="range"] {
--min: attr(min sort(<quantity>));
--max: attr(max sort(<quantity>));
timeline-scope: --timeline;
animation: value_update linear each;
animation-timeline: --timeline;
animation-range: entry 100% exit 0%;
overflow: hidden;
}
@keyframes value_update {
0% { --val: var(--max) }
100% { --val: var(--min) }
}
enter[type="range"]::thumb {
view-timeline: --timeine inline;
}
The star ranking part
All that now we have finished to date is get the chosen worth of the enter vary — which is actually about 90% of the work we have to do. What stays is a few primary types and code taken from what we made within the first article.
If we omit the code from the earlier part and the code from the earlier article here’s what we’re left with:
enter[type="range"] {
background:
linear-gradient(90deg,
hsl(calc(30 + 4 * var(--val)) 100% 56%) calc(var(--val) * 100% / var(--max)),
#7b7b7b 0
);
}
enter[type="range"]::thumb {
opacity: 0;
}
We make the thumb invisible and we outline a gradient on the principle ingredient to paint within the stars. No shock right here, however the gradient makes use of the identical --val
variable that comprises the chosen worth to tell how a lot is coloured in.
When, for instance, you choose three stars, the --val
variable will equal 3
and the colour cease of the primary colour will equal 3*100%/5
, or 60%
, which means three stars are coloured in. That very same colour can be dynamic as I’m utilizing the hsl()
perform the place the primary argument (the hue) is a perform of --val
as effectively.
Right here is the complete demo, which you’ll want to open in Chrome 115+ on the time I’m penning this:
And guess what? This implementation works with half stars as effectively with out the necessity to change the CSS. All you must do is replace the enter’s attributes to work in half increments.
<enter sort="vary" min=".5" step=".5" max="5">
That’s it! We have now our ranking star part that you would be able to simply management by adjusting the attributes.
border-image
or a scroll-driven animation?
So, ought to I take advantage of If we glance previous the browser help issue, I contemplate this model higher than the border-image
strategy we used within the first article. The border-image
model is easier and does the job fairly effectively, however it’s restricted in what it might do. Whereas our objective is to create a star ranking part, it’s good to have the ability to do extra and be capable of model an enter vary as you need.
With scroll-driven animations, now we have extra flexibility because the thought is to first get the worth of the enter after which use it to model the ingredient. I do know it’s not straightforward to know however don’t fear about that. You’ll face scroll-driven animations extra typically sooner or later and it’ll change into extra acquainted with time. This instance will look straightforward to you in good time.
Price noting, that the code used to get the worth is a generic code that you would be able to simply reuse even if you’re not going to model the enter itself. Getting the worth of the enter is unbiased of styling it.
Here’s a demo the place I’m including a tooltip to a spread slider to point out its worth:
Many methods are concerned to create that demo and one in all them is utilizing scroll-driven animations to get the enter worth and present it contained in the tooltip!
Right here is one other demo utilizing the identical approach the place totally different vary sliders are controlling totally different variables on the web page.
And why not a wavy vary slider?
This one is a bit loopy however it illustrates how far we go together with styling an enter vary! So, even when your objective is to not create a star ranking part, there are a variety of use circumstances the place such a way might be actually helpful.
Conclusion
I hope you loved this transient two-part collection. Along with a star ranking part made with minimal code, now we have explored a variety of cool and trendy options, together with the attr()
perform, CSS masks
, and scroll-driven animations. It’s nonetheless early to undertake all of those options in manufacturing due to browser help, however it’s a superb time to discover them and see what might be finished quickly utilizing solely CSS.