¿Es posible escuchar un evento de "cambio de estilo"?

206

¿Es posible crear un detector de eventos en jQuery que pueda vincularse a cualquier cambio de estilo? Por ejemplo, si quiero "hacer" algo cuando un elemento cambia de dimensión, o cualquier otro cambio en el atributo de estilo que podría hacer:

$('div').bind('style', function() {
    console.log($(this).css('height'));
});

$('div').height(100); // yields '100'

Sería realmente útil

¿Algunas ideas?

ACTUALIZAR

Perdón por responder esto yo mismo, pero escribí una solución ordenada que podría adaptarse a otra persona:

(function() {
    var ev = new $.Event('style'),
        orig = $.fn.css;
    $.fn.css = function() {
        $(this).trigger(ev);
        return orig.apply(this, arguments);
    }
})();

Esto anulará temporalmente el método interno prototype.css y lo redefinirá con un disparador al final. Entonces funciona así:

$('p').bind('style', function(e) {
    console.log( $(this).attr('style') );
});

$('p').width(100);
$('p').css('color','red');
David Hellsing
fuente
sí, buena solución, pero en una aplicación real, sospecho que esto puede requerir muchos recursos e incluso hacer que el navegador parezca lento para mantenerse al día con las interacciones del usuario. Me encantaría saber si ese es el caso o no.
pixeline
1
@pixeline: No puedo ver que debería ser mucho más lento. jQuery todavía llama al prototipo css de todos modos, solo le agrego un disparador. Entonces, básicamente es solo una llamada de método adicional.
David Hellsing
Entiendo eso: solo parece que en el flujo de un usuario que interactúa con una aplicación, css () se llama mucho tiempo. Dependiendo de lo que hagas con ese disparador (si es solo un registro de consola, supongo que estás a salvo, por supuesto), esto podría crear problemas. Sólo me pregunto. Depende de la aplicación también. Personalmente, tiendo a hacer muchos comentarios visuales en mis cosas.
pixeline
Hay un par de cosas más que hacer para que funcione con las llamadas jquery css existentes: 1) desea devolver el elemento de su nueva función css o de lo contrario se romperán las llamadas css de estilo fluido. p.ej. $ ('p'). css ('display', 'block'). css ('color', 'black'); etc. 2) si se trata de una llamada para un valor de propiedad (por ejemplo, 'obj.css (' height ');'), querrá devolver el valor de retorno de la función original; lo hago comprobando si la lista de argumentos para la función solo contiene un argumento.
theringostarrs
1
Hice una versión más robusta de este enfoque que también funciona cuando se elimina el atributo de estilo en sí . Lanzará un evento de 'estilo' para cada estilo que se elimine de esta manera. gist.github.com/bbottema/426810c21ae6174148d4
Benny Bottema

Respuestas:

19

Dado que jQuery es de código abierto, supongo que podría ajustar la cssfunción para llamar a una función de su elección cada vez que se invoca (pasando el objeto jQuery). Por supuesto, querrá explorar el código jQuery para asegurarse de que no haya nada más que use internamente para establecer las propiedades CSS. Idealmente, desearía escribir un complemento separado para jQuery para que no interfiera con la biblioteca jQuery, pero tendrá que decidir si eso es factible o no para su proyecto.

Josh Stodola
fuente
1
Pero en este caso, ¿qué pasaría si configuro la propiedad de estilo directamente con las funciones DOM?
Jaime Hablutzel
Aquí hay un ejemplo completo de esta solución: gist.github.com/bbottema/426810c21ae6174148d4
Benny Bottema
272

Las cosas han avanzado un poco desde que se hizo la pregunta: ahora es posible usar un MutationObserver para detectar cambios en el atributo 'style' de un elemento, no se requiere jQuery:

var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutationRecord) {
        console.log('style changed!');
    });    
});

var target = document.getElementById('myId');
observer.observe(target, { attributes : true, attributeFilter : ['style'] });

El argumento que se pasa a la función de devolución de llamada es un objeto MutationRecord que le permite obtener los valores de estilo antiguos y nuevos.

El soporte es bueno en los navegadores modernos, incluido IE 11+.

caja de código
fuente
19
Tenga en cuenta que esto solo funciona con estilos en línea, no cuando el estilo cambia como consecuencia de un cambio de clase o @mediacambio.
Fregante
2
@ bfred.it ¿Tendrías una idea de cómo lograr eso? Quiero ejecutar algunos js después de que las reglas CSS de una clase se hayan aplicado a un elemento.
Kragalon
Puede usar getComputedStylecon, setIntervalpero eso ralentizaría mucho su sitio web.
fregante
Nota: MutationObserver en realidad no funciona en todas las versiones de Android 4.4, a pesar de lo que dice caniuse.
James Gould
Vea este twiddle para ver un ejemplo de cómo cambiar el color de fondo de un área de texto a azul cuando el ancho del área de texto se extiende más allá de 300px. jsfiddle.net/RyanNerd/y2q8wuf0 (desafortunadamente parece haber un error en FF que cuelga el script enHTMLElement.style.prop = "value"
RyanNerd
18

La declaración de su objeto de evento debe estar dentro de su nueva función css. De lo contrario, el evento solo se puede disparar una vez.

(function() {
    orig = $.fn.css;
    $.fn.css = function() {
        var ev = new $.Event('style');
        orig.apply(this, arguments);
        $(this).trigger(ev);
    }
})();
Mike Allen
fuente
Genial, gracias. Algunas personas dicen que cambian la fuente original, pero su extensión es la mejor. Tuve algunos problemas pero finalmente funciona.
ccsakuweb
Se dispara continuamente, después de ejecutar este código, y ni siquiera logra el resultado requerido.
smkrn110
13

Creo que la mejor respuesta es de Mike en el caso de que no pueda iniciar su evento porque no es de su código. Pero recibo algunos errores cuando lo uso. Entonces escribo una nueva respuesta para mostrarle el código que uso.

Extensión

// Extends functionality of ".css()"
// This could be renamed if you'd like (i.e. "$.fn.cssWithListener = func ...")
(function() {
    orig = $.fn.css;
    $.fn.css = function() {
        var result = orig.apply(this, arguments);
        $(this).trigger('stylechanged');
        return result;
    }
})();

Uso

// Add listener
$('element').on('stylechanged', function () {
    console.log('css changed');
});

// Perform change
$('element').css('background', 'red');

Obtuve un error porque var ev = new $ .Event ('style'); Algo parecido al estilo no se definió en HtmlDiv. Lo eliminé y ahora ejecuto $ (this) .trigger ("stylechanged"). Otro problema fue que Mike no devolvió el resultado de $ (css, ..), entonces puede causar problemas en algunos casos. Entonces obtengo el resultado y lo devuelvo. Ahora funciona ^^ En cada cambio de CSS incluye algunas bibliotecas que no puedo modificar y desencadenar un evento.

ccsakuweb
fuente
Muy servicial e inteligente
dgo
5

Como otros han sugerido, si tiene control sobre cualquier código que esté cambiando el estilo del elemento, puede activar un evento personalizado cuando cambie la altura del elemento:

$('#blah').bind('height-changed',function(){...});
...
$('#blah').css({height:'100px'});
$('#blah').trigger('height-changed');

De lo contrario, aunque requiere muchos recursos, puede configurar un temporizador para verificar periódicamente los cambios en la altura del elemento ...

droo
fuente
De hecho, esta es una solución muy ordenada si uno tiene acceso al código que está cambiando los estilos.
Umair Hafeez
2

No hay soporte incorporado para el evento de cambio de estilo en jQuery o en el script java. Pero jQuery admite crear eventos personalizados y escucharlos, pero cada vez que hay un cambio, debe tener una forma de activarlo usted mismo. Por lo tanto, no será una solución completa.

Teja Kantamneni
fuente
2

puede probar el complemento Jquery, desencadena eventos cuando css cambia y es fácil de usar

http://meetselva.github.io/#gist-section-attrchangeExtension
 $([selector]).attrchange({
  trackValues: true, 
  callback: function (e) {
    //console.log( '<p>Attribute <b>' + e.attributeName +'</b> changed from <b>' + e.oldValue +'</b> to <b>' + e.newValue +'</b></p>');
    //event.attributeName - Attribute Name
    //event.oldValue - Prev Value
    //event.newValue - New Value
  }
});
usuario889030
fuente
1

Interesante pregunta. El problema es que height () no acepta una devolución de llamada, por lo que no podrá activar una devolución de llamada. Use animate () o css () para establecer la altura y luego active el evento personalizado en la devolución de llamada. Aquí hay un ejemplo usando animate (), probado y funciona ( demo ), como prueba de concepto:

$('#test').bind('style', function() {
    alert($(this).css('height'));
});

$('#test').animate({height: 100},function(){
$(this).trigger('style');
}); 
pixeline
fuente
8
Lo siento, pero ¿qué demostró esto? Usted trigger()el evento de forma manual. Creo que el OP está después de algún evento autoactivado cuando se modifica un atributo de estilo.
JPot
@JPot muestra que el atributo de altura no genera eventos cuando se cambia.
AnthonyJClink
1

Simplemente agregando y formalizando la solución de @David desde arriba:

Tenga en cuenta que las funciones jQuery son encadenables y devuelven 'esto' para que se puedan invocar varias invocaciones una tras otra (por ejemplo $container.css("overflow", "hidden").css("outline", 0);).

Entonces el código mejorado debería ser:

(function() {
    var ev = new $.Event('style'),
        orig = $.fn.css;
    $.fn.css = function() {
        var ret = orig.apply(this, arguments);
        $(this).trigger(ev);
        return ret; // must include this
    }
})();
Nir Hemed
fuente
0

¿Qué tal jQuery cssHooks?

Tal vez no entiendo la pregunta, pero lo que está buscando se hace fácilmente cssHooks, sin cambiarcss() función.

copia de la documentación:

(function( $ ) {

// First, check to see if cssHooks are supported
if ( !$.cssHooks ) {
  // If not, output an error message
  throw( new Error( "jQuery 1.4.3 or above is required for this plugin to work" ) );
}

// Wrap in a document ready call, because jQuery writes
// cssHooks at this time and will blow away your functions
// if they exist.
$(function () {
  $.cssHooks[ "someCSSProp" ] = {
    get: function( elem, computed, extra ) {
      // Handle getting the CSS property
    },
    set: function( elem, value ) {
      // Handle setting the CSS value
    }
  };
});

})( jQuery ); 

https://api.jquery.com/jQuery.cssHooks/

medio bit
fuente
-1

Tuve el mismo problema, así que escribí esto. Funciona bastante bien. Se ve muy bien si lo mezclas con algunas transiciones CSS.

function toggle_visibility(id) {
   var e = document.getElementById("mjwelcome");
   if(e.style.height == '')
      e.style.height = '0px';
   else
      e.style.height = '';
}
KingLouie
fuente