两个小技巧,使css的animation动画更好地开始和结束

5,161 阅读2分钟

问题

需要实现一个下载按钮的动画,要求在鼠标进入容器元素(container的mouseenter事件)时触发弹跳动画(使用的是tailwind的预设动画animate-bounce),在离开时结束动画。具体实现时遇到了一个尴尬的问题:动画初始帧和结束帧与默认css不一致时,进入和退出动画都会顿一下,效果如下。

1.gif

需求和解决方案

现在要求是鼠标进出容器时动画都流畅地显示,这里先给出tailwindanimate-bounce的默认实现。

animation: bounce 1s infinite;

@keyframes bounce {
  0%, 100% {
    transform: translateY(-25%);
    animationTimingFunction: cubic-bezier(0.8, 0, 1, 1);
  }
  50% {
    transform: translateY(0);
    animationTimingFunction: cubic-bezier(0, 0, 0.2, 1);
  }
}

解决开始动画问题

1. 重写关键帧和过渡函数

首先想到的是自己实现一个animate-bounce,使得动画在0% 100%的时候,transform的值为translateY(0),除此外要满足原来的动画效果(即动画下部分节奏较快),还需要调整贝塞尔曲线,这无疑是比较麻烦的,所以这里就不实现了。

2. animation-delay

动画延迟一般用于实现一些重复效果的序列动画。如:

delay.gif 源代码codepen.io

易看出这个动画是由一个个带有正值animation-delay的小圆组成,那么如果animation-delay设置为负值会怎么样呢?

给原来的弹跳动画增加一个负的延迟,则动画提前执行,初始帧将从后面开始。

animation-delay: -0.5s 
/* 0.5s恰好为animation-duration的一半 */

效果:

2.gif

动画初始帧虽然正常了,但是鼠标移出时动画戛然而止,同样也很突兀。

解决结束动画问题

关键点在于移出类名animate-bounce的时机,animation提供了一个animationiteration的事件回调,在完整走完一次动画时会触发这个事件。借助它可以在恰当的时机结束动画。

// 父容器绑定鼠标离开事件
container.addEventListener("mounseleave", () => {
    animateTarget.addEventlistener("animationiteration", () => {
        // 因为tailwind这个动画的结束在translateY(-25%)的位置
        // 为了满足结束时translateY为0,需要延迟500ms
        setTimeout(() => {
            animateTarget.classList.remove("animate-bounce");
        }, 500);
    }, { once: true })
})

此时无论何时鼠标进入或者离开容器,都能流畅的控制动画效果。 效果:

3.gif

总结

  1. 进入动画:使用负值animation-delay对齐初始帧。
  2. 退出动画:监听animationiteration事件,使动画完整走完一个流程再结束。 完整代码: stackblitz

首次写文,如果觉得有用,麻烦点个赞吧。