Событие" 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/