Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Tue, 18 Nov 2025 20:23:46 +0000 en-US hourly 1 https://wordpress.org/?v=6.9 225069128 More CSS random() Learning Through Experiments https://frontendmasters.com/blog/more-css-random-learning-through-experiments/ https://frontendmasters.com/blog/more-css-random-learning-through-experiments/#respond Tue, 18 Nov 2025 20:23:44 +0000 https://frontendmasters.com/blog/?p=7764 The random() function in CSS is well-specced and just so damn fun. I had some, ahem, random ideas lately I figured I’d write up.

As I write, you can only see random() work in Safari Technical Preview. I’ve mostly used videos to show the visual output, as well as linked up the demos in case you have STP.

Rotating Star Field

I was playing this game BALL x PIT which makes use of this rotating background star field motif. See the video, snipped from one of the games promo videos.

I like how the star field is random, but rotates around the center, and in rings where the direction reverses.

My idea for attempting to reproduce it was to make a big stack of <div> containers where the top center of them are all in the exact center of the screen. Then apply:

  1. A random() height
  2. A random() rotation

Then if I put the “star” at the end (bottom center) of each <div>, I’ll have a random star field where I can later rotate the container around the center of the screen to get the look I was after.

Making a ton of divs is easy in Pug:

- let n = 0;
- let numberOfStars = 1000;
while n < numberOfStars
- ++n
div.starContainer
div.star

Then the setup CSS is:

.starContainer {  
  position: absolute;
  left: 50%;
  top: 50%;
   
  rotate: random(0deg, 360deg);
  transform-origin: top center;
  display: grid;

  width: 4px;
  height: calc(1dvh * var(--c));

  &:nth-child(-n+500) {
    /* Inside Stars */
    --rand: random(--distAwayFromCenter, 0, 35);
  }

  &:nth-child(n+501) {
    /* Outside Stars */
    --rand: random(--distAwayFromCenter2, 35, 70);
  }

}

.star {
  place-self: end;
  background: red;
  height: calc(1dvh * var(--rand));
  width: random(2px, 6px);
  aspect-ratio: 1;
  border-radius: 50%;
}

If I chuck a low-opacity white border on each container so you can see how it works, we’ve got a star field going!

with border on container
border removed

Then if we apply some animated rotation to those containers like:

...
transform-origin: top center;
animation: r 20s infinite linear;

&:nth-child(-n+500) {
  ...
  --rotation: 360deg;
}

&:nth-child(n+501) {
  ...
  --rotation: -360deg;
}

@keyframes r {
  100% {
    rotate: var(--rotation);
  }
}

We get the inside stars rotating one way and the outside stars going the other way:

Demo

I don’t think I got it nearly as cool as the BALL x PIT design, but perhaps the foundation is there.

I found this particular setup really fun to play with, as flipping on and off what CSS you apply to the stars and the containers can yield some really beautiful randomized stuff.

Imagine what you could do playing with colors, shadows, size transitions, etc!

Parallax Stars

While I had the star field thing on my mind, it occurred to me to attach them to a scroll-driven animation rather than just a timed one. I figured if I selected a random selection of 1/3 of them into three groups, I could animate them at different speeds and get a parallax thing going on.

Demo

This one is maybe easier conceptually as we just make a bunch of star <div>s (I won’t paste the code as it’s largely the same as the Pug example above, just no containers) then place their top and left values randomly.

.star {
  width: random(2px, 5px);
  aspect-ratio: 1;
  background: white;
  position: fixed;
  top: calc(random(0dvh, 150dvh) - 25dvh);
  left: random(0dvh, 100dvw);

  opacity: 0.5;
  &:nth-child(-n + 800) {
    opacity: 0.7;
  }
  &:nth-child(-n + 400) {
    opacity: 0.6;
  }
}

Then attach the stars to a scroll-driven animation off the root.

.star {
  ...

  animation: move-y;
  animation-timeline: scroll(root);
  animation-composition: accumulate;
  --move-distance: 100px;

  opacity: 0.5;
  &:nth-child(-n + 800) {
    --move-distance: 300px;
    opacity: 0.7;
  }
  &:nth-child(-n + 400) {
    --move-distance: 200px;
    opacity: 0.6;
  }
}

@keyframes move-y {
  100% {
    top: var(--move-distance);
  }
}

So each group of stars either moves their top position 100px, 200px or 300px over the course of scrolling the page.

The real trick here is the animation-composition: accumulate; which is saying not to animate the top position to the new value but to take the position they already have and “accumulate” the new value it was given. Leading me to think:

I think `animation-composition: accumulate` is gonna see more action with `random()`, as it's like "take what you already got as a value and augment it rather than replace it".Here's a parallax thing where randomly-fixed-positioned stars are moved different amounts (with a scroll-driven animation)

Chris Coyier (@chriscoyier.net) 2025-11-14T16:22:46.035Z

Horizontal Rules of Gridded Dots

Intrigued by combining random() and different animation controlling things, I had the thought to toss steps() into the mix. Like what if a scroll-driven animation wasn’t smooth along with the scrolling, it kinda stuttered the movement of things only on a few “frames”. I considered trying to round() values at first, which is maybe still a possibility somehow, but landed on steps() instead.

The idea here is a “random” grid of dots that then “step” into alignment as the page scrolls. Hopefully creating a satisfying sense of alignment when it gets there, half way through the page.

Again Pug is useful for creating a bunch of repetitive elements1 (but could be JSX or whatever other templating language):

- var numberOfCells = 100;
- var n = 0;

.hr(role="separator")
- n = 0;
while n < numberOfCells
- ++n;
.cell

We can make that <div class="hr" role="seperator"> a flex parent and then randomize some top positions of the cells to look like:

.hr {
  view-timeline-name: --hr-timeline;
  view-timeline-axis: block;

  display: flex;
  gap: 1px;

  > .cell {
    width: 4px;
    height: 4px;
    flex-shrink: 0;
    background: black;

    position: relative;
    top: calc(random(0px, 60px));

    animation-name: center;
    animation-timeline: --hr-timeline;
    animation-timing-function: steps(5);
    animation-range: entry 50% contain 50%;
    animation-fill-mode: both;
  }
}

Rather than using a scroll scroll-driven animation (lol) we’ll name a view-timeline meaning that each one of our separators triggers the animation based on it’s page visibility. Here, it starts when it’s at least half-visible on the bottom of the screen, and finished when it’s exactly half-way across the screen.

I’ll scoot those top positions to a shared value this time, and wait until the last “frame” to change colors:

@keyframes center {
  99% {
    background: black;
  }
  100% {
    top: 30px;
    background: greenyellow;
  }
}

And we get:

Demo

Just playing around here. I think random() is an awfully nice addition to CSS, adding a bit of texture to the dynamic web, as it were.

  1. Styling grid cells would be a sweet improvement to CSS in this case! Here where we’re creating hundreds or thousands of divs just to be styleable chunks on a grid, that’s a lot of extra DOM weight that is really just content-free decoration. ↩︎

]]>
https://frontendmasters.com/blog/more-css-random-learning-through-experiments/feed/ 0 7764
Numbers That Fall (Scroll-Driven Animations & Sibling Index) https://frontendmasters.com/blog/numbers-that-fall-scroll-driven-animations-sibling-index/ https://frontendmasters.com/blog/numbers-that-fall-scroll-driven-animations-sibling-index/#comments Tue, 07 Oct 2025 19:40:28 +0000 https://frontendmasters.com/blog/?p=7338 Maybe you noticed the big number at the bottom of the current design of this site? As I write, that number is $839,000 and it’s just a reminder of how Frontend Masters gives back to the open source community. I did that design so I figured I’d write down how it works here.

Figuring out the Scroll-Driven Animation

It’s helpful to just stop and think about how you want the animation to behave in regard to scrolling. In this case, I’d like the numbers do be done animating shortly after they arrive on the page from the bottom. This means they will be visible/readable most of the time, which feels appropriate for text especially.

With this in mind, I recommend heading to Bramus Van Damme’s tool View Progress Timeline: Ranges and Animation Progress Visualizer. This tool is extremely useful to play around with to understand the different possibilities with different sizes of content. After various playing, I found a good match of animation-range-start and animation-range-end values for what I had in mind.

In the video above, we’re seeing a “fly in” animation. But that animation doesn’t matter. It’s just showing us the time range that will be relevant to whatever animation we choose to apply. Our numbers are going to “fall in”, and we’ll get to that soon.

Split the Numbers Up

We’ll put each number in a <span> so we can animate them individually. But we’ll make the accessible text for that component read properly with an aria-label attribute:

<div class="dollar-amount" aria-label="$839,000">
  <span class="dollar-sign">$</span>
  <span>8</span>
  <span>3</span>
  <span>9</span>
  <span>,</span>
  <span>0</span>
  <span>0</span>
  <span>0</span>
</div>

Animate Each Span

Each one of the numbers will have the same animation:

.dollar-amount {
  ...

  > span {
    display: inline-block;
    animation: scroll-in linear both;
    animation-timeline: view();
    animation-range: cover 0% entry-crossing 120%;
  }
}

The animation we’ve named there scroll-in might look like this:

@keyframes scroll-in {
  from {
    scale: 1.33;
    opacity: 0;
    translate: 0 -50px
  }
}

That will make each letter “fall” from 50px above down to it’s natural position, while also fading in and starting bigger and ending up it’s intrinsic size.

But, they will all do that the exact same way. We want staggering!

Staggering in a Scroll-Driven World

Usually animation staggering uses transition-delay or animation-delay on individual “nth” elements. Something like:

.dollar-amount {
  span:nth-child(1) { animation-delay: 0.1s; }
  span:nth-child(2) { animation-delay: 0.2s; }
  span:nth-child(3) { animation-delay: 0.3s; }
  ...
}

But that’s not going to work for us here. Delay in a scroll-driven animation doesn’t mean anything. I don’t think anyway?! I tried the above and it didn’t do anything.

Fortunately, the effect I was going for was a bit different anyway. I wanted the numbers to have a staggered fall in effect (see video above) where subsequent numbers almost look like they are falling from a different height and yet all arrive at the same time. So I could handle that like…

.dollar-amount {
  span:nth-child(1) { translate: 0 -20px; }
  span:nth-child(2) { translate: 0 -40px; }
  span:nth-child(3) { translate: 0 -60px; }
  ...
}

But, if we’re being really future-looking, we can handle it 1) within the @keyframes 2) in one line.

@keyframes scroll-in {
  from {
    scale: 1.33;
    opacity: 0;
    translate: 
      /* x */ calc(sibling-index() * 4px)
      /* y */ calc(sibling-index() * -20px);
  }
}

The sibling-index() function is perfect for staggering of any kind. It’ll return 1 for what would be :nth-child(1), 2 for what would be :nth-child(2), etc. Then we can use that integer in a calculation or delay.

Demo

That should do it!

(Note this will only work in Chrome’n’friends due to the sibling-index() usage. I’ll leave it as an exercise for the reader to write a fallback that supports a deeper set of browsers.)

The part that feels the weirdest to me are the “magic number”y values as part of the animation-range. But I guess they are about as magic as font-size or the like. They are values that describe the animation that works best for you, even if they are a little hard to immediately visualize.

]]>
https://frontendmasters.com/blog/numbers-that-fall-scroll-driven-animations-sibling-index/feed/ 2 7338
Scroll-Driven Letter Grid https://frontendmasters.com/blog/scroll-driven-letter-grid/ https://frontendmasters.com/blog/scroll-driven-letter-grid/#comments Mon, 09 Jun 2025 22:28:59 +0000 https://frontendmasters.com/blog/?p=6059 I was thinking about variable fonts the other day, and how many of them that deal with a variable axis for their weight go from 100 to 900. It varies — so you can always check wakamaifondue.com if you have the font file. Jost on Google Fonts is a classic example. Load that sucker up and you can use whatever weight you want.

I was also thinking about the “simple” kind of scroll-driven animations where all it does is move a @keyframe animation from 0% to 100% while a scrolling element goes from 0% to 100% “scrolled”. Fair warning that browser support isn’t great, but it’s just a fun thing that can easily just not happen.

It’s deliciously simple to use:

We can smash these things together. We should be able to map 0%-100% to 100-900 pretty easily, right?

Right.

Let’s made a grid of 100 letters inside a <div id="grid">. We could use any kind of HTML generating technology. Let’s just vanilla JavaScript here.

function generateGrid() {
  const grid = document.getElementById("grid");
  grid.innerHTML = "";

  for (let i = 0; i < 100; i++) {
    const div = document.createElement("div");
    div.textContent = getRandomLetter();
    grid.appendChild(div);
  }
}

generateGrid();

The lay it out as a 10✕10:

#grid {
  display: grid;
  grid-template-columns: repeat(10, 1fr);
}

We can chew through that grid in Sass applying random weights:

@for $i from 1 through 100 {
  #grid :nth-child(#{$i}) {
    font-weight: 100 + math.ceil(random() * 800);
  }
}

Looks like this.

But scroll up and down that preview!

I attached a scroll timeline to the document like:

html {
  scroll-timeline: --page-scroll block;
}

Then use that timeline to call an animation like:

#grid {
  > div {
    animation: to-thin auto linear;
    animation-timeline: --page-scroll;
  }
}

That animation is named to-thin, but actually I made three different animations: to-thick, to-thin, and to-mid, then applied them in rotation to all the letters, so any given letter does something a bit different.

@keyframes to-thick {
  50% {
    font-weight: 900;
  }
}
@keyframes to-thin {
  50% {
    font-weight: 100;
  }
}
@keyframes to-mid {
  50% {
    font-weight: 450;
  }
}

See how I used 50% keyframes there which is a nice trick to animate to that value half way through the animation, then back.

It then occurred to me I could make a secret message. So I make a @mixin that would override certain letters in CSS to make the message. It still randomized the weight, but all the letters animate to thin while the secret message animates to thick, revealing it as you scroll down.

Anyway this is sometimes how I spend my free time and it’s completely normal.

]]>
https://frontendmasters.com/blog/scroll-driven-letter-grid/feed/ 1 6059
Scroll-Driven & Fixed https://frontendmasters.com/blog/scroll-driven-fixed/ https://frontendmasters.com/blog/scroll-driven-fixed/#respond Fri, 20 Dec 2024 17:00:57 +0000 https://frontendmasters.com/blog/?p=4812 Scroll-driven animations is a good name. They are… animations… that are… scroll-driven. As you scroll you can make something happen. The most basic kind, where a @keyframe is ran 0% to 100% as the element is scrolled 0% to 100% is particularly easy to wrap your mind around.

I also think it’s fun to mess with the expectations of scrolling.

In very light, fun-only, non-crucial ways. Not in ways that would hurt the access of content.

Like what if we made an element that definitely scrolled:

.scrolling-element {
  height: 100dvh;
  /* ... make something inside it taller than it is ... */
}

But then all the content within it was position: fixed; so it didn’t move normally when the element was scrolled.

.scrolling-element {
  height: 100dvh;
  > * {
    position: fixed;
  }
}

Instead, we could have the elements react the scroll position however we wanted.

.scrolling-element {
  scroll-timeline-name: --my-scroller;
  scroll-timeline-axis: block;
  
  > * {
    position: fixed;
    animation: doSomethingCool linear;
    animation-timeline: --my-scroller;
  }
}

@keyframes doSomethingCool {
  100% {
    rotate: 2turn;
  }
}  

Here’s that basic setup:

I bet you could imagine that this is the same exact trick for a “scroll position indicator” bit of UI. Position that <div> as like a 2px tall bar and have the scaleX transform go from 0 to 100% and donezo.

I’ll use the same spirit here to have a whole grid of cells use that “scale to zero” animation to reveal a hidden picture.

I think that hidden picture thing is fun! I’m imagining a game where you have to guess the picture by scrolling down as little as possible. Like “name that tune” only “name that movie still” or whatever.

In this next one I took the idea a bit further and create randomized positions for each of the grid cells to “fly off” to (in SCSS).

I find that extraordinary that that kind of interaction can be done in HTML and CSS these days.

]]>
https://frontendmasters.com/blog/scroll-driven-fixed/feed/ 0 4812
(Up-) Scoped Scroll Timelines https://frontendmasters.com/blog/scoped-scroll-timelines/ https://frontendmasters.com/blog/scoped-scroll-timelines/#comments Mon, 11 Nov 2024 17:36:23 +0000 https://frontendmasters.com/blog/?p=4365 I keep learning little details about scroll-driven animations!

I started this little journey thinking about if you wanted to do special styling when a page scrolled through a certain section. I thought then that in order to pass scrolling information to descendants, you’d have to do it with --custom-properties. That’s sometimes still a decent idea, but it’s not strictly true, as those descendants can inherit a named timeline and tap into that to do styling on themselves.

Then I thought, while that’s a nice improvement, it’s still limited in the sense that only descendants can tap into a higher-up-the-DOM element’s timeline. Like an enforced parent/child situation. Turns out this isn’t true either, and again thanks to Bramus for showing me how it works.

Since we’re three-deep here on this journey, I figure calling it a series makes sense:

Article Series

Fair warning all this stuff is Chrome ‘n’ friends only right now. But I’ve seen flags in both Safari and Firefox so it’s coming along.

You can have any element on the page “listen” to a scroll (or view) timeline of a totally different element.

That’s the rub.

I wrongly assumed it had to be a parent/child thing (or parent/descendant). By default, that’s true, but if you intentionally move the scope of the timeline to another element up the DOM, you can make it work for any elements.

I’ll illustrate:

Demo of that:

The idea above is that you scroll the element on the top and the element below rotates. They are sibling elements though, so this is only possibly by “hoisting” the scroll-timeline to a higher-in-the-DOM element with timeline-scope so that the other element can pick up on it.

My ridiculous head thought of trying to make a quiz game or some kind of unlocking puzzle with getting scroll positions just right. I proved out the idea here:

There are a bunch of abused CSS tricks in there:

  1. Declare a custom property with @property so it’s value can be animated
  2. Make a scrolling element with a scroll-timeline
  3. Hoist that timeline up to a parent element
  4. Have the “number” element explicitly use that timeline
  5. Make the @keyframes animate that <integer> custom property
  6. Display the number using a pseudo element and counter()
  7. Use @container style() queries to check when the custom property is exactly the “answer” and update styling.

Phew. It all kinda leads up to that very last step where we can react to a value that came from a user scrolling. It might be a fun little project to build a bike lock number-twister thing with this.

Article Series

]]>
https://frontendmasters.com/blog/scoped-scroll-timelines/feed/ 3 4365
Named Scroll & View Timelines https://frontendmasters.com/blog/named-scroll-view-timelines/ https://frontendmasters.com/blog/named-scroll-view-timelines/#comments Mon, 04 Nov 2024 16:22:35 +0000 https://frontendmasters.com/blog/?p=4327 I just blogged about a way to pass information from an element that had a view timeline on itself down to it’s descendent elements. The idea is to set up CSS custom properties with @property and the @keyframe would animate those, thus the descendent’s would have access to them.

It worked fine (noting that scroll-driven animations are, so far, only in Chrome).

But Bramus noted that another possibility there was to name the view timeline and have the children reference that instead. His video on this is informative.

Article Series

The update to my original demo was pretty easy to do. Remember it had a section with a “pull quote”. The section had the view timeline, the pull quote is what I was trying to animate. So here I name the timeline (and note the scroll direction: block means vertical in logical property world):

.has-pullquote {
  animation: reveal linear both;
  animation-timeline: view();
  animation-range: cover 0% cover 100%;
  view-timeline: --section-pullquote block;
}

You have to name the timeline with a --double-sash name like that, which in modern CSS parlance is referred to as custom ident. It looks like a custom property but it isn’t, it’s just a unique name.

Now that the timeline is named, any descendent can reference it for it’s own animation timeline. Again specific to my demo, I switched things up to work like this (just the basics):

blockquote {
  /* Pull quote styling... */ 
  
  animation: blockquote linear both;
  animation-timeline: --section-pullquote;
}

@keyframes blockquote {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

Here’s an updated demo using that technique instead of animating all custom properties.

The actual demo updates a few more things just to have a bit more of a play and try things out. Notably, one of the effects still required me to animate a custom property still. That’s because I’m animating the color-stop of a gradient, and since that’s just a part of a whole valid value, it really needs to be done as a custom property.

Still, most of the animation work was moved over to using a keyframe applied directly to the descendent element itself, which I think is a logical improvement and I think it’s very cool how you can do that.

Essentially: you can name a view (or scroll) animation timeline (view-timeline or scroll-timeline) and any descendent element can tap into it and base it’s animation off of that (animation-timeline).

The fact that it works for descendents only is interesting to me. When I was playing with this to try to understand it, my first thought was to try to make a random element on the page have a scroll-timeline and another totally random element on the page “listen” to that one for it’s animation-timeline. I made them siblings and was confused why it wasn’t working. That ain’t gonna work, apparently, gotta have that parent/child thing going on. (Bramus’ video notes another gotcha: intermediary parents with overflow: hidden ruining the effect, and a perfect solution: overflow: clip).

I updated my playing to make it a parent/child relationship, and here’s that silly idea:

There you can see the “number” using the “scroller”s scroll-timeline for it’s animation-timeline and “animating” an integer value. If I wanted to take that integer and place it wherever on the page, it’s somewhat limiting because the parent container necessarily has overflow on it so it scrolls.

It does make me wonder if anchor positioning or even some abuse of popover would be able to pop it out of that constrained container, but that’ll have to be an exploration for another day.

Article Series

]]>
https://frontendmasters.com/blog/named-scroll-view-timelines/feed/ 1 4327
Edge to Edge Text https://frontendmasters.com/blog/edge-to-edge-text/ https://frontendmasters.com/blog/edge-to-edge-text/#comments Thu, 31 Oct 2024 22:33:58 +0000 https://frontendmasters.com/blog/?p=4294 I kid you not: Roman Komarov’s Fit-to-Width Text is one of my favorite CSS tricks I’ve ever seen. It’s, uh, quite a treat (that’s all you’re going to get here on Halloween, sorry). It’s just so strange. The end result is that you can size a line of text such that it hits the left and right edge of a container perfectly.

This is a very legitimate need that people have been solving for ages. If the container is a fixed size, you can solve it by setting ultra specific font sizes. But more likely these days, containers are of unknown widths, leaving us to JavaScript for figuring out how big of text we can fit in there. FitText was a seminal example. These days, we can also do it with container units, but it’s still extremely fiddly. Wouldn’t it be nice to be like font-size: make-it-fit;?

Roman’s trick is as close to that as can be. Check out his post for all the details, but the core concept is that it uses scroll-driven animations. The text gets set pretty big by default, then a scroll-driven animation is set on it which runs scales the text down essentially until animation-range: entry-crossing; is fulfilled then stops. Here’s an example with just one word (free free to resize and see):

The absolute core of the idea is:

@supports (animation-range: entry-crossing) {
  .fit-to-width {
    font-size: 12rem; /* max-font-size */
    overflow: hidden;

    & > * {
      inline-size: max-content;
      transform-origin: 0 0;
      animation: apply-text-ratio linear;
      animation-timeline: view(inline);
      animation-range: entry-crossing;
      display: block;
    }
  }
}

@keyframes apply-text-ratio {
  from {
    scale: 0;
    margin-block-end: -1lh;
  }
}

Like Roman’s original demo, it works great on multiple lines, actually showing off the power of the technique much better. The design of “multiple lines sized to fit exactly on a line” made me think of those “In this house we believe…” signs, so I made my own:

That demo has contenteditable on it so you can mess with the letters and see it work.

If, like me, you have a hard time wrapping your mind around the trick, note that you can inspect the animations in Chrome DevTools and see how each span has a different length of animation:

I think the longer the animation the more the text scales down toward zero. Phew — I told you it was weird.

]]>
https://frontendmasters.com/blog/edge-to-edge-text/feed/ 3 4294
Scroll-Driven… Sections https://frontendmasters.com/blog/scroll-driven-sections/ https://frontendmasters.com/blog/scroll-driven-sections/#comments Tue, 29 Oct 2024 12:45:59 +0000 https://frontendmasters.com/blog/?p=4277 I was checking out a very cool art-directed article the other day, full of scrollytelling, and, like us web devs will be forever cursed to do, wondering what they used to build it. Spoiler: it’s GSAP and ScrollTrigger.

No shame in those tech choices, they are great. But with scroll-driven animations now being a web standard with growing support, it begs the question whether we could do this with native technologies.

My brain focused on one particular need of the scrollytelling style:

  1. While the page scrolls through a particular section
  2. Have a child element appear in a fixed position and be animated
  3. … but before and after this section is being scrolled through, the element is hidden

Perhaps a diagram can help drive that home:

But I was immediately confused when thinking about how to do this with scroll-driven animations. The problem is that that “section” itself is the thing we need to apply the animation-timeline: view(); to, such that we have the proper moment to react to (“the section is currently in view!“). But in my diagram above, it’s actually a <blockquote> that we need to apply special conditional styling to, not the section. In a @keyframe animation, all we can do is change declarations, we can’t select other elements. Apologies if that confusing, but the root of is that we need to transfer styles from the section to the blockquote without using selectors — and it’s weird.

The good news is that what we can do is update CSS custom properties on the section, and those values will cascade to all the children of the section, and we can use those to style the blockquote.

First, in order to make a custom property animatable, we need to declare it’s type. Let’s do a fade in first, thus we need opacity:

@property --blockquoteOpacity {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

Now the section itself has the animation timeline:

section.has-pullquote {
  animation: reveal linear both;
  animation-timeline: view();
  animation-range: cover 0% cover 100%;
}

And that animation we’ve named reveal above can now update the custom property:

@keyframes reveal {
  from {
    --blockquoteOpacity: 0%;
  }
  to% {
    --blockquoteOpacity: 100%;
  }
}

Now as the animation runs, based on it’s visibility in the viewport, it will update the custom property and thus fade/in out the blockquote:

blockquote {
  opacity: var(--blockquoteOpacity);

  position: sticky;
  top: 50%;
  transform: translateY(-50%);
}

Note I’m using position: sticky in there too, which will keep our blockquote in the middle of the viewport while we’re cruising through that section.

Try it out (Chrome ‘n’ friends have stable browser support):

Here’s a video of it working in case you’re in a non-supporting browser:

Because we instantiated the opacity custom property for the opacity at 100%, even in a non-supporting browser like Safari, the blockquote will be visible and it’s a fine experience.

I found this all a little fiddly, but I’m not even sure I’m doing this “correctly”. Maybe there is a way to tap into another elements view timeline I’m not aware of? If I’m doing it the intended way, I could see this getting pretty cumbersome with lots of elements and lots of different values needing updated. But after all, that’s the job sometimes. This is intricate stuff and we’re using the CSS primitives directly. The control we have is quite fine-grained, and that’s a good thing!

Article Series

]]>
https://frontendmasters.com/blog/scroll-driven-sections/feed/ 9 4277
Custom Range Slider Using Anchor Positioning & Scroll-Driven Animations https://frontendmasters.com/blog/custom-range-slider-using-anchor-positioning-scroll-driven-animations/ https://frontendmasters.com/blog/custom-range-slider-using-anchor-positioning-scroll-driven-animations/#comments Wed, 21 Aug 2024 14:20:29 +0000 https://frontendmasters.com/blog/?p=3569 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>  

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 Tooltip Position

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 */
}

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: --thumb;
}

The above code should limit the scope of the anchor --thumb to the label element and its descendant. In other words, the anchor cannot be seen outside the label element.

Another fix is to add position: relative to label. I won’t detail how it works but it has to do with the creation of a containing block.

Hmmmmm. We have fixed the scoping problem but the position of the tooltip is still not good. If you move the thumb to the edges, the tooltip is no longer following. It’s limited to the boundary of the slider. It’s kind of strange, but it’s by design.

By adding position: relative we create a containing block for the tooltip and we trigger the following behavior described by 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.

To fix this, we can use justify-self: unsafe anchor-center;.

When using position-area: top (or bottom), the browser applies a default alignment in the horizontal axis equivalent to justify-self: anchor-center. By adding the unsafe keyword, we allow it to overflow the containing block instead of shifting inside it.

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;
}

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) }
}

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);
}

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%;
}

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 */
}

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) }
}

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!

]]>
https://frontendmasters.com/blog/custom-range-slider-using-anchor-positioning-scroll-driven-animations/feed/ 3 3569
How to Get the Width/Height of Any Element in Only CSS https://frontendmasters.com/blog/how-to-get-the-width-height-of-any-element-in-only-css/ https://frontendmasters.com/blog/how-to-get-the-width-height-of-any-element-in-only-css/#respond Thu, 25 Jul 2024 14:14:28 +0000 https://frontendmasters.com/blog/?p=3119 Getting the dimension of an element using JavaScript is a trivial task. You barely even need to do anything. If you have a reference to an element, you’ve got the dimensions (i.e. el.offsetWidth / el.offsetHeight). But we aren’t so lucky in CSS. While we’re able to react to elements being particular sizes with @container queries, we don’t have access to a straight up number we could use to, for example, display on the screen.

It may sound impossible but it’s doable! There are no simple built-in functions for this, so get ready for some slightly hacky experimentation.

Note: At time of writing, only Chrome (and Edge) have the full support of the features we will be using so consider those browsers to read the article.

Let’s start with a demo:

This demo has a simple layout with elements that will all have different sizes. Each rectangular element displays it’s own width/height. You can resize the browser or adjust the content; the values will update automatically.

Don’t try to find the hidden JavaScript, it’s 100% CSS magic, powered mostly by scroll-driven animations.

Why Scroll-Driven Animations?

Scroll-Driven animations is one of the most popular new CSS features in 2024. It unlocked a lot of possibilities and solved some common problems.

How are these features relevant to this situation of figuring out an element’s dimensions, though?

The terms “scroll” and “animation” tend to bring to mind, uhh, animating stuff on scroll. To be fair, that is the main purpose:

It allows you to animate property values based on a progression along a scroll-based timeline instead of the default time-based document timeline. This means that you can animate an element by scrolling a scrollable element, rather than just by the passing of time.

MDN

But we can think about it differently and achieve more than a simple animation on scroll. If you keep reading the MDN page, it explains there are two types of “scroll-based timelines”. In our case, we will consider the “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.

MDN

With this type of scroll timeline, there are three relevant elements: the scroller which is the container having the scroll, the subject which is an element moving inside the container and the animation that will progress based on the position of the “subject” inside the “scroller”.

The three elements are linked with each other. To identify the progress of the animation we need to know the position of the subject inside the scroller and for this, we need to know the dimension of the scroller, the dimension of the subject, and the offset of the subject (the distance between the subject and the edges of the scroller).

So our equation contains four variables:

  1. Dimension of the scroller
  2. Dimension of the subject
  3. Progress of the animation
  4. Offset of the subject

If three variables are known, we can automatically find the missing one. In our case, the missing variable will be the “dimension of scroller” and that’s how we are going to find the width/height of any element (an element that will a be scroller).

How Does it Work?

Let’s dive into the theory and get to how scroll-driven animations are actually used to do this. It won’t be long and boring, I promise! I’ll be using width as the dimension being measured, but height would use the same logic just on the other axis.

Consider the following figure:

We have a container (the scroller) and an element inside it (the subject) placed on the left. There are two special positions within the container. The 0% position is when the element is at the right (inside the container) and the 100% position is when the element has exited the container from the left (outside the container).

The movement of the subject between 0% and 100% will define the percentage of the progression but our element will not move so the percentage will be fixed. Let’s call it P. We also know the width of the subject and we need to find the width of the scroller.

Remember the variables we talked about. Considering this configuration, we already know three of them: “the width of the subject”, “the offset of the subject” (fixed to the left edge), and the “progress of the animation” (since the subject is fixed). To make things easier, let’s consider that the width of the scroller is a multiplier of the width of the subject:

W = N * S.

The goal is to find the N or more precisely, we need to find the relation between the P and N. I said the P is fixed, but in reality it’s only fixed when the scroller width is fixed which is logical. But if the width of the scroller changes, the progress will also change, that’s why we need to find the formula between the progress and the width.

Let’s start with the case where the width of the scroller is equal to twice the width of the subject, we get the following:

The subject is in the middle between 0% and 100% so the progress in this case is 50%. For N = 2 we get P = 50%.

Let’s try for N = 3:

Now we have two extra slots in addition to the 0% and 100%. If we suppose that the subject can only be placed inside one of the 4 slots, we can have the following progress: 0%33.33%66.67%100%. But the subject is always placed at the before-the-last slot so the progress in this case is equal to 66.67% or, seen differently, it’s equal to 100% - 100%/3 (100%/3 is the progression step).

Are you seeing the pattern? If the width of the scroller is equal to N times the width of the subject we will have N+1 slots (including 0% and 100%) so the step between each slot is equal to 100%/N and the subject is located at the before-the-last slot so the progress is equal to 100% - 100%/N.

We have our equation: P = 100% - 100%/N so N = 100%/(100% - P).

If we convert the percentage to values between 0 and 1 we get N = 1/(1 - P) and the width we are looking for is equal to W = N * S = S/(1 - P).

Now If we consider a width for the subject equal to 1px, we get W = 1px/(1 - P) and without the unit, we have W = 1/(1 - P).

Let’s Write Some Code

Enough theory! Let’s transform all this into code. We start with this structure:

<div class="container"></div>
.container {
  overflow: auto;
  position: relative;
}
.container:before {
  content: "";
  position: absolute;
  left: 0;
  width: 1px;
}

The scroller element is the container and the subject element is a pseudo-element. I am using position: absolute so the subject doesn’t affect the width of the container (the value we need to calculate). Like described in the previous section, it’s placed at the left of the container with 1px of width.

Next, we define a named timeline linked to the pseudo-element (the subject)

.container {
  timeline-scope: --cx;
}
.container:before {
  view-timeline: --cx inline
}

The MDN description of the property:

The view-timeline CSS shorthand property is used to define a named view progress timeline, which is progressed through based on the change in visibility of an element (known as the subject) inside a scrollable element (scroller). view-timeline is set on the subject.

We consider the inline (horizontal) axis. We need to also use timeline-scope to give the container access to the view progress. By default, a named timeline is scoped to the element where it’s defined (and its descendants) but we can change this to make it available at any level.

Why not define the scope at the html level, then?

Enlarging the scope to all the elements may sound like a good idea, but it’s not. We may need to use the same code for different elements so limiting the scope allows us to reuse the same code and keep the same naming.

I won’t spend too much time detailing the scope feature but don’t forget about it. If the code doesn’t work as intended, it’s probably a scoping issue.

Now let’s define the animation:

@property --x {
  syntax: "<number>";
  inherits: true;
  initial-value: 0; 
}
.container {
  animation: x linear;
  animation-timeline: --cx;
  animation-range: entry 100% exit 100%; 
}
@keyframes x {
  0%   { --x: 0; }
  100% { --x: 1; }
}

We define a keyframes that animates a variable from 0 to 1. We have to register that variable with a number type to be able to animate it. We run the animation on the container with a linear easing and define the timeline using animation-timeline.

At this step, we told the browser to consider the named timeline defined on the pseudo-element (the subject) as the reference for the animation progress. And that progress will be stored in the --x variable. At 50%, we have --x: 0.5, at 70%, we have --x: 0.7, and so on.

The last step is to add the formula we identified earlier:

@property --w {
  syntax: "<integer>";
  inherits: true;
  initial-value: 0; 
}
.container {
  --w: calc(1/(1 - var(--x)));
}

The --w variable will contain the width in pixel of the container as a unitless value. It’s important to notice the “unitless” part. It gives us a lot of flexibility as we can integrate it within any formula. If you are a CSS hacker like me, you know what I mean!

What about that animation-range: entry 100% exit 100%;?

In addition to using a named timeline to define which element control the progress, we can also control the range of the animation. In other words, we can explicitly define where the 0% and 100% progress are located within the timeline.

Let’s get back to the first figure where I am showing the 0% and 100% progress.

The 0% is when the subject has completely entered the scroller from the right. We can express this using animation-range-start: entry 100%.

The 100% is when the subject has completely exited the scroller from the left. We can express this using animation-range-end: exit 100%.

Or using the shorthand:

animation-range: entry 100% exit 100%;

If you are new to scroll-driven animations, this part is not easy to grasp, so don’t worry if you don’t fully understand it. It requires some practice to build a mental model for it. Here is a good online tool that can help you visualize the different values.

Now, we do the same for the height and we are done. Here is the first demo again so you can inspect the full code.

Notice that I am using another pseudo-element to show the values. Let’s consider this as our first use case. Being able to get the width/height of any element and show them using only CSS is super cool!

.size::after {
  content: counter(w) "x" counter(h);
  counter-reset: w var(--w) h var(--h);
}

Are There Any Drawbacks?

Even if it seems to work fine, I still consider this as a “hack” to be used with caution. I am pretty sure it will fail in many situations so don’t consider this as a robust solution.

I also said “any element” in the title but in reality not all of them. It’s mandatory to be able to have a child element (the subject) so we cannot apply this trick to elements like <img> for example.

You also need to add overflow: auto (or hidden) to the container to make it the scroller for the subject. If you plan to have overflowing content then this solution will give you some trouble.

The value you will get using this method will include the padding but not the border! Pay attention to this part and compare the values you get with the ones of the Dev tools. You may need to perform another calculation to get the real dimension of the element by adding or subtracting specific amounts.

Another drawback is related to the use of 1px as our unit. We assumed that the size is a multiplier of 1px (which is true in most cases) but if your element is having a size like 185.15px, this trick won’t work. We can overcome this by using a smaller width for the subject (something like 0.01px) but I don’t think it is worth making this hack more complex.

A Few Use Cases

The first use case we saw is to show the dimension of the element which is a cool feature and can be a good one for debugging purposes. Let’s dig into more use cases.

Getting the Screen Dimension

We already have the viewport units vh and vw that works fine but this method can give us the unitless pixel values. You may ask how to do this since the viewport is not a real element. The solution is to rely on position: fixed applied to any element on the page. A fixed element is positioned relative to the viewport so its scroller will the viewport.

If you check the code, you will see that I am relying on the HTML pseudo-element for the subject and I don’t need to define any overflow or position on the HTML element. Plus the values are available globally since they are defined inside the HTML element!

For this particular case, I also have another CSS trick to get the screen dimension with an easier method:

Calculating the Scrollbar Width

There is a slight difference between the two screen width calculating methods above. The first demo will not include the scrollbar width if the page has a lot of content but the second one will. This means that If we combine both methods we can get the width of scrollbar!

Cool right? In addition to the screen dimension, you can also have the width of the scrollbar. Both values are available at root level so you can use them anywhere on the page.

If you want, you can also get the scrollbar width using a different method like I am detailing here: Get the scrollbar width using only CSS

Counting Stuff

All the calculations we did were based on the 1px size of the subject. If we change this to something else we can do some interesting counting. For example, if we consider 1lh (the height of the line box) we can count the number of lines inside a text.

Here is the version where you can edit the content. The number of lines will adjust based on the content you will enter.

Note how I am playing with the scope in this example. I am making the variable available at a higher level to be able to show the count inside a different element. Not only we can count the numbers of lines but we can also show the result anywhere on the page.

Can you think about something else to count? Share your example in the comment section.

Transferring Sizes

Being able to control the scope means that we can transfer the size of an element to another one on the page.

Here is an example where resizing the left element will also resize the right one!

Another important part of this trick is being able to get the width/height values as integer. This allows us to use them within any formula and append any unit to them.

Here is an example, where resizing the left element will rotate/scale the right one.

I have mapped the width with the rotation and the height with the scaling. Cool right? We can get the width/height of an element, have them as an integer, and transfer them to another element to do whatever we want. CSS is magic!

Conclusion

I hope you enjoyed this funny experiment. I still insist on the fact that it’s a hacky workaround to do something that was not possible using CSS. Use it for fun, use it to experiment with more CSS-only ideas but think twice before including this into a real project. Using one line of JavaScript code to get the dimension of an element is safer. Not all CSS-only tricks are a good replacement for JavaScript.

This said, if you find an interesting use case or you have another CSS-only experimentation where this trick can be useful, share it in the comment section.

If you’re interested in more experimentation with scroll-eriven animations check the following articles:

Article Series

]]>
https://frontendmasters.com/blog/how-to-get-the-width-height-of-any-element-in-only-css/feed/ 0 3119