一个总是触发且仅触发一次的“transitionend”事件

19

我需要一个类似于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);

我在这里创建了一个实时示例。

唯一的问题是,它仅在元素本身具有过渡属性时起作用,忽略来自子元素的过渡。如果我将transitionsComplete切换到transitionend,则在子过渡完成后,父级和子级事件处理程序都会运行。是否有某种方法或更好的方法来确定元素或其子项是否存在过渡?如果可能,我想避免手动查看其过渡属性并检查其子代。 (无论如何,这也不可靠,因为即使某些子元素有过渡效果,也不意味着它们在那一点上是活动的)


你能否编辑过渡的子元素的CSS? - David Murdoch
但在这个示例中,过渡已经在子元素上进行。 - nice ass
@niceass,你能编辑一下过渡效果的子元素CSS吗? - David Murdoch
我不确定我理解你的问题。过渡效果是在样式表中定义的(或者没有定义),所以它们是可编辑的。这段JavaScript代码应该能够通过更改类来触发它们,等待它们完成后再执行其操作,或者如果没有过渡效果,则立即执行其操作。 - nice ass
好的,那么我应该能够为您提供一个可行的答案,而无需遍历整个树结构。 - David Murdoch
显示剩余2条评论
4个回答

4

我使用了treeWalker api来遍历原始节点(根节点)和所有的子节点(仅元素),过滤掉没有转换效果的元素,并收集转换属性到queue中(fiddle)。正如你所看到的,我解决了complete-divcomplete-p之间的时间差异问题,它们现在同时触发(几乎是同时的 - 有几毫秒的差距)。

然而,还有两个注意事项,目前我没有解决方案:

  1. 如果有不同方式触发的转换效果,例如一个是通过将.visible添加到div中触发的,另一个是通过添加.invisible触发的,则它们都会被添加到queue中。事件将永远不会触发,因为queue永远不会清空 - 我不知道如何解决这个问题。
  2. 如果有快捷属性(例如padding)的转换效果,则可能会多次触发transitionend事件,带有transition-property,例如padding-toppadding-right等...这将导致数组非常快地清空,因为splice(-1, 1)会从数组末尾删除项。我有一个解决方法,但是可能会出现问题,因为它可能会从queue中删除其他属性。最好的解决方法是不要在快捷属性上进行转换。

treeWalker的代码基于Ban Nadel的 - 使用TreeWalker在DOM中查找HTML注释节点

最后附上代码:

(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);

3

下面是需要翻译的内容:所以,这里给您看,实际上我检查了子元素: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);
//});

2
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/

没关系。我之所以为一个特殊事件而写代码,是因为这段代码在多个地方被使用,我想避免重复代码。 - nice ass

1

$(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 {{link1}}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接