滚动事件上的@keyframe 动画

@keyframe animation on scroll event

我正在使用可变字体并希望在滚动时使用@keyframes 为其设置动画,然后在用户停止滚动时不设置动画。

我可以让动画正常工作,但是当您停止滚动时,动画会停止并快速回到起始位置,这让它看起来非常跳跃。

为了让它更顺利完成,我想知道是否有一种方法可以在用户停止滚动时获取动画的当前位置,然后完成该动画循环,然后停止,而不是立即回到起始位置?

因为我无法使用@font-face 将变量字体加载到 jsfiddle 中,所以我把它放在这里: http://slug.directory/GX/

这是 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);
    });
});

和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;
}

提前致谢!

你面临的情况很简单,就是如何从动画中的任意点过渡到静止位置。
不幸的是,没有 CSS 定义的方式,所以我们不得不求助于 javascript 来处理它。

基本思路是手动触发该转换。 getComputedStyle 可以为您提供动画当前所在的值,因此我们可以将其设置在元素的内联样式上,然后在强制回流后立即将其删除以触发过渡到原始位置。

不幸的是,Safari 的行为很奇怪,我们也必须切换转换 属性,使此操作强制 3 次同步回流...

这是一个使用移动框的示例,因为它更容易设置为片段:

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>

使用您的代码可以得到:

$(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;
}

(如果您想从 OP 的网站上尝试,请在应用这些更改之前在您的 js 控制台中键入 $(window).off('scroll'))。

我无法访问您的字体,所以我使用 css 属性 color 代替!

为了跟踪动画 end-point,我在 body 上使用了 animationiteration 事件。逻辑是,

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

这是我添加的,

$('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);
  }
});

此外,我使用started来检查动画是否已经开始,并相应地设置属性 animation-direction 仅一次.

$(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!(查看控制台了解更多详情)


您可能想知道为什么我不直接在 body 上使用 animationend。这是因为您的动画无限循环 ;)

这应该可以做到。

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

我稍微更改了动画并删除了 animation-direction: alternate;animation-fill-mode: forwards;。只是让你知道。

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解释:

第一步:滚动时添加scrollingclass。

第 2 步: 添加一个 setTimeout 这样我们就可以在用户停止滚动时激活一个事件。 (我看到你已经有了这样的东西,太好了)。

第 3 步: 当用户停止滚动时,获取当前 font-variation-settings 并将它们存储为变量 (computedStyle)。

第 4 步: 删除 scrolling class 并将 font-variation-settings 设置为 computedStyle

第 5 步:等待 500 毫秒,以便可以开始转换。在 .5s 之后,我们重置 font- variation-settings.


JSFiddletransform: rotate() 的示例。

来源:这个CSS-Tricks article and this Stack Overflow问题。