一个总是触发的“过渡”事件,只有一次

我需要一个特殊的transitionend like事件,在所有转换完成后触发一次,或者如果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  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); 

我在这里做了一个实例。

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

所以,你去,确实我检查了孩子们: 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); //}); 

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

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

  1. 如果存在由不同方式触发的转换,例如通过向div添加.visible来触发一个转换,而通过添加.visible来触发另一个.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); 
 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/

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