1

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!

R-G
  • 105
  • 2
  • 14
  • *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.* Animate it how? *I can make the animation work*. I opened the link and it's not animating when I'm scrolling. – Richard Apr 18 '20 at 10:28

3 Answers3

2

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

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Thanks @Kaiido You're answer is exactly what I was hoping for. It works in Chrome, however in Safari it still snaps back to the beginning instead of finishing the rotation smoothly. Any ideas here? If this is working on Safari too then i'm happy to mark your answer as correct. I've put your code here to check live: http://slug.directory/GX1/ – R-G Apr 21 '20 at 15:07
  • @R-J you are right, I didn't tested in Safari, and they act very weirdly wrt to transitions and animations (not far from being completely broken on their end). Anyway, I added a fix for Safari. – Kaiido Apr 22 '20 at 04:57
  • Bingo! nailed it! working well in Safari now too. thank you! Bounty is yours :-) + could you also explain what you mean by: '(type $(window).off('scroll')' in your js console before applying these changes if you wish to try it from OP's website). – R-G Apr 22 '20 at 06:50
  • It's meant for readers that want to apply it on your web-site from their browser's dev-tools. They have to remove your own handler before applying this new one. – Kaiido Apr 22 '20 at 07:08
0

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

vrintle
  • 5,501
  • 2
  • 16
  • 46
  • @R-J: I've removed `lastScroll` to make it bit simple. Any feedback? – vrintle Apr 18 '20 at 11:13
  • I like your thinking here, however I don't think it works in practice. I've put your answer live here to check: http://slug.directory/GX3/ Did I implement it correctly, as it's not spinning anymore? thanks – R-G Apr 21 '20 at 15:10
  • @R-J: I can't debug the script on the website (I tried console logs, but it didn't worked). So, would you like to share your font so that I can debug it locally? It would be helpful. – vrintle Apr 21 '20 at 15:25
  • Sure, the font is there already if you look in the you (c) know (s) what (s). Looking forward to the result :-) thanks! – R-G Apr 21 '20 at 19:09
  • @R-J: I didn't understood your first sentence's end. I was saying that you'd given a relative path to your font `url('../fonts/AccidenzTestGX.ttf')`, and hence I can never access your font. How do you say that it is there already? – vrintle Apr 22 '20 at 02:34
0

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.

benhatsor
  • 1,863
  • 6
  • 20
  • Hi all, many thanks for your contributions–more than I expected. I'm looking into each thoroughly to test which works best for the site. More soon, thank you! – R-G Apr 21 '20 at 13:02
  • thanks for the answer, however it still snaps back to the position. Any thoughts? I've put your version here if you want to check what I mean: http://slug.directory/GX2/ – R-G Apr 21 '20 at 15:09