javascript - play - Un evento de "transición" que siempre se dispara, y solo una vez
jquery restart css animation (4)
Necesito un evento de transitionend
especial, como el que se dispara una vez que todas las transiciones se han completado, o se dispara inmediatamente si no hay transiciones definidas en el CSS.
Esto es lo que he subido hasta ahora:
(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);
He hecho un ejemplo vivo here .
El único problema es que solo funciona si el elemento en sí tiene propiedades de transición, ignorando las transiciones de los elementos secundarios. Si cambio transitionsComplete
Completo para hacer una transitionend
los controladores de eventos principal y secundario se ejecutan después de que finalice la transición secundaria. ¿Hay alguna manera, o tal vez un mejor enfoque para determinar si un elemento tiene transiciones que le suceden a él oa sus hijos? Me gustaría evitar pasar por los niños manualmente y verificar sus propiedades de transición, si es posible. (De todos modos, eso no sería confiable, porque incluso si algunos niños tienen transiciones, no significa que estén activos en ese momento)
Así que aquí van, de hecho inspecciono a los niños: 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);
//});
He utilizado la api treeWalker para atravesar el nodo original (raíz) y todos los nodos secundarios (solo elementos), filtrar elementos sin transiciones y recopilar las propiedades de las transiciones para poner en queue
( fiddle ). Como puede ver, he resuelto la diferencia de tiempo entre complete-div
y complete-p
, y se disparan ahora (casi - un par de ms) al mismo tiempo.
Hay dos advertencias, para las cuales no tengo soluciones:
- Si hay transiciones que se activan por diferentes medios, por ejemplo, una se activa agregando
.visible
aldiv
, y la otra agregando.invisible
, todas se agregarán a laqueue
. El evento nunca se activará, ya que laqueue
nunca se vaciará. No tengo idea de cómo resolver esto. - Si hay transiciones de propiedades de acceso directo (por ejemplo,
padding
), se puedentransitionend
múltiples eventos detransitionend
, contransition-property
, comopadding-top
,padding-right
, etc. Esto provocará que la matriz se vacíe muy rápidamente comosplice(-1, 1)
elimina el elemento del final de una matriz. Tuve una workaround , pero eso puede causar problemas, ya que podría eliminar otras propiedades en laqueue
. La mejor solución es no hacer la transición en las propiedades de acceso directo.
El código para el treeWalker se basa en Ban Nadel''s - Búsqueda de nodos de comentarios HTML en el DOM mediante TreeWalker .
Y el código al fin:
(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);
$(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/
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");
}
Estoy seguro de que alguien explicará por qué una implementación como esta no funcionará, pero tal vez proporcione algo de inspiración / discusión.