A “transitionend” event that always fires, and once only

不打扰是莪最后的温柔 提交于 2019-12-22 01:28:51

问题


I need a special transitionend-like event that fires once after all transitions are complete, or fires immediately if there are no transitions defined in the CSS.

This what I've come up so far:

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

I've made a live example here.

The only issue is that it only works if the element itself has transition properties, ignoring transitions from children elements. If I switch transitionsComplete to transitionend both the parent and child event handlers are run after the child transition finishes. Is there some way, or perhaps a better approach to determine if an element has transitions happening to it or its children? I'd like to avoid going through the children manually and checking their transitions properties, if possible. (That wouldn't be reliable anyway, because even if some children have transitions, it doesn't mean they would be active at that point)


回答1:


So here you go, indeed i inspect the children: 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:


I've used the treeWalker api to traverse the original node (root), and all child nodes (elements only), filter out elements without transitions, and collect transitions properties to queue (fiddle). As you can see, I've solved the time difference between complete-div and complete-p, and they fire now (almost - couple of ms) at the same time.

There are two caveat, for which I have no workarounds:

  1. If there are transitions that are triggered by different means, for example one is triggered by adding .visible to the div, and the other by adding .invisible, they will all be added to the queue. The event will never fire, as the queue will never empty - I don't have any idea how to solve this.
  2. If there are transitions of shortcut properties (padding for example), multiple transitionend events may be fired, with transition-property, such as padding-top, padding-right, etc... This will cause the array to empty very quickly as splice(-1, 1) removes item from the end of an array. I had a workaround, but that may cause problems, as it might remove other properties in the queue. The best workaround is not to transition on shortcut properties.

The code for the treeWalker is based on Ban Nadel's - Finding HTML Comment Nodes In The DOM Using TreeWalker.

And the code at last:

(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:


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");
}

I'm sure someone will explain why an implementation like this won't work, but maybe it will provide some inspiration / discussion.

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/



来源:https://stackoverflow.com/questions/32469577/a-transitionend-event-that-always-fires-and-once-only

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!