Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add a delay or smooth out scroll-driven animations using the animation-timeline property?

I'm currently using CSS scroll-driven animations with the new animation-timeline property. An example of the technique I'm referring to can be seen here: Image Reveal Example.

However, I'm facing an issue: I would like to add an animation delay to smooth out the animation, especially when scrolling with a mouse. Currently, the animation looks a bit choppy, as mouse scrolls on a PC can be less smooth and occur in larger, instant steps.

I've tried using the traditional CSS property animation-delay, but it doesn't seem to have any effect when used with scroll-driven animations and the animation-timeline property.

Here is an example of how I want the animation to feel:

// Initialize Lenis
const lenis = new Lenis();

// Listen for the scroll event and log the event data
lenis.on('scroll', (e) => {
  console.log(e);
});

// Use requestAnimationFrame to continuously update the scroll
function raf(time) {
  lenis.raf(time);
  requestAnimationFrame(raf);
}

requestAnimationFrame(raf);
.animation-element-wrapper {
  display: grid;
  justify-content: center;
  background-color: green;
}

.animation-element {
  background-color: red;
  height: 50px;
  width: 50px;
  animation: move;
  animation-timeline: view(block);
}

@keyframes move {
  from {
    transform: translateY(0) rotate(0deg);
  }
  to {
    transform: translateY(-150px) rotate(360deg);
  }
}

// Smooth scroll styling

html.lenis, html.lenis body {
  height: auto;
}

.lenis.lenis-smooth {
  scroll-behavior: auto !important;
}

.lenis.lenis-smooth [data-lenis-prevent] {
  overscroll-behavior: contain;
}

.lenis.lenis-stopped {
  overflow: hidden;
}

.lenis.lenis-smooth iframe {
  pointer-events: none;
}
<script src="https://unpkg.com/[email protected]/dist/lenis.min.js"></script>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>

<div class="animation-element-wrapper">
  <div class="animation-element"></div>
</div>

<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
(The stackoverflow snippet doesn't seem to work well with smooth scroll so here's a codepen where the smooth scroll does work: Smooth Scroll Example)

In this case, I'm using a JavaScript library to achieve smooth scrolling for the entire page. My goal is to apply a similar smooth, slow-moving effect for a CSS scroll-based animation without using a JS library.

To highlight the issue, here's another example without the smooth scroll effect:

.animation-element-wrapper {
  display: grid;
  justify-content: center;
  background-color: green;
}

.animation-element {
  background-color: red;
  height: 50px;
  width: 50px;
  animation: move;
  animation-timeline: view(block);
}

@keyframes move {
  from {
    transform: translateY(0) rotate(0deg);
  }
  to {
    transform: translateY(-150px) rotate(360deg);
  }
}
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>

<div class="animation-element-wrapper">
  <div class="animation-element"></div>
</div>

<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
Or Codepen: Non-Smooth Example

The difference in fluidity is noticeable, and I'd like to replicate the smoother experience using pure CSS.

Is there a way to introduce a delay or smooth out this animation behavior using just CSS, or is JavaScript still required? I'm primarily looking for a CSS-based solution.

like image 327
Jesper Ingels Avatar asked Oct 24 '25 21:10

Jesper Ingels


1 Answers

I've been able to figure it out by using only CSS:

@property --scroll-position {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}

@property --scroll-position-delayed {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}

@keyframes adjust-pos {
  to {
    --scroll-position: 1;
    --scroll-position-delayed: 1;
  }
}

.animation-element-wrapper {
  animation: adjust-pos linear both;
  animation-timeline: view(block);
  
  display: grid;
  justify-content: center;
  background-color: green;
}

.animation-element {
  transition: --scroll-position-delayed 0.15s linear;
}

.red-square {
  background-color: red;
  height: 50px;
  width: 50px;
  transform: translateY(calc(-150px * var(--scroll-position-delayed)));
}

/* Display debugging information */
#debug {
  position: fixed;
  top: 50%;
  left: 75%;
  translate: -50% -50%;
  background: white;
  border: 1px solid #ccc;
  padding: 1rem;
  
  & li {
    list-style: none;
  }
  
  counter-reset: scroll-position calc(var(--scroll-position) * 100) scroll-position-delayed calc(var(--scroll-position-delayed) * 100);
  
  [data-id="--scroll-position"]::after {
    content: "--scroll-position: " counter(scroll-position);
  }
  [data-id="--scroll-position-delayed"]::after {
    content: "--scroll-position-delayed: " counter(scroll-position-delayed);
  }
}
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>

<div class="animation-element-wrapper">
  <div class="animation-element">
    <div class="red-square"></div>
    <div id="debug">
      <ul>
        <li data-id="--scroll-position"></li>
        <li data-id="--scroll-position-delayed"></li>
      </ul>
    </div>
  </div>
</div>

<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>
<h1>Hello World!</h1>

This is all thanks to this article: https://www.bram.us/2023/10/23/css-scroll-detection/#lerp-effects

Explanation.
Here the scroll position is fetched (and animated) from the parent of the red-square:

    @keyframes adjust-pos {
      to {
        --scroll-position: 1;
        --scroll-position-delayed: 1;
      }
    }    
    
    .animation-element-wrapper {
        animation: adjust-pos linear both;
        animation-timeline: view(block);

Here the scroll-position is delayed (responsible for the smoothness).

.animation-element {
  transition: --scroll-position-delayed 0.15s linear;
}

Here the delayed scroll-position is used to animate the "red-square":

.red-square {
  background-color: red;
  height: 50px;
  width: 50px;
  transform: translateY(calc(-150px * var(--scroll-position-delayed)));
}
like image 171
Jesper Ingels Avatar answered Oct 27 '25 10:10

Jesper Ingels



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!