Popping Comments With CSS Anchor Positioning And View-Driven Animations


The State of CSS 2024 survey wrapped up and the results are interesting, as always. Even though each section is worth analyzing, we are usually most hyped about the section on the most used CSS features. And if you are interested in writing about web development (maybe start writing with us 😉), you will specifically want to check out the feature’s Reading List section. It holds the features that survey respondents wish to read about after completing the survey and is usually composed of up-and-coming features with low community awareness.

One of the features I was excited to see was my 2024 top pick: CSS Anchor Positioning, ranking in the survey’s Top 4. Just below, you can find Scroll-Driven Animations, another amazing feature that gained broad browser support this year. Both are elegant and offer good DX, but combining them opens up new possibilities that clearly fall into what most of us would have considered JavaScript territory just last year.

I want to show one of those possibilities while learning more about both features. Specifically, we will make the following blog post in which footnotes pop up as comments on the sides of each text.

For this demo, our requirements will be:

  • Pop the footnotes up when they get into the screen.
  • Attach them to their corresponding texts.
  • The footnotes are on the sides of the screen, so we need a mobile fallback.

The Foundation

To start, we will use the following everyday example of a blog post layout: title, cover image, and body of text:

The only thing to notice about the markup is that now and then we have a paragraph with a footnote at the end:

<main class="post">

  <!-- etc. -->

  <p class="note">
    Super intereseting information!
    <span class="footnote"> A footnote about it </span>
  </p>
</main>

In that demo, the footnotes are located inside the body of the post just after the text we want to note. However, we want them to be attached as floating bubbles on the side of the text. In the past, we would probably need a mix of absolute and relative positioning along with finding the correct inset properties for each footnote.

However, we can now use anchor positioning for the job, a feature that allows us to position absolute elements relative to other elements — rather than just relative to the containment context it is in. We will be talking about “anchors” and “targets” for a while, so a little terminology as we get going:

  • Anchor: This is the element used as a reference for positioning other elements, hence the anchor name.
  • Target: This is an absolutely-positioned element placed relative to one or more anchors. The target is the name we will use from now on, but you will often find it as just an “absolutely positioned element” in other resources.

I won’t get into each detail, but if you want to learn more about it I highly recommend our Anchor Positioning Guide for complete information and examples.

The Anchor and Target

It’s easy to know that each .footnote is a target element. Picking our anchor, however, requires more nuance. While it may look like each .note element should be an anchor element, it’s better to choose the whole .post as the anchor. Let me explain if we set the .footnote position to absolute:

.footnote {
  position: absolute;
}

You will notice that the .footnote elements on the post are removed from the normal document flow and they hover visually above their .note elements. This is great news! Since they are already aligned on the vertical axis, we just have to move them on the horizontal axis onto the sides using the post as an anchor.

Example of the footnotes inside the posts and where do we want them

This is when we would need to find the correct inset property to place them on the sides. While this is doable, it’s a painful choice since:

  1. You would have to rely on a magic number.
  2. It depends on the viewport.
  3. It depends on the footnote’s content since it changes its width.

Elements aren’t anchors by default, so to register the post as an anchor, we have to use the anchor-name property and give it a dashed-ident (a custom name starting with two dashes) as a name.

.post {
  anchor-name: --post;
}

In this case, our target element would be the .footnote. To use a target element, we can keep the absolute positioning and select an anchor element using the position-anchor property, which takes the anchor’s dashed ident. This will make .post the default anchor for the target in the following step.

.footnote {
  position: absolute;
  position-anchor: --post;
}

Moving the Target Around

Instead of choosing an arbitrary inset value for the .footnote‘s left or right properties, we can use the anchor() function. It returns a <length> value with the position of one side of the anchor, allowing us to always set the target’s inset properties correctly. So, we can connect the left side of the target to the right side of the anchor and vice versa:

.footnote {
  position: absolute;
  position-anchor: --post;

  /* To place them on the right */
  left: anchor(right);

  /* or to place them on the left*/
  right: anchor(left);

  /* Just one of them at a time! */
}

However, you will notice that it’s stuck to the side of the post with no space in between. Luckily, the margin property works just as you are hoping it does with target elements and gives a little space between the footnote target and the post anchor. We can also add a little more styles to make things prettier:

.footnote {
  /* ... */

  background-color: #fff;
  border-radius: 20px;
  margin: 0px 20px;
  padding: 20px;
}

Lastly, all our .footnote elements are on the same side of the post, if we want to arrange them one on each side, we can use the nth-of-type() selector to select the even and odd notes and set them on opposite sides.

.note:nth-of-type(odd) .footnote {
  left: anchor(right);
}

.note:nth-of-type(even) .footnote {
  right: anchor(left);
}

We use nth-of-type() instead of nth-child since we just want to iterate over .note elements and not all the siblings.

Just remember to remove the last inset declaration from .footnote, and tada! We have our footnotes on each side. You will notice I also added a little triangle on each footnote, but that’s beyond the scope of this post:

The View-Driven Animation

Let’s get into making the pop-up animation. I find it the easiest part since both view and scroll-driven animation are built to be as intuitive as possible. We will start by registering an animation using an everyday @keyframes. What we want is for our footnotes to start being invisible and slowly become bigger and visible:

@keyframes pop-up {
  from {
    opacity: 0;
    transform: scale(0.5);
  }

  to {
    opacity: 1;
  }
}

That’s our animation, now we just have to add it to each .footnote:

.footnote {
  /* ... */
  animation: pop-up linear;
}

This by itself won’t do anything. We usually would have set an animation-duration for it to start. However, view-driven animations don’t run through a set time, rather the animation progression will depend on where the element is on the screen. To do so, we set the animation-timeline to view().

.footnote {
  /* ... */
  animation: pop-up linear;
  animation-timeline: view();
}

This makes the animation finish just as the element is leaving the screen. What we want is for it to finish somewhere more readable. The last touch is setting the animation-range to cover 0% cover 40%. This translates to, “I want the element to start its animation when it’s 0% in the view and end when it’s at 40% in the view.”

.footnote {
  /* ... */
  animation: pop-up linear;
  animation-timeline: view();
  animation-range: cover 0% cover 40%;
}

This amazing tool by Bramus focused on scroll and view-driven animation better shows how the animation-range property works.

What About Mobile?

You may have noticed that this approach to footnotes doesn’t work on smaller screens since there is no space at the sides of the post. The fix is easy. What we want is for the footnotes to display as normal notes on small screens and as comments on larger screens, we can do that by making our comments only available when the screen is bigger than a certain threshold, which is about 1000px. If it isn’t, then the notes are displayed on the body of the post as any other note you may find on the web.

.footnote {
  display: flex;
  gap: 10px;

  border-radius: 20px;
  padding: 20px;

  background-color: #fce6c2;

  &::before {
    content: "Note:";
    font-weight: 600;
  }
}

@media (width > 1000px) {
  /* Styles */
}

Now our comments should be displayed on the sides only when there is enough space for them:

Wrapping Up

If you also like writing about something you are passionate about, you will often find yourself going into random tangents or wanting to add a comment in each paragraph for extra context. At least, that’s my case, so having a way to dynamically show comments is a great addition. Especially when we achieved using only CSS — in a way that we couldn’t just a year ago!

Recent Articles

Related Stories

Leave A Reply

Please enter your comment!
Please enter your name here