最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - A "transitionend" event that always fires, and once only - Stack Overflow

programmeradmin8浏览0评论

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

This what I've e up so far:

(function($){

  $.event.special.transitionsComplete = {

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

      // only count properties with duration higher than 0s
      for(var i = 0; i < putedDurations.length; i++)
        if(putedDurations[i] !== '0s')
          queue.push(putedProps[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)

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

This what I've e up so far:

(function($){

  $.event.special.transitionsComplete = {

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

      // only count properties with duration higher than 0s
      for(var i = 0; i < putedDurations.length; i++)
        if(putedDurations[i] !== '0s')
          queue.push(putedProps[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)

Share Improve this question edited May 11, 2021 at 20:22 Brian Tompsett - 汤莱恩 5,89372 gold badges61 silver badges133 bronze badges asked Sep 9, 2015 at 1:30 nice assnice ass 16.7k8 gold badges52 silver badges92 bronze badges 7
  • Can you edit the CSS for the child elements that transition? – David Murdoch Commented Sep 15, 2015 at 13:35
  • But the transition is already on the child in the example – nice ass Commented Sep 16, 2015 at 9:06
  • If you are able to edit the CSS itself I think I can help you e up with a cleaner way. Let me know – David Murdoch Commented Sep 16, 2015 at 10:39
  • From your question, I wonder if css transitions are best for what you have in mind. you will have better control and likely better performance with JavaScript transitions instead. look at greensock.js. – CodeToad Commented Sep 16, 2015 at 14:06
  • @niceass, can you edit the CSS for the child elements that transition? – David Murdoch Commented Sep 17, 2015 at 15:54
 |  Show 2 more ments

4 Answers 4

Reset to default 4 +400

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 plete-div and plete-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 putedDuration = window.getComputedStyle(node, null)
                    .getPropertyValue(TRANSITION_DURATION);

                return putedDuration === '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 putedProps = style.getPropertyValue(TRANSITION_PROPERTY).split(', ');
                var putedDurations = style.getPropertyValue(TRANSITION_DURATION).split(', ');

                /** push all props with duration which is not 0s **/
                putedDurations.forEach(function (duration, index) {
                    duration !== '0s' && queue.push(putedProps[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);

So here you go, indeed i inspect the children: http://jsfiddle/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('plete-div', (new Date().getTime() - start) / 1000);
});

//p.one('transitionsComplete', function(e){
//    console.log('plete-p', (new Date().getTime() - start) / 1000);
//});
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/nf8gvbuo/16/

$(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./ajax/libs/jquery/1.11.1/jquery.min.js"></script>

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

jsfiddle http://jsfiddle/nf8gvbuo/1/

发布评论

评论列表(0)

  1. 暂无评论