简体   繁体   中英

@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!

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

I couldn't access your font, so I've used css property color instead!

To keep track of animation end-point, I've used animationiteration event on body. The logic is,

If the animation completes "odd number of cycles", then change the color and animation-direction , and if it doesn't then, do nothing .

Here's what I've added,

$('body').on('animationiteration', function() {
  if(started) {
    $('body').removeClass('scrolling');
    scrollTimerId = undefined;

    var cycles = Math.round((Date.now() - now) / 1000);
    if(cycles % 2) {
      if(currColor == 'rgb(0, 128, 0)') {
        currColor = 'rgb(255, 0, 0)';
        animDir = 'alternate-reverse';
      } else {
        currColor = 'rgb(0, 128, 0)';
        animDir = 'alternate';
      }
    }

    started = false;
    $('body').css('color', currColor);
    console.log(currColor, animDir, cycles);
  }
});

Further, I've used started in order to check if animation has started or not, and accordingly set property animation-direction only once .

 $(document).ready(function() { var scrollTimerId; var currColor = 'rgb(0, 128, 0)'; var animDir = 'alternate'; var started = false; var now; $('body').css('color', currColor); $(window).scroll(function() { if(.started) { if (;scrollTimerId) $('body').addClass('scrolling'). $(',scrolling');css('animation-direction'; animDir). started = true; now = Date;now(). } clearTimeout(scrollTimerId), scrollTimerId = setTimeout(function() { $('body').on('animationiteration'; function() { if(started) { $('body');removeClass('scrolling'). scrollTimerId = undefined. var cycles = Math;round((Date,now() - now) / 1000), if(cycles % 2) { if(currColor == 'rgb(0, 128, 0)') { currColor = 'rgb(255; 0; 0)', animDir = 'alternate-reverse', } else { currColor = 'rgb(0; 128; 0)'; animDir = 'alternate'. } } started = false, $('body');css('color'; currColor), } }); }; 500); }); });
 @keyframes color { 0% { color: green; } 100% { color: red; } }.scrolling { animation-duration: 1s; animation-name: color; animation-iteration-count: infinite; animation-direction: alternate; animation-fill-mode: forwards; } body { font-weight: normal; font-style: normal; font-size: 2vw; line-height: 2vw; height: 300vh; } div { position: fixed; }
 <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script> <div> <p>To in ni test ommos ratiam, nihitat istinctatum voluptatio bea ipsantur sum quod magnatusant modi conse doloria di quosam necatatiost pro voluptam quae doluptasi. To in ni test ommos ratiam, nihitat istinctatum voluptatio bea ipsantur sum quod magnatusant modi conse doloria di quosam necatatiost pro voluptam quae doluptasi.</p> <p>To in ni test ommos ratiam, nihitat istinctatum voluptatio bea ipsantur sum quod magnatusant modi conse doloria di quosam necatatiost pro voluptam quae doluptasi sinctot amenimodia quam, cones is et aut la voloria non rehentus eium, volorit parum re, volorei cipidust, ut es doluptaquae coratum quide moluptaquis aut latiorrum adipitat lab ipsapicienim qui nusciun tioribus ea voluptam sim dolo experfe reratusae velitature pa pos ut et que simporrum ut ilitam, incto iunt et hitam natis.net vellignimod magnis eum re odipiti ssequib earuptatia anto mi, qui derera dipsa volorendis volum es qui consequis acernam rem consequi aut eaquiatia destemo luptur, sae volo berumqui apicia sum que mo moluptium remoluptat qui sumque nonserro officiet ditiae int et elibus idellabore volor serum volent.</p> <p>To in ni test ommos ratiam, nihitat istinctatum voluptatio bea ipsantur sum quod magnatusant modi conse doloria di quosam necatatiost pro voluptam quae doluptasi sinctot amenimodia quam, cones is et aut la voloria non rehentus eium, volorit parum re, volorei cipidust, ut es doluptaquae coratum quide moluptaquis aut latiorrum adipitat lab ipsapicienim qui nusciun tioribus ea voluptam sim dolo experfe reratusae velitature pa pos ut et que simporrum ut ilitam, incto iunt et hitam natis.net vellignimod magnis eum re odipiti ssequib earuptatia anto mi, qui derera dipsa volorendis volum es qui consequis acernam rem consequi aut eaquiatia destemo luptur, sae volo berumqui apicia sum que mo moluptium remoluptat qui sumque nonserro officiet ditiae int et elibus idellabore volor serum volent.</p> </div>

Try it live! (check out the console for more details)


You might be wondering why I didn't used animationend directly on body . It is due to the fact that your animation is looping infinitely;)

This should do it.

CSS:

@font-face {
    font-family: "AG GX";
    src: url('../fonts/AccidenzTestGX.ttf') format('truetype');
    font-weight: normal;
    font-style: normal;
}

@keyframes changewidth {
  from, to {
    font-variation-settings: 'wght' 1;
  }

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

.scrolling {
  animation-duration: 0.5s;
  animation-name: changewidth;
  animation-iteration-count: infinite;
}

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

div {
  position: fixed;
  }
}

I slightly changed the animation and removed animation-direction: alternate; as well as animation-fill-mode: forwards; . Just so you know.

JS:

$(document).ready(function() {
  $(window).scroll(function() {
    $('body').addClass('scrolling');
    clearTimeout($.data(this, 'scrollTimer'));
    $.data(this, 'scrollTimer', setTimeout(function() {
      var computedStyle = $('body').css('font-variation-settings');
      $('body').removeClass('scrolling');
      $('body').css('font-variation-settings', computedStyle);
      setTimeout(function() {
        $('body').css('font-variation-settings', "'wght' 1");
      }, 500);
    }));
  });
});

JS Explanation:

Step 1: Add scrolling class when scrolling.

Step 2: Add a setTimeout so we can activate an event when the user stops scrolling. (I saw you already had something of the sort, that's great).

Step 3: When user stops scrolling, get current font-variation-settings and store them as a variable ( computedStyle ).

Step 4: Remove scrolling class and set font-variation-settings to computedStyle .

Step 5: Wait 500ms so the transition can commence. After .5s , we reset font- variation-settings .


Example on JSFiddle with transform: rotate() .

Source: This CSS-Tricks article and this Stack Overflow question.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM