Событие" transitionend", которое всегда срабатывает и только один раз
мне нужен специальный 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 to transitionend оба родителя и обработчики дочерних событий запускаются после завершения дочернего перехода. Есть ли какой-то способ или, возможно, лучший подход для определения того, происходят ли переходы с элементом или его детьми? Я бы хотел избежать прохождения детей вручную и проверки их свойств переходов, если это возможно. (Это все равно не было бы надежным, потому что даже если у некоторых детей есть переходы, это не означает, что они будут активны в этот момент)
4 ответов
Так вот, я действительно осматривают детей: 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);
//});
я использовал api treeWalker чтобы пройти исходный узел (корень) и все дочерние узлы (только элементы), отфильтровать элементы без переходов и собрать свойства переходов в queue (скрипка). Как вы можете видеть, я решил разницу во времени между complete-div и complete-p, и они стреляют сейчас (почти - пара МС) одновременно.
есть два предостережения, для которых у меня нет обходных путей:
- если есть переходы, которые запускаются различными способами, для
пример один запускается добавлением
.visibleдоdiv, и другие, добавив.invisible, они все будут добавлены кqueue. Событие никогда не будет стрелять, какqueueникогда не будет пустым - я понятия не имею, как это решить. - если есть переходы свойств ярлыка (
paddingдля пример), несколькоtransitionendсобытия могут быть уволены, сtransition-property, напримерpadding-top,padding-right, etc... Этот будет заставьте массив пустеть очень быстро, какsplice(-1, 1)удаляет элемент из конца массива. У меня был решение, но это может вызвать проблемы, так как это может удалить другие свойства вqueue. Лучший способ не переходить на ярлык свойства.
код для treeWalker базируется на запрет Надэль по - поиск узлов комментариев HTML в DOM с помощью 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);
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");
}
Я уверен, что кто-то объяснит, почему такая реализация не будет работать, но, возможно, это даст некоторое вдохновение / обсуждение.
$(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/