惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

K
Kaspersky official blog
小众软件
小众软件
Engineering at Meta
Engineering at Meta
博客园 - 三生石上(FineUI控件)
WordPress大学
WordPress大学
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V
V2EX
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Google DeepMind News
Google DeepMind News
Security Archives - TechRepublic
Security Archives - TechRepublic
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
C
Check Point Blog
aimingoo的专栏
aimingoo的专栏
罗磊的独立博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
MongoDB | Blog
MongoDB | Blog
L
LINUX DO - 热门话题
酷 壳 – CoolShell
酷 壳 – CoolShell
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
H
Help Net Security
Martin Fowler
Martin Fowler
G
GRAHAM CLULEY
Simon Willison's Weblog
Simon Willison's Weblog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - Franky
V
Vulnerabilities – Threatpost
云风的 BLOG
云风的 BLOG
博客园_首页
C
Cybersecurity and Infrastructure Security Agency CISA
量子位
Stack Overflow Blog
Stack Overflow Blog
Recent Announcements
Recent Announcements
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
I
Intezer
Scott Helme
Scott Helme
A
About on SuperTechFans
博客园 - 司徒正美
Hacker News: Ask HN
Hacker News: Ask HN
The GitHub Blog
The GitHub Blog
Forbes - Security
Forbes - Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - 聂微东
人人都是产品经理
人人都是产品经理
The Cloudflare Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Spread Privacy
Spread Privacy
T
Tailwind CSS Blog
S
Security Affairs
宝玉的分享
宝玉的分享

CSS Articles by Temani Afif

Get Ready For the Powerful CSS border-shape Property! | CSS-Tricks Let’s Play With Gap Decorations! How to Control Infinite CSS Animations (Part 2 of 2) How to Control Infinite CSS Animations (Part 1 of 2) Two Circles, One Arrow, and Anchor Positioning Making a Responsive Pyramidal Grid With Modern CSS | CSS-Tricks How to Create a CSS-only Elastic Text Effect Making Complex CSS Shapes Using shape() | CSS-Tricks Responsive Hexagon Grid Using Modern CSS | CSS-Tricks Responsive List of Avatars Using Modern CSS (Part 2) | CSS-Tricks Responsive List of Avatars Using Modern CSS (Part 1) | CSS-Tricks Perfectly Pointed Tooltips: To The Corners Perfectly Pointed Tooltips: All Four Sides Perfectly Pointed Tooltips: A Foundation Sequential linear() Animation With N Elements | CSS-Tricks Infinite Marquee Animation using Modern CSS Better CSS Shapes Using shape() — Part 4: Close and Move | CSS-Tricks Drawing CSS Shapes using corner-shape Better CSS Shapes Using shape() — Part 3: Curves | CSS-Tricks Better CSS Shapes Using shape() — Part 2: More on Arcs | CSS-Tricks Better CSS Shapes Using shape() — Part 1: Lines and Arcs | CSS-Tricks Creating Blob Shapes using clip-path: shape() Creating Flower Shapes using clip-path: shape() Custom progress element using the attr() function A CSS-Only Star Rating Component and More! (Part 2) | CSS-Tricks A CSS-Only Star Rating Component and More! (Part 1) | CSS-Tricks How to Create Wavy Boxes Using CSS Full-Bleed Layout with Modern CSS Fancy Menu Navigation Using Anchor Positioning | CSS-Tricks How to Create a Zig-Zag Box Using CSS How to Create Zig-Zag CSS Loaders Using One Element Custom Progress Element Using Anchor Positioning & Scroll-Driven Animations How to Create Filling CSS Loaders Using One Element How to Create Curved-Edge and Rounded-Edge Shapes Using CSS CSS Tricks That Use Only One Gradient | CSS-Tricks How to create Shapes with Inner Curves using CSS Mask How to Get the Width/Height of Any Element in Only CSS How Keyboard Navigation Works in a CSS Game How To Create Cut-Out Shapes using The clip-path property The Modern Guide For Making CSS Shapes — Smashing Magazine css-shape.com: The Ultimate Collection of CSS-only Shapes ⚡️ Sliding 3D Image Frames In CSS — Smashing Magazine CSS Tricks To Master The clip-path Property Creating Wavy Circles with Fancy Animations in CSS Modern CSS Tooltips And Speech Bubbles (Part 2) — Smashing Magazine Do you need a Tooltip or a Speech Bubble? I have created 100 using CSS 😲 Modern CSS Tooltips And Speech Bubbles (Part 1) — Smashing Magazine Creating Flower Shapes using CSS Mask & Trigonometric Functions CSS Shapes: The Triangle The Complex But Awesome CSS border-image Property — Smashing Magazine CSS Tricks to add 3D Effects to your Text CSS Responsive Multi-Line Ribbon Shapes (Part 2) — Smashing Magazine CSS Responsive Multi-Line Ribbon Shapes (Part 1) — Smashing Magazine I have made 100+ CSS-only Ribbon Shapes | The Perfect Collection 🎀 How to create a CSS-only infinite scroll animation Re-Creating The Pop-Out Hover Effect With Modern CSS (Part 2) — Smashing Magazine Re-Creating The Pop-Out Hover Effect With Modern CSS (Part 1) — Smashing Magazine How to Create CSS Ribbon Shapes with a Single Element CSS Shapes: The Ribbon Revealing Images With CSS Mask Animations — Smashing Magazine css-loaders.com: The Biggest Collection of Loading Animations (more than 500 🤯) How To Define An Array Of Colors With CSS — Smashing Magazine CSS effects on images II Shines, Perspective, And Rotations: Fancy CSS 3D Effects For Images — Smashing Magazine What's your Lucky CSS Pattern? Check It Now! How to Add a CSS Reveal Animation to Your Images Modern Layouts using CSS Grid I created 100+ unique CSS patterns | The best collection 🤩 How to Create a Custom Range Slider Using CSS CSS Shapes: The Heart CSS Shapes: Polygon & Starburst A Text Reveal Animation using CSS CSS Tip: learn CSS the easy way! How to build a CSS-only accordion How to create Breadcrumb Navigation with CSS Different Ways to Get CSS Gradient Shadows | CSS-Tricks A CSS-only responsive Stepper component A Fancy Hover Effect For Your Avatar | CSS-Tricks How to make a zoom effect using CSS Creating a Custom Cursor using CSS CSS Infinite 3D Sliders | CSS-Tricks CSS Infinite Slider Flipping Through Polaroid Images | CSS-Tricks How to create a responsive sidebar menu using CSS CSS Infinite and Circular Rotating Image Slider | CSS-Tricks Making Static Noise From a Weird CSS Gradient Bug | CSS-Tricks CSS Grid and Custom Shapes, Part 3 | CSS-Tricks Fancy Image Decorations: Outlines and Complex Animations | CSS-Tricks Fancy Image Decorations: Masks and Advanced Hover Effects | CSS-Tricks Fancy Image Decorations: Single Element Magic | CSS-Tricks How to create an infinite image slider using CSS How to Create Wavy Shapes & Patterns in CSS | CSS-Tricks How I Made a Pure CSS Puzzle Game | CSS-Tricks How to create a Tooltip/Speech Bubble using CSS CSS Grid and Custom Shapes, Part 2 | CSS-Tricks CSS Grid and Custom Shapes, Part 1 | CSS-Tricks Zooming Images in a Grid Layout | CSS-Tricks How to create a CSS-only loader with one element Exploring CSS Grid’s Implicit Grid and Auto-Placement Powers | CSS-Tricks How to create background pattern using CSS & conic-gradient Single Element Loaders: The Bars | CSS-Tricks
Custom Range Slider Using Anchor Positioning & Scroll-Driven Animations
Temani Afif on August 21, 2024 · 2024-08-21 · via CSS Articles by Temani Afif

Anchor positioning and scroll-driven animations are among of the most popular and exciting CSS features of 2024. They unlock a lot of possibilities, and will continue to do so as browser support improves and developers get to know them.

Here is a demo of a custom range slider where I am relying on such features.

This whole UI is a semantic HTML <input type="range">, with another semantic <output> element showing off the current value, along with quite fancy CSS.

Intuitively, you may think there is a JavaScript code somewhere gathering the value of the input “on change” and updating the position/content of the tooltip. As for the motion, it’s probably a kind of JavaScript library that calculates the speed of the mouse movement to apply a rotation and create that traction illusion.

Actually, there is no JavaScript at all.

It’s hard to believe but CSS has evolved in a way that we can achieve such magic without any scripts or library. You will also see that the code is not that complex. It’s a combination of small CSS tricks that we will dissect together so follow along!

At the time of writing, only Chrome (and Edge) have the full support of the features we will be using.

Prerequisites

First, let’s start with the HTML structure:

<label>
  Label
  <input type="range" id="one" min="0" max="120" value="20">
  <output for="one" style="--min: 0;--max: 120"></output>
</label>  Code language: HTML, XML (xml)

An input element and an output element are all that we need here. The label part is not mandatory for the functionality, but form elements should always be labelled and you need a wrapper element anyway.

I won’t detail the attributes of the input element but note the use of two CSS variables on the output element that should have the same values as the min and max attributes.

In addition to the HTML code, I am going to consider the styling of the range slider and the tooltip as prerequisites as well. I will mainly focus on the new features and skip most of the aesthetic parts, although I have covered some of those aspects in other articles, like here where I detail the styling of the range slider.

As for the tooltip, I have a big collection of 100 different tooltip shapes and I am going to use the #41 and #42. I also have a two-part article detailing the creation of most of the tooltips.

You don’t need the fancy styled tooltip output, nor do you need the custom styling of the range slider itself, it’s just fun and offers some visual control you might want. Here’s a “naked” demo without all that:

The first thing we are going to do is to correctly place the tooltip above (or below) the thumb element of the slider. This will be the job of Anchor positioning and here is the code:

input[type="range" i]::-webkit-slider-thumb {
  anchor-name: --thumb;
}
output {
  position-anchor: --thumb;
  position: absolute;
  position-area: top; /* or bottom */
}Code language: CSS (css)

That’s all! No more than four CSS declarations and our tooltip is correctly placed and will follow the movement of the slider thumb.

Anchor positioning is an upgrade of position: absolute here. Instead of positioning the element relatively to an ancestor having position: relative we can consider any element on the page called an “anchor”. To define an anchor we use anchor-name with whatever value you want. It’s mandatory to use the dashed indent notation like with custom properties. That same value can later be used within the absolute element to link it with the “anchor” using position-anchor.

Defining the anchor is not enough, we also need to correctly position the element. For this, we have the position-area.

The position-area CSS property enables an anchor-positioned element to be positioned relative to the edges of its associated anchor element by placing the positioned element on one or more tiles of an implicit 3×3 grid, where the anchoring element is the center cell.

ref

Here is an online tool to visualize the different values.

We’re using position-area: top on the <output>, and a bottom class flips that to position-area: bottom to re-position it and make the design work below.

Here is the demo so far:

Hmmmm, there is an issue! Both tooltips are linked to the same thumb. This is understandable, because I used the same anchor name so the first one will get ignored.

Use a different name, you say, and that’s correct, but it’s not the optimal solution. We can still keep the same name, and instead, limit the scope using anchor-scope.

label {
  anchor-scope: all;
}Code language: CSS (css)

The above code should limit the scope of any anchor defined inside the label element to the label element and its descendants. In other words, the anchors cannot be seen outside the label element.

We can also limit the scoping by adding position: relative to label.

This fixes the scoping problem, but the position of the tooltip is not good. It’s kind of strange, but it’s by design. Even if anchor-scope is doing a better job, I wanted to highlight the use of position: relative because you will for sure face it one day, so you have to understand why it does this.

By adding position: relative, we create a containing block for the tooltip and we trigger two behaviors. The first one is related to the scoping feature. An element cannot reference an anchor outside its containing block. You can find more details in this post: Why is Anchor Positioning not working?

The second behavior is related to overflow, as described in the specification:

If the box overflows its inset-modified containing block, but would still fit within its original containing block, by default it will “shift” to stay within its original containing block, even if that violates its normal alignment. This behavior makes it more likely that positioned boxes remain visible and within their intended bounds, even when their containing block ends up smaller than anticipated.

We have a default “safe” alignment that prevents the tooltip from overflowing its containing block, which can be good in some cases but not in ours. We want the tooltip to overflow the label element

To allow this, we can use justify-self: unsafe anchor-center; and align-self: unsafe end (or align-self: unsafe start for the bottom tooltip).

When using position-area: top, the browser applies a default alignment equivalent to justify-self: anchor-center and align-self: end. By adding the unsafe keyword, we allow it to overflow the containing block. Same logic for position: area: bottom, except that align-self is equal to start instead.

If you want to dig deeper into alignment in the context of anchor positioning, check out my series of articles about tooltips.

The Tooltip Content

Now that our tooltip is correctly positioned, let’s move to the content. This is where scroll-driven animations enter the story. I know what you are thinking: “We have nothing to scroll, so how are we going to use scroll-driven animations?”

If you read the MDN page you will find something called a “view progress timeline”:

You progress this timeline based on the change in visibility of an element (known as the subject) inside a scroller. The visibility of the subject inside the scroller is tracked as a percentage of progress — by default, the timeline is at 0% when the subject is first visible at one edge of the scroller, and 100% when it reaches the opposite edge.

This is perfect for us since we have a thumb (the subject) that moves inside the input (the scroller) so we don’t really need to have anything else to scroll.

We start by defining the timeline as follows:

input {
  overflow: hidden; /* or `auto` */
}
input[type="range" i]::-webkit-slider-thumb {
  view-timeline: --thumb-view inline;
}Code language: CSS (css)

Similar to what we did with the anchor, we give a name and the axis (inline) which is the horizontal one in our default writing mode. Then, we define overflow: hidden on the input element. This will make the input our scroller while the thumb is the subject.

If you forget about the overflow (so easy to forget!), another element will get used as the scroller, and won’t really know which one, and nothing will work as expected. Always remember that you need to define the subject using view-timeline and the scroller using overflow. I will repeat it again: don’t forget to define overflow on the scroller element!

Next, we define the animation:

@property --val {
  syntax: '<integer>';
  inherits: true;
  initial-value: 0; 
}
label {
  timeline-scope: --thumb-view;
}
output {
  animation: range linear both;
  animation-timeline: --thumb-view;
}
@keyframes range {
  0%   { --val: var(--max) }
  100% { --val: var(--min) }
}Code language: CSS (css)

Let’s start with timeline-scope. This is yet another scoping issue that will give you a lot of headaches. With anchor positioning, we saw that an anchor is by default available everywhere on the page and we have to limit its scope. With scroll-driven animations, the scope is limited to the element where it’s defined (the subject) and its descendant so we have to increase the scope to make it available to other elements. Two different implementations but the same issue.

Never ever forget about scoping when working with both features. Sometimes, everything is correctly defined and you are only missing timeline-scope or position: relative somewhere.

Next we define an animation that animates an integer between the min and max variables, then link that animation with the timeline we previously defined using animation-timeline.

Why the max is at 0% and the min at 100%? Isn’t that backwards, you ask?

Intuitively, we tend to think “from left to right” but this looks like it’s “from right to left”. To understand this, we need to consider the “scroll” part of the feature.

I know that we don’t have scrolling in our case but consider the following example to better understand.

When you scroll the container “from left to right” you have a red circle that moves “from right to left”. We still have the “from left to right” behavior but it’s linked to the scroll. As for the content, it will logically move in the opposite direction “from right to left”.

When the scroll is at the left, the element is at the right and when the scroll is at the right, the element is at the left. The same logic applies to our thumb even if there is nothing to scroll. When the thumb is at the right edge, this is our 0% state and we need to have the max value there. The left edge will be the 100% state and it’s the min value.

The last step is to show the value using a pseudo-element and counter()

output::before {
  content: counter(num);
  counter-reset: num var(--val);
}Code language: CSS (css)

And we are done!

Wait a minute, the values aren’t good! We are not reaching the min and max values. For the first slider, we are supposed to go from 0 to 120 but instead, we have 9 and 111.

Another trick related to the scroll part of the feature and here is a figure to illustrate what is happening:

The movement of the thumb is limited to the input container (the scroller) but the 0% and 100% state are defined to be outside the scroller. In our case, the subject cannot reach the 0% and 100% since it cannot go outside but luckily we can update the 0% and 100% state:

We can either use animation-range to make both states inside the container:

output {
  animation: range linear both;
  animation-timeline: --thumb-view;
  animation-range: entry 100% exit 0%;
}Code language: CSS (css)

Or we consider view-timeline-inset with a value equal to the width of the thumb.

input[type="range" i]::-webkit-slider-thumb{
  anchor-name: --thumb;
  view-timeline: --thumb-view inline;
  view-timeline-inset: var(--s); /* --s is defined on an upper element and is used to define the size of the thumb */
}Code language: CSS (css)

The first method seems better as we don’t have to know the size of the thumb (the subject) but keep in mind both methods. The view-timeline-inset property may be more suitable in some situations.

Now our slider is perfect!

A lot of stuff to remember, right? Between the scoping issues, the range we have to correct, the overflow we should not forget, the min that should be at 100% and max that should be at 0%, etc. Don’t worry, I feel the same. They are new features with new mechanisms so it requires a lot of practice to get used to them and build a clear mental model. If you are a bit lost, that’s fine! No need to understand everything at once. Take the time to play with the different demos, read the doc of each property, and try things on your own.

Adding Motion

Now let’s move to the fun part, those silly wobbly animations. A tooltip that follows the thumb with dynamic content is good but it’s even better if we add some motion to it.

You may think this is gonna be the hardest part but actually it’s the easiest one, and here is the relevant code:

@property --val {
  syntax: '<integer>';
  inherits: true;
  initial-value: 0; 
}
@property --e {
  syntax: '<number>';
  inherits: true;
  initial-value: 0; 
}
output {
  animation: range linear both;
  animation-timeline: --thumb-view;
  animation-range: entry 100% exit 0%;
}
output:before {
  content: counter(num);
  counter-reset: num var(--val);
  --e: var(--val);
  transition: --e .1s ease-out;
  rotate: calc((var(--e) - var(--val))*2deg);
}
@keyframes range {
  0%   { --val: var(--max) }
  100% { --val: var(--min) }
}Code language: CSS (css)

We add a new CSS variable --e with a number type. This variable will be equal to the --val variable. Until now, nothing fancy. We have two variables having the same value but one of them has a transition. Here comes the magic.

When you move the thumb, the animation will update the --val variable inside the output element. The pseudo-element will then inherit that value to update the content and also update --e. But since we are applying a transition to --e, it will not have an instant update but a smooth one (well, you know how transitions work!). This means that for a brief moment, both --e and --val will not be equal thus their difference is different from 0. We use that difference inside the rotation!

In addition to this, the difference can get bigger if you move the thumb fast or slow. Let’s suppose the current value is equal to 5. If you move the thumb rapidly to the value 50, the difference will be equal to 45 hence we get a big rotation. If you move to the value 7, the difference will be equal to 2 and the rotation won’t be that big.

Here is the full demo again so you can play with it. Try different speeds of movement and see how the rotation is different each time.

If you want to dig more into this technique and see more examples I advise you to read this article by Bramus.

Another Example

Let’s try a different idea.

This time, I am adjusting the tooltip position (and its tail) to remain within the horizontal boundary of the input element. Can you figure out how it’s done? This will be your homework!

For the tooltip part, I already did the job for you. I will redirect you again to my online collection where you can get the code of the tooltip shape. Within that code, I am already defining one variable that controls the tail position.

Conclusion

CSS is cool. A few years ago, doing such stuff with CSS would have been impossible. You would probably need one or two JavaScript libraries to handle the position of the tooltip, the dynamic content, the motion, etc. Now, all it takes is a few lines of CSS.

It’s still early to adopt those features and include them in real projects but I think it’s a good time to explore them and get an overview of what could be done in the near future. If you want more “futuristic” experimentation make sure to check my CSS Tip website where I regularly share cool demos!