具有多个转换的transitionEnd事件,检测最后一次转换

时间:2016-11-10 15:15:57

标签: javascript dom

transitionEnd事件会在结束第一次但不是最后一次的转换时触发,这不是所需的行为。任何解决方法?



document.querySelector('a').addEventListener('transitionend', function(){
  var time = (new Date().getMinutes()) + ':' + (new Date().getSeconds());
  console.log('transitionEnd - ', time);
});

a{
  display:block;
  opacity:.5;
  width:100px;
  height:50px;
  background:lightblue;
}
a:hover{
  width:200px;
  height:100px;
  background:red;
  transition: 4s width,     /* <-- "transitionEnd" should fire after this */
              2s height,
              .5s background;  
}
&#13;
<a>Hover me</a>
&#13;
&#13;
&#13;

现在我检查Chrome(我不使用该浏览器),我看到该事件被调用3次,每次转换一次。无论如何,我需要它来解决最后一个,并在Firefox中。 (无论如何我都不知道元素上有多少转换,知道哪个转换是最后一个)

4 个答案:

答案 0 :(得分:3)

transitionEnd在事件对象中返回名为propertyName的属性。 Source

因此,您可以测试所需的属性,并使用简单的逻辑来过滤回调:

document.querySelector('a').addEventListener('transitionend', function(event){
    if(event.propertyName !== 'width') return;
    console.log('transitionEnd - width!');
});

答案 1 :(得分:2)

一些hacky解决方案可能是试图找出哪个css属性的总持续时间最长。您可以在window.getComputedStyle元素上使用<a>并添加所有durationdelay属性来执行此操作。

你可以在三次触发的常规事件处理程序中执行此操作(它非常快),或者创建一个预先计算您正在寻找的属性名称的函数。

这种方法存在的主要问题:

  • Css允许您在一个语句中使用mss,您可能需要进行更多计算。
  • 当您在悬停时更改transition样式或在转换之前/之后添加新类时,很难预测计算样式是什么。

&#13;
&#13;
var getValues = function(str) {
  return str
    .replace(/[A-Z]/gi, "")
    .split(", ")
    .map(parseFloat);
};

var getMaxTransitionProp = function(el) {
  var style = window.getComputedStyle(el);
  var props = style.transitionProperty.split(", ");

  var delays = getValues(style.transitionDelay);
  var durations = getValues(style.transitionDuration);
  var totals = durations.map(function(v, i) {
    return v + delays[i];
  });

  var maxIndex = totals.reduce(function(res, cur, i) {
    if (res.val > cur) {
      res.val = cur;
      res.i = i;
    }
    return res;
  }, {
    val: -Infinity,
    i: 0
  }).i;

  return props[maxIndex];
}

var lastEventListenerFor = function(el, cb) {
  var lastProp = getMaxTransitionProp(el);
  return function(e) {
    if (e.propertyName == lastProp) {
      cb(e);
    }
  };
}

var a = document.querySelector("a");
var cb = function(e) {
  console.log("End");
};

a.addEventListener("transitionend", lastEventListenerFor(a, cb));
&#13;
a {
  display: block;
  opacity: .5;
  width: 100px;
  height: 50px;
  background: lightblue;
  transition: 3s width,
  /* <-- "transitionEnd" should fire after this */
  2s height, .5s background;
}
a:hover {
  width: 200px;
  height: 100px;
  background: red;
}
&#13;
<a>Hover me</a>
&#13;
&#13;
&#13;

答案 2 :(得分:1)

我现在可能是老问题,但遇到同样的麻烦。 以这种方式解决:

document.querySelector('a').addEventListener('click', function(e){
  this.classList.toggle('animate');
  let style = window.getComputedStyle(this, null);
  Promise.all(style.transition.split(',').map((prop) => {
     prop = prop.trim().split(/\s+/);
     return Promise.race([
         new Promise((resolve) => {
              let h = (e) => {
                   if (e.propertyName == prop[0])
                        resolve('transitionEnd ' + prop[0]);
              };
              this.addEventListener('transitionend', h, {once:false});
         }),
         new Promise((resolve) => 
              setTimeout(
                  () => resolve('TIMEOUT ' + prop[0]),
                  prop[1].replace(/s/,'')*1000 + 100
              )
         )
     ])
  }))
  .then((res) => {
     console.log(res + 'DONE!!!!');
     /*  do your stuff */
  });
});

说明:

  • 首先我们确定实际应该更改哪些属性 拆分/映射/转换它以执行操作。通常它的格式如下:“宽度4s缓和0s,高度2s缓解0s,..”
  • 接下来我们使用Promise.race,因为并不总是会触发transitionEnd(两个状态的prop是相同的,或者prop名称在规则和事件中不匹配(background可能是背景颜色),以及其他)我们需要回退到简单的超时
  • 最后我们等待每个属性的所有timeout / eventListeners

免责声明:

  • 这个例子我承认它使用了很多魔法,并且错过了很多检查
  • 如果动画被取消(超时将会终止),
  • “回调”将被解雇
  • 需要ES6:)

你可以在jsfiddle

上玩它

答案 3 :(得分:0)

我以更简单的方式解决了这个问题(在我看来)。就我而言,我正在为 svg 的路径(剪切路径)设置动画。所以我在监听 cilpPath 元素 transitionEnd 事件。每条路径都以不同的时间动画,但是(我的情况只有一个属性)。

解决方案:

计算所有 transitionStart 和存储元素在集合中。 (如果是一个具有许多转换的元素,我相信可以存储 propertyName 或某些 id: element-property)。然后在 transtionEnd 事件上从当前运行的转换集中删除元素/属性。如果 set 为空,则所有转换都已结束。

(function() {
    window.addEventListener("load", () => {
        const animated = document.querySelectorAll("[data-animation]") || [];
        [...animated].forEach(target => {
            animate(target as HTMLElement);
        })
    })


    function animate(target: HTMLElement){
        let currentFrame = 0;
        let frameLimit = parseInt(target.getAttribute("data-animation-frames") || "0");

        const setFrame = (frame: number) => {
            currentFrame = frame;
            target.setAttribute("data-animation-frame", frame.toString());
        }

        const runningTransitions = new Set<HTMLElement>();

        target.addEventListener("transitionend", function transitionEnd(e){
            runningTransitions.delete(e.target as HTMLElement);
            if(runningTransitions.size)
                return;
            const nextFrame = currentFrame + 1;
            if(nextFrame > frameLimit){
                target.removeEventListener("transitionend", transitionEnd);
                return;
            }
            setFrame(nextFrame);
        })

        target.addEventListener("transitionstart", function transitionStart(e){
            runningTransitions.add(e.target as HTMLElement)
        })

        setFrame(currentFrame);
    }
})()