一个总是触发的“transitionend”事件,并且只触发一次

Posted

技术标签:

【中文标题】一个总是触发的“transitionend”事件,并且只触发一次【英文标题】:A "transitionend" event that always fires, and once only 【发布时间】:2015-12-04 20:00:51 【问题描述】:

我需要一个类似于transitionend 的特殊事件,它在所有转换完成后触发一次,或者如果 CSS 中没有定义转换则立即触发。

这是我到目前为止提出的:

(function($)

  $.event.special.transitionsComplete = 

    setup: function(data, namespaces, eventHandle)    
      var queue = [],
          style = window.getComputedStyle(this, null),
          computedProps = style.getPropertyValue('transition-property').split(', '),
          computedDurations = style.getPropertyValue('transition-duration').split(', '),
          $node = $(this);          

      // only count properties with duration higher than 0s
      for(var i = 0; i < computedDurations.length; i++)
        if(computedDurations[i] !== '0s')
          queue.push(computedProps[i]);           

      // there are transitions
      if(queue.length > 0)
        $node.on('webkitTransitionEnd.x transitionend.x', function(e)          
          queue.splice(queue.indexOf(e.originalEvent.propertyName));          
          if(queue.length < 1)
            $node.trigger('transitionsComplete');
        );

      // no transitions, fire (almost) immediately
      else
        setTimeout(function()
          $node.trigger('transitionsComplete');
        , 5);

      

    ,

    teardown: function(namespaces)
      $(this).off('.x');
    

  ;
)(jQuery);

我做了一个活生生的例子here。

唯一的问题是它只有在元素本身具有过渡属性时才有效,忽略来自子元素的过渡。如果我将transitionsComplete 切换到transitionend,则父事件处理程序和子事件处理程序都会在子转换完成后运行。是否有某种方法,或者更好的方法来确定一个元素是否发生了转换或它的子元素?如果可能的话,我想避免手动检查孩子并检查他们的转换属性。 (无论如何这都不可靠,因为即使有些孩子有过渡,也不意味着他们会在那个时候活跃)

【问题讨论】:

你能编辑过渡的子元素的 CSS 吗? 但是例子中的过渡已经在孩子身上了 如果您能够编辑 CSS 本身,我想我可以帮助您想出一种更简洁的方法。告诉我 根据您的问题,我想知道 css 转换是否最适合您的想法。相反,您将拥有更好的控制和可能更好的性能 javascript 转换。看看 greensock.js。 @niceass,你能编辑过渡的子元素的 CSS 吗? 【参考方案1】:

我已经使用treeWalker api 遍历原始节点(根)和所有子节点(仅元素),过滤掉没有过渡的元素,并将过渡属性收集到queue (fiddle)。如您所见,我已经解决了complete-divcomplete-p 之间的时间差,它们现在同时触发(几乎-几毫秒)。

有两个警告,我没有解决方法:

    如果存在由不同方式触发的转换,对于 示例一是通过将.visible 添加到div 来触发的,并且 其他通过添加.invisible,它们都将被添加到queue。该事件永远不会触发,因为 queue 永远不会为空 - 我不知道如何解决这个问题。 如果有快捷方式属性的转换(padding for 例如),可能会触发多个transitionend事件,有transition-property,如padding-toppadding-right等……这个 将导致数组很快清空为splice(-1, 1) 从数组的末尾删除项目。我有一个workaround,但是 这可能会导致问题,因为它可能会删除 queue。最好的解决方法是不要在快捷方式上转换 属性。

treeWalker 的代码基于 Ban Nadel 的 - Finding html Comment Nodes In The DOM Using TreeWalker。

最后是代码:

(function ($) 

    $.event.special.transitionsComplete = 

        setup: function (data, namespaces, eventHandle) 
            var TRANSITION_PROPERTY = 'transition-property';
            var TRANSITION_DURATION = 'transition-duration';

            var root = this;
            var queue = [];
            var $node = $(this);

            function filter(node)  // filter for treeWalker
                /*** filters transitions which are a string with one '0s'. If more then '0s' is defined it will be catched when creating the queue ***/
                var computedDuration = window.getComputedStyle(node, null)
                    .getPropertyValue(TRANSITION_DURATION);

                return computedDuration === '0s' ? NodeFilter.FILTER_SKIP : NodeFilter.FILTER_ACCEPT;
            

            filter.acceptNode = filter; // for webkit and firefox

            /** create the treeWalker to traverse only elements **/
            var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, filter, false);

            /** traverse all elements using treeWalker.nextNode(). First node is the root **/
            do 
                var style = window.getComputedStyle(treeWalker.currentNode, null);
                var computedProps = style.getPropertyValue(TRANSITION_PROPERTY).split(', ');
                var computedDurations = style.getPropertyValue(TRANSITION_DURATION).split(', ');

                /** push all props with duration which is not 0s **/
                computedDurations.forEach(function (duration, index) 
                    duration !== '0s' && queue.push(computedProps[index]);
                );
             while (treeWalker.nextNode()); // iterate until no next node

            // no transitions, fire (almost) immediately
            if (queue.length === 0) 

                setTimeout(function () 
                    $node.trigger('transitionsComplete');
                , 5);

                return; // return out of the function to skip the transitions block
            

            // there are transitions
            $node.on('webkitTransitionEnd.x transitionend.x', function (e) 
                var propertyName = e.originalEvent.propertyName;
                var indexOfProp = queue.indexOf(propertyName);

                queue.splice(indexOfProp, 1);

                if (queue.length < 1) 
                    console.log('Transitions Complete');
                    $node.trigger('transitionsComplete');
                
            );

        ,

        teardown: function (namespaces) 
            $(this).off('.x');
        

    ;
)(jQuery);

【讨论】:

【参考方案2】:

所以你来了,我确实在检查孩子们:http://jsfiddle.net/cegejk59/2/

(function($)

  $.event.special.transitionsComplete = 

    setup: function( data, namespaces, eventHandle ) 

        var allTransitions          = [];
            w                       = window,
            TRANSITION_PROPERTY_KEY = 'transition-property',
            TRANSITION_DURATION_KEY = 'transition-duration',
            $node                   = $( this );

        function collectTransitionsRecursively( node ) 

            var style                   = w.getComputedStyle( node ),
                nodeComputedProperties  = style.getPropertyValue( TRANSITION_PROPERTY_KEY ).split( ', ' ),
                nodeComputedDurations   = style.getPropertyValue( TRANSITION_DURATION_KEY ).split( ', ' );

            for( var i = 0; i < nodeComputedDurations.length; i++ )
                if( nodeComputedDurations[ i ] !== '0s' )
                    allTransitions.push( nodeComputedProperties[ i ] );

            for( var childIndex = 0; childIndex < node.children.length; childIndex++ )
                collectTransitionsRecursively( node.children[ childIndex ] );
        

        function triggerTransitionsComplete( $onNode ) 

            console.log( "No transitions (left)." );

            $onNode.trigger('transitionsComplete');
        

        function onNoTransitionsFound() 

            setTimeout( function() 

                triggerTransitionsComplete( $node );
            );
        

        collectTransitionsRecursively( this );

        if( allTransitions.length == 0 )
            return onNoTransitionsFound();
        else
            console.log( 'remaining', allTransitions );    

        $node.on('webkitTransitionEnd.x transitionend.x', function( e ) 

            allTransitions.splice(allTransitions.indexOf(e.originalEvent.propertyName));

            if( allTransitions.length == 0 )
                triggerTransitionsComplete( $node );
            else
                console.log('remaining', allTransitions);
        );
    ,

    teardown: function( namespaces ) 

      $( this ).off( '.x' );
    
  ;
)(jQuery);


var div = $('div'), p = $('p'), start = new Date().getTime();
console.log('-- start --');
div.addClass('visible');

div.one('transitionsComplete', function(e)
    console.log('complete-div', (new Date().getTime() - start) / 1000);
);

//p.one('transitionsComplete', function(e)
//    console.log('complete-p', (new Date().getTime() - start) / 1000);
//);

【讨论】:

【参考方案3】:
if($('div').one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend')) 
  $('div').one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function(e) 
    console.log("Fire after transitions");
  );
 else 
  console.log("Fire immediately if there are no transitions");

我相信有人会解释为什么这样的实现不起作用,但也许它会提供一些灵感/讨论。

https://jsfiddle.net/nf8gvbuo/16/

【讨论】:

没关系。我参加特殊活动的唯一原因是因为它在多个地方使用并希望避免重复代码【参考方案4】:

$(function() 

  var div = $("div"),
    p = $("p"),
    start = new Date().getTime();
  console.log("-- start --");
  div.addClass("visible");

  var n = 0
  , transitions = [];
  div.on(
    "transitionend": function(e) 
      ++n;
      transitions.push(
        "element": e.originalEvent.srcElement,
        "property": e.originalEvent.propertyName,
        "duration": e.originalEvent.elapsedTime
      );
      var container = $(this).css("transition").split(","),
        elems = $(p, this).css("transition").split(",");
      if (container.length === 1 && n === elems.length) 
        $(this).trigger("transitionComplete", [transitions])
      
    ,
    "transitionComplete": function(e, tx) 
      console.log(e.type, this, (new Date().getTime() - start) / 1000, tx);
      alert(e.type);
    
  );

);
p 
  opacity: 0;
  transition: opacity 10s, transform 5s;
  background: red;
  width: 50px;
  height: 50px;
  margin: 100px;

div.visible p 
  opacity: 1;
  transform: scale(1.5);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<div>
  <p></p>
</div>

jsfiddle http://jsfiddle.net/nf8gvbuo/1/

【讨论】:

以上是关于一个总是触发的“transitionend”事件,并且只触发一次的主要内容,如果未能解决你的问题,请参考以下文章

在 Linux 下的 Firefox 15.0.1 中未触发 transitionend 事件

如何在 jQuery 中使用 transitionend?

JS如何监听动画结束

过渡结束事件

transitionend事件 监听 fadeIn fadeOut 两个方法无效(动画结束时无法执行transitionend里面的代码)

transitionend的运用案例