为了账号安全,请及时绑定邮箱和手机立即绑定

我可以使用 JS 等待多个 CSS 动画吗?

我可以使用 JS 等待多个 CSS 动画吗?

神不在的星期二 2021-10-29 15:03:17
我们有一种方法可以使用 JS 检测动画何时结束:const element = $('#animatable');element.addClass('being-animated').on("animationend", (event) => {  console.log('Animation ended!');});@keyframes animateOpacity {  0% {    opacity: 1;  }  100% {    opacity: 0;  }}@keyframes animatePosition {  0% {    transform: translate3d(0, 0, 0);  }  100% {    transform: translate3d(0, 15px, 0);  }}#animatable.being-animated {  animation: animateOpacity 1s ease 0s forwards, animatePosition 2s ease 0s forwards;}<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script><div id="animatable">I'm probably being animated.</div>正如你所看到的,JS,理所当然地,因为我被这个animationend事件吸引住了,它告诉我“是的,动画完成了”但不知道接下来会发生什么,我错过了第二个。不是有动画队列吗?当然,CSS 必须在系统中的某个地方注册这些东西,然后才能在它们被触发之前达到峰值。
查看完整描述

3 回答

?
守着一只汪

TA贡献1872条经验 获得超3个赞

免责声明:我不认为 jQuery 对回答这个问题很重要,如果其他人在看到这个答案后选择依赖此代码,它会损害负载和运行时性能。因此,我将使用 vanilla JavaScript 来回答,以帮助尽可能多的人,但如果您想使用 jQuery,您仍然可以应用相同的概念。


答:没有动画队列,但您可以自己制作。


例如,您可以使用闭包和/或 a Map(在下面的代码段中,我实际上使用 aWeakMap来帮助垃圾收集)将有关动画的数据链接到目标元素。如果您将动画状态保存为true完成时,您可以检查并最终在全部为 时触发不同的回调true,或者调度您自己的自定义事件。我使用了自定义事件方法,因为它更灵活(能够添加多个回调)。


以下代码还可以帮助您避免在您实际上只关心几个特定动画的情况下等待所有动画。它还应该让您多次处理多个单独元素的动画事件(尝试运行代码段并单击几次框)


const addAnimationEndAllEvent = (() => {

  const weakMap = new WeakMap()


  const initAnimationsObject = (element, expectedAnimations, eventName) => {

    const events = weakMap.get(element)

    const animationsCompleted = {}

    for (const animation of expectedAnimations) {

      animationsCompleted[animation] = false

    }

    events[eventName] = animationsCompleted

  }


  return (element, expectedAnimations, eventName = 'animationendall') => {

    if (!weakMap.has(element)) weakMap.set(element, {})


    if (expectedAnimations) {

      initAnimationsObject(element, expectedAnimations, eventName)

    }


    // When any animation completes...

    element.addEventListener('animationend', ({ target, animationName }) => {

      const events = weakMap.get(target)

      

      // Use all animations, if there were none provided earlier

      if (!events[eventName]) {

        initAnimationsObject(target, window.getComputedStyle(target).animationName.split(', '), eventName)

      }

      

      const animationsCompleted = events[eventName]

      

      // Ensure this animation should be tracked

      if (!(animationName in animationsCompleted)) return


      // Mark the current animation as complete (true)

      animationsCompleted[animationName] = true


      // If every animation is now completed...

      if (Object.values(animationsCompleted).every(

        isCompleted => isCompleted === true

      )) {

        const animations = Object.keys(animationsCompleted)


        // Fire the event

        target.dispatchEvent(new CustomEvent(eventName, {

          detail: { target, animations },

        }))


        // Reset for next time - set all animations to not complete (false)

        initAnimationsObject(target, animations, eventName)

      }

    })

  }

})()


const toggleAnimation = ({ target }) => {

  target.classList.toggle('being-animated')

}


document.querySelectorAll('.animatable').forEach(element => {

  // Wait for all animations before firing the default event "animationendall"

  addAnimationEndAllEvent(element)


  // Wait for the provided animations before firing the event "animationend2"

  addAnimationEndAllEvent(element, [

    'animateOpacity',

    'animatePosition'

  ], 'animationend2')


  // Listen for our added "animationendall" event

  element.addEventListener('animationendall', ({detail: { target, animations }}) => {

    console.log(`Animations: ${animations.join(', ')} - Complete`)

  })


  // Listen for our added "animationend2" event

  element.addEventListener('animationend2', ({detail: { target, animations }}) => {

    console.log(`Animations: ${animations.join(', ')} - Complete`)

  })


  // Just updated this to function on click, so we can test animation multiple times

  element.addEventListener('click', toggleAnimation)

})

.animatable {

  margin: 5px;

  width: 100px;

  height: 100px;

  background: black;

}


@keyframes animateOpacity {

  0% {

    opacity: 1;

  }

  100% {

    opacity: 0;

  }

}


@keyframes animatePosition {

  0% {

    transform: translate3d(0, 0, 0);

  }

  100% {

    transform: translate3d(0, 15px, 0);

  }

}


@keyframes animateRotation {

  100% {

    transform: rotate(360deg);

  }

}


.animatable.being-animated {

  animation:

    animateOpacity 1s ease 0s forwards,

    animatePosition 1.5s ease 0s forwards,

    animateRotation 2s ease 0s forwards;

}

<div class="animatable"></div>

<div class="animatable"></div>


查看完整回答
反对 回复 2021-10-29
?
阿晨1998

TA贡献2037条经验 获得超6个赞

它当然值得成为公认的答案。也就是说,我受到启发,想看看一种不那么冗长的方法是否可行。这是我想出的。


这是不言自明的,但基本上概念是所有动画属性的索引都是相关的,我们可以使用它来查找最后完成的动画的名称。


const getFinalAnimationName = el => {

  const style = window.getComputedStyle(el)

  

  // get the combined duration of all timing properties

  const [durations, iterations, delays] = ['Duration', 'IterationCount', 'Delay']

    .map(prop => style[`animation${prop}`].split(', ')

      .map(val => Number(val.replace(/[^0-9\.]/g, ''))))

  const combinedDurations = durations.map((duration, idx) =>

    duration * iterations[idx] + delays[idx])

  

  // use the index of the longest duration to select the animation name

  const finalAnimationIdx = combinedDurations

    .findIndex(d => d === Math.max(...combinedDurations))

  return style.animationName.split(', ')[finalAnimationIdx]

}


// pipe your element through this function to give it the ability to dispatch the 'animationendall' event

const addAnimationEndAllEvent = el => {

  const animationendall = new CustomEvent('animationendall')

  el.addEventListener('animationend', ({animationName}) =>

    animationName === getFinalAnimationName(el) &&

      el.dispatchEvent(animationendall))

  return el

}


// example usage

const animatable = document.querySelector('.animatable')

addAnimationEndAllEvent(animatable)

  .addEventListener('animationendall', () => console.log('All animations have finished'))

.animatable {

  width: 50px;

  height: 50px;

  background-color: red;

  position: relative;

  left: 0;

  animation: 1.5s slidein, 1s fadein;

}


@keyframes slidein {

  0% { left: 100vw; }

  100% { left: 0; }

}


@keyframes fadein {

  0% { opacity: 0; }

  100% { opacity: 1; }

}

<div class="animatable"></div>


查看完整回答
反对 回复 2021-10-29
  • 3 回答
  • 0 关注
  • 131 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信