Within the final article, we created a CSS-only star score element utilizing the CSS masks
and border-image
properties, in addition to the newly enhanced attr()
operate. We ended with CSS code that we are able to simply regulate to create element variations, together with a coronary heart score and quantity management.
This second article will research a distinct 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 score element with the brand new implementation. And since we’re treading in experimental territory, you’ll need to view this in Chrome 115+ whereas we look forward to 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 celebs primarily based on what number of of them are chosen — one thing we can not do utilizing the border-image
trick!
I extremely advocate you learn the primary article earlier than leaping into this second half if you happen to missed it, as I might be referring to ideas and methods that we explored over there.
Yet another time: On the time of writing, solely Chrome 115+ and Edge 115+ totally help the options we might be utilizing on this article, so please use both a kind of as you observe alongside.
Why scroll-driven animations?
You is likely to be questioning why we’re speaking about scroll-driven animation when there’s nothing to scroll to within the star score element. Scrolling? Animation? However now we have nothing to scroll or animate! It’s much more complicated if you learn the MDN explainer for scroll-driven animations:
It means that you can animate property values primarily based on a development alongside a scroll-based timeline as a substitute of the default time-based doc timeline. This implies which you can animate a component by scrolling a scrollable aspect, somewhat than simply by the passing of time.
However if you happen to preserve studying you will notice that now we have two kinds 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 primarily based on the change in visibility of a component (often 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 other edge.
You may try the CSS-Methods almanac definition for view-timeline-name
whilst you’re at it for one more rationalization.
Issues begin to make extra sense if we take into account the thumb aspect as the topic and the enter aspect as the scroller. In spite of everything, the thumb strikes throughout the enter space, so its visibility modifications. We will observe that motion as a share of progress and convert it to a worth we are able to use to fashion the enter aspect. 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 part 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, it is 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 aspect, 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 primarily based on the change in visibility of a component (often known as the topic) inside a scrollable aspect (scroller).view-timeline
is about on the topic.
I believe 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 aspect, and for this, we use overflow: hidden
(or overflow: auto
). This half is the simplest but additionally the one you’ll neglect essentially the most so let me insist on this: don’t neglect to outline overflow on the scroller!
I insist on this as a result of your code will work wonderful with out defining overflow, however the values gained’t be good. The reason being that the scroller exists however might 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: keep 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()
operate 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
.
Notice the usage of timeline-scope
which is one other tough function that’s simple to miss. By default, named view timelines are scoped to the aspect the place they’re outlined and its descendant. In our case, the enter is a dad or mum aspect of the thumb so it can not entry the named view timeline. To beat this, we enhance the scope utilizing timeline-scope
. Once more, from MDN:
timeline-scope
is given the identify of a timeline outlined on a descendant aspect; this causes the scope of the timeline to be elevated to the aspect thattimeline-scope
is about on and any of its descendants. In different phrases, that aspect and any of its descendant parts can now be managed utilizing that timeline.
Always remember about this! Typically every part is appropriately outlined however nothing is working since you neglect concerning the scope.
There’s one thing else you is likely to 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 possibly can scroll the container horizontally to disclose a pink circle inside it.
Initially, the pink circle is hidden on the suitable facet. As soon as we begin scrolling, it seems from the suitable facet, then disappears to the left as you proceed scrolling in the direction of the suitable. 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 place to begin is the suitable facet and the ending level is the left facet. In different phrases, the animation begins when the thumb is on the suitable facet (the enter’s max
worth) and can finish when it’s on the left facet (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 suitable 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 can by no means attain the beginning and the top 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 suitable (entry 100%
) and the top of the animation when the topic begins to exit the scroller from the left (exit 0%
).

To summarize, the thumb aspect will transfer inside enter’s space and that motion is used to manage the progress of an animation that animates a variable between the enter’s min
and max
attribute values. We’ve got our substitute for doc.querySelector("enter").worth
in JavaScript!
What’s happening with all of the
--val
situations in every single place? Is it the identical factor every time?
I’m intentionally utilizing the identical --val
in every single place 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 nicely.
In our examples now we have three various things named --val
:
- The variable that’s animated and registered utilizing
@property
. It accommodates the chosen worth and is used to fashion 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 score element
All that now we have accomplished so far is get the chosen worth of the enter vary — which is truthfully about 90% of the work we have to do. What stays is a few primary kinds 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 aspect to paint within the stars. No shock right here, however the gradient makes use of the identical --val
variable that accommodates 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 shade will equal 3*100%/5
, or 60%
, that means three stars are coloured in. That very same shade can also be dynamic as I’m utilizing the hsl()
operate the place the primary argument (the hue) is a operate of --val
as nicely.
Right here is the complete demo, which you’ll want to open in Chrome 115+ on the time I’m scripting this:
And guess what? This implementation works with half stars as nicely with out the necessity to change the CSS. All you need to do is replace the enter’s attributes to work in half increments. Keep in mind, we’re yanking these values out of HTML into CSS utilizing attr()
, which reads the attributes and returns them to us.
<enter sort="vary" min=".5" step=".5" max="5">
That’s it! We’ve got our score star element which you can 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 take into account this model higher than the border-image
strategy we used within the first article. The border-image
model is less complicated and does the job fairly nicely, nevertheless it’s restricted in what it may possibly do. Whereas our purpose is to create a star score element, it’s good to have the ability to do extra and be capable to fashion 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 fashion the aspect. I do know it’s not simple to know however don’t fear about that. You’ll face scroll-driven animations extra usually sooner or later and it’ll change into extra acquainted with time. This instance will look simple to you in good time.
Price noting, that the code used to get the worth is a generic code which you can simply reuse even in case you are not going to fashion the enter itself. Getting the worth of the enter is impartial of styling it.
Here’s a demo the place I’m including a tooltip to a variety slider to point out its worth:
Many methods are concerned to create that demo and certainly one of 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 nevertheless it illustrates how far we go together with styling an enter vary! So, even when your purpose is to not create a star score element, there are lots of use circumstances the place such a method will be actually helpful.
Conclusion
I hope you loved this temporary two-part collection. Along with a star score element made with minimal code, now we have explored lots of cool and fashionable options, together with the attr()
operate, CSS masks
, and scroll-driven animations. It’s nonetheless early to undertake all of those options in manufacturing due to browser help, nevertheless it’s time to discover them and see what will be accomplished quickly utilizing solely CSS.