Search code examples
javascriptjquerycss-animationskeyframeonscroll

@keyframe animation on scroll event


I'm using a variable font and would like to animate it using @keyframes on scroll and then not animate when the user stops scrolling.

I can make the animation work, however when you stop scrolling, the animation stops and snaps back to the starting position which makes it look very jumpy.

To make it more of a smooth finish, I'm wondering if there is a way that when the user stops scrolling, it is possible to get the current position of the animation and then complete that animation loop and then stop, instead of snapping immediately back to the starting position?

As i cannot load the variable font into a jsfiddle using @font-face, i've put it up here: http://slug.directory/GX/

Here is the js...


    $(document).ready(function() {

    var scrollTimerId;

    $(window).scroll(function() {
        if (!scrollTimerId)
            $('body').addClass('scrolling');

        clearTimeout(scrollTimerId);
        scrollTimerId = setTimeout(function(){
            $('body').removeClass('scrolling');
            scrollTimerId = undefined;
        },150);
    });
});

and css...

@keyframes changewidth {
  0% {
    font-variation-settings: 'wght' 1;
  }

  100% {
    font-variation-settings: 'wght' 100;
  }
}

.scrolling {
  animation-duration: 0.5s;
  animation-name: changewidth;
  animation-iteration-count: infinite;
  animation-direction: alternate;
 animation-fill-mode: forwards;
}

body {
    font-family: "AG GX", Helvetica, sans-serif;
    font-weight: normal;
    font-style: normal;
    font-size: 2vw;
    line-height: 2vw;
    font-variation-settings: 'wght' 1;
    height: 300vh;
}

div {
  position: fixed;
}

Thanks in advance!


Solution

  • The situation you are facing is simply how to transition from any point in an animation to a static position.
    There is unfortunately no CSS defined way, so we have to resort on javascript to handle that.

    The basic idea is to trigger that transition manually. getComputedStyle can give you the value at which your animation currently is, so we can set it on our element's inline style, and then remove it right after a forced reflow for the transition to the original position triggers.

    Unfortunately Safari behaves weirdly and we have to toggle the transition property too, making this operation force 3 synchronous reflows...

    Here is an example using a moving box, as its easier to set up as a snippet:

    const box = document.getElementById( 'box' );
    onclick = e => {
      box.style.setProperty( 'transform', getComputedStyle( box ).transform );
      // set the inline style to the current value
      box.classList.toggle( 'anim' ); // disable the animation
      
      box.offsetWidth; // trigger a first reflow just for Safari
      box.classList.toggle( 'transition' ); // toggle the transition
      box.offsetWidth; // trigger an other reflow so the browser knows where we are
      box.style.removeProperty( 'transform' ); // come back to initial position
    };
    #box {
      width: 50px; 
      height: 50px;
      background: lime;
    }
    .anim {
      animation: move 2.5s infinite;
    }
    .transition {
      transition: transform 2s;
    }
    @keyframes move {
      from { transform: translate(0, 0) rotate(0deg); } /* Safari needs a 'from' */
      to { transform: translate(100vw, 0) rotate(360deg); }
    }
    <pre>click to toggle the animation on/off</pre>
    <div id="box" class="transition"></div>

    With your code that would give:

    $(window).scroll(function() {
      if (!scrollTimerId)
        $('body').addClass('scrolling')
          .removeClass('transition-font-variation');
    
      clearTimeout(scrollTimerId);
      scrollTimerId = setTimeout(function() {
        const val = getComputedStyle(document.body).getPropertyValue('font-variation-settings');
        document.body.style.setProperty( 'font-variation-settings', val );
        $('body').removeClass('scrolling');
        document.body.offsetWidth; // force reflow
        $('body').addClass('transition-font-variation');
        document.body.offsetWidth; // force reflow
        document.body.style.removeProperty( 'font-variation-settings' );
        scrollTimerId = undefined;
      }, 150);
    });
    
    body {
        font-family: "AG GX", Helvetica, sans-serif;
        font-weight: normal;
        font-style: normal;
        font-size: 2vw;
        line-height: 2vw;
        font-variation-settings: 'wght' 1;
        height: 300vh;
    }
    body.transition-font-variation {
      transition: font-variation-settings 2s;
    }
    

    (type $(window).off('scroll') in your js console before applying these changes if you wish to try it from OP's website).