Eventos de disparo en cambios de clase CSS en jQuery

240

¿Cómo puedo activar un evento si se agrega o cambia una clase CSS usando jQuery? ¿El cambio de una clase CSS activa el change()evento jQuery ?

usuario237060
fuente
8
7 años después, con la adopción bastante amplia de MutationObservers en los navegadores modernos, la respuesta aceptada aquí realmente debería actualizarse para ser del Sr. Br .
joelmdev
Esto te puede ayudar. stackoverflow.com/questions/2157963/…
PSR
Para completar la respuesta de Jason, encontré esto: stackoverflow.com/a/19401707/1579667
Benj

Respuestas:

223

Cada vez que cambie una clase en su secuencia de comandos, puede usar a triggerpara generar su propio evento.

$(this).addClass('someClass');
$(mySelector).trigger('cssClassChanged')
....
$(otherSelector).bind('cssClassChanged', data, function(){ do stuff });

pero por lo demás, no, no hay una forma preparada de disparar un evento cuando cambia una clase. change()solo se dispara después de que el foco deja una entrada cuya entrada ha sido alterada.

$(function() {
  var button = $('.clickme')
      , box = $('.box')
  ;
  
  button.on('click', function() { 
    box.removeClass('box');
    $(document).trigger('buttonClick');
  });
            
  $(document).on('buttonClick', function() {
    box.text('Clicked!');
  });
});
.box { background-color: red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div class="box">Hi</div>
<button class="clickme">Click me</button>

Más información sobre jQuery Triggers

Jason
fuente
44
¿De qué trata desencadenar el evento que luego llama a una función? ¿Por qué no llamar a la función directamente, en lugar de activarla?
RamboNo5
43
La activación es un mecanismo que permitirá que ciertos elementos se "suscriban" a un evento. de esa manera, cuando se desencadena el evento, todos esos eventos pueden ocurrir a la vez y ejecutar su funcionalidad respectiva. por ejemplo, si tengo un objeto que cambia de rojo a azul y tres objetos que esperan que cambie, cuando cambia, puedo activar el changeColorevento y todos los objetos que se suscriben a ese evento pueden reaccionar en consecuencia.
Jason
1
Parece un poco extraño que el OP quiera escuchar un evento 'cssClassChanged'; seguramente la clase se agregó como resultado de algún otro evento de 'aplicación' como 'colourChanged' que tendría más sentido anunciar con el disparador ya que no puedo imaginar por qué algo estaría interesado específicamente en un cambio de nombre de clase, es más el resultado de la classNameChange seguramente?
riscarrott
Si fue específicamente un cambio de className lo que fue de interés, entonces imagino que decorar jQuery.fn.addClass para activar 'cssClassChanged' sería más sensato como @micred dijo
riscarrott
55
@riscarrott no presumo conocer la aplicación del OP. Solo les presento el concepto de pub / sub y pueden aplicarlo como quieran. Discutir cuál debería ser el nombre real del evento con la cantidad de información dada es una tontería.
Jason
140

En mi humilde opinión, la mejor solución es combinar dos respuestas de @ RamboNo5 y @Jason

Me refiero a anular la función addClass y agregar un evento personalizado llamado cssClassChanged

// Create a closure
(function(){
    // Your base, I'm in it!
    var originalAddClassMethod = jQuery.fn.addClass;

    jQuery.fn.addClass = function(){
        // Execute the original method.
        var result = originalAddClassMethod.apply( this, arguments );

        // trigger a custom event
        jQuery(this).trigger('cssClassChanged');

        // return the original result
        return result;
    }
})();

// document ready function
$(function(){
    $("#YourExampleElementID").bind('cssClassChanged', function(){ 
        //do stuff here
    });
});
Arash Milani
fuente
De hecho, esto parece el mejor enfoque, pero aunque asocio el evento solo a "#YourExampleElementID", parece que todos los cambios de clase (es decir, en cualquiera de los elementos de mi página) son manejados por este controlador ... ¿alguna idea?
Filipe Correia
Funciona bien para mí @FilipeCorreia. ¿Puede proporcionar un jsfiddle con la replicación de su caso?
Arash Milani
@ Goldentoa11 Debería tomarse el tiempo una tarde o un fin de semana y controlar todo lo que sucede en una carga de página típica que utiliza mucho la publicidad. Te sorprenderá el volumen de scripts que intentan (a veces sin éxito de la manera más espectacular) hacer cosas como esta. Me he encontrado con páginas que activan decenas de eventos DOMNodeInserted / DOMSubtreeModified por segundo, totalizando cientos de eventos para cuando llegas $(), cuando comienza la acción real.
L0j1k
Presumiblemente también querrás apoyar la activación del mismo evento en removeClass
Darius el
44
Arash, supongo que entiendo por qué @FilipeCorreia estaba experimentando algunos problemas con este enfoque: si vincula este cssClassChangedevento a un elemento, debe tener en cuenta que se activará incluso cuando su hijo cambie de clase. Por lo tanto, si por ejemplo no desea activar este evento para ellos, simplemente agregue algo como $("#YourExampleElementID *").on('cssClassChanged', function(e) { e.stopPropagation(); });después de su enlace. De todos modos, muy buena solución, ¡gracias!
tonix
59

Si desea detectar el cambio de clase, la mejor manera es usar Observadores de mutación, que le da control total sobre cualquier cambio de atributo. Sin embargo, debe definir el oyente usted mismo y agregarlo al elemento que está escuchando. Lo bueno es que no necesita activar nada manualmente una vez que se agrega el oyente.

$(function() {
(function($) {
    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;

    $.fn.attrchange = function(callback) {
        if (MutationObserver) {
            var options = {
                subtree: false,
                attributes: true
            };

            var observer = new MutationObserver(function(mutations) {
                mutations.forEach(function(e) {
                    callback.call(e.target, e.attributeName);
                });
            });

            return this.each(function() {
                observer.observe(this, options);
            });

        }
    }
})(jQuery);

//Now you need to append event listener
$('body *').attrchange(function(attrName) {

    if(attrName=='class'){
            alert('class changed');
    }else if(attrName=='id'){
            alert('id changed');
    }else{
        //OTHER ATTR CHANGED
    }

});
});

En este ejemplo, el detector de eventos se agrega a cada elemento, pero no lo desea en la mayoría de los casos (guardar memoria). Agregue este oyente "attrchange" al elemento que desea observar.

Sr. Br
fuente
1
Parece que está haciendo exactamente lo mismo que la respuesta aceptada, pero con más código, no compatible con algunos navegadores y con menos flexibilidad. el único beneficio que esto parece tener es que se dispara cada vez que algo cambia en la página, que destruirán absolutamente su rendimiento ...
Jason
10
Su declaración en respuesta es incorrecta @Json, esta es realmente la forma de hacerlo si no desea activar ningún disparador manualmente, pero lo tiene activo automáticamente. No es solo un beneficio, es el beneficio, porque esto se hizo en cuestión. En segundo lugar, esto fue solo un ejemplo, y como se dice en el ejemplo, no se sugiere agregar un detector de eventos a cada elemento, sino solo a los elementos que desea escuchar, por lo que no entiendo por qué destruirá el rendimiento. Y lo último, es futuro, la mayoría de los últimos navegadores lo admiten, caniuse.com/#feat=mutationobserver . Reconsidere la edición, es la forma correcta.
Sr. Br
1
La biblioteca insertionQ le permite capturar nodos DOM que aparecen si coinciden con un selector. Tiene un soporte de navegador más amplio porque no lo usa DOMMutationObserver.
Dan Dascalescu
Esta es una gran solución. En particular para iniciar y detener animaciones de lienzo o SVG cuando se agrega o elimina una clase. En mi caso, para asegurarme de que no se están ejecutando cuando se desplaza fuera de la vista. ¡Dulce!
BBaysinger
1
Para 2019, esta debería ser la respuesta aceptada. Todos los principales navegadores parecen admitir Mutation Observers ahora, y esta solución no requiere activar eventos manualmente o reescribir la funcionalidad básica de jQuery.
sp00n
53

Sugeriría que anule la función addClass. Puedes hacerlo de esta manera:

// Create a closure
(function(){
    // Your base, I'm in it!
    var originalAddClassMethod = jQuery.fn.addClass;

    jQuery.fn.addClass = function(){
        // Execute the original method.
        var result = originalAddClassMethod.apply( this, arguments );

        // call your function
        // this gets called everytime you use the addClass method
        myfunction();

        // return the original result
        return result;
    }
})();

// document ready function
$(function(){
    // do stuff
});
RamboNo5
fuente
16
Combinar esto con el $ (mySelector) .trigger de Jason ('cssClassChanged') sería el mejor enfoque, creo.
kosoant
44
Hay un complemento que hace esto genéricamente: github.com/aheckmann/jquery.hook/blob/master/jquery.hook.js
Jerph
37
Parece una excelente manera de confundir a un futuro mantenedor.
Roufamatic
1
No olvide devolver el resultado del método original.
Petah
1
Como referencia, está utilizando el Patrón de proxy en.wikipedia.org/wiki/Proxy_pattern
Darius
22

Solo una prueba de concepto:

Mira la esencia para ver algunas anotaciones y mantente actualizado:

https://gist.github.com/yckart/c893d7db0f49b1ea4dfb

(function ($) {
  var methods = ['addClass', 'toggleClass', 'removeClass'];

  $.each(methods, function (index, method) {
    var originalMethod = $.fn[method];

    $.fn[method] = function () {
      var oldClass = this[0].className;
      var result = originalMethod.apply(this, arguments);
      var newClass = this[0].className;

      this.trigger(method, [oldClass, newClass]);

      return result;
    };
  });
}(window.jQuery || window.Zepto));

El uso es bastante simple, solo agregue un nuevo listender en el nodo que desea observar y manipule las clases como de costumbre:

var $node = $('div')

// listen to class-manipulation
.on('addClass toggleClass removeClass', function (e, oldClass, newClass) {
  console.log('Changed from %s to %s due %s', oldClass, newClass, e.type);
})

// make some changes
.addClass('foo')
.removeClass('foo')
.toggleClass('foo');
yckart
fuente
Esto funcionó para mí, excepto que no podía leer la clase vieja o nueva con el método removeClass.
slashdottir
1
@slashdottir ¿Puedes crear un violín y explicar qué no funciona exactamente ?
yckart
Sí ... no funciona TypeError: este [0] no está definido en «return this [0] .className;»
Pedro Ferreira
funciona pero a veces (¿por qué?), this[0]no está definido ... simplemente reemplace el final de las líneas con var oldClassy var newClasscon la siguiente asignación:= (this[0] ? this[0].className : "");
fvlinden
9

utilizando la última mutación jquery

var $target = jQuery(".required-entry");
            var observer = new MutationObserver(function(mutations) {
                mutations.forEach(function(mutation) {
                    if (mutation.attributeName === "class") {
                        var attributeValue = jQuery(mutation.target).prop(mutation.attributeName);
                        if (attributeValue.indexOf("search-class") >= 0){
                            // do what you want
                        }
                    }
                });
            });
            observer.observe($target[0],  {
                attributes: true
            });

// cualquier código que actualice div que tenga una clase required-entry que esté en $ target como $ target.addClass ('search-class');

Hassan Ali Shahzad
fuente
Buena solución, lo estoy usando. Sin embargo, estoy usando react y uno de los problemas es que agrego un observador cada vez que el elemento web se genera virtualmente. ¿Hay alguna manera de verificar si un observador ya está presente?
Mikaël Mayer
Creo que esto se ha depreciado y no debe usarse de acuerdo con [link] developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
WebDude0482
@ WebDude0482 MutationObserver.observe no está en desuso, ya que es un "Método de instancia" del "MutationObserver". Ver developer.mozilla.org/en-US/docs/Web/API/MutationObserver
nu everest
8

change()no se dispara cuando se agrega o elimina una clase CSS o cambia la definición. Se activa en circunstancias como cuando se selecciona o no se selecciona un valor de cuadro de selección.

No estoy seguro de si quiere decir si la definición de clase CSS se modifica (lo que se puede hacer mediante programación pero es tedioso y generalmente no se recomienda) o si se agrega o elimina una clase a un elemento. No hay forma de capturar de manera confiable este hecho en ninguno de los casos.

Por supuesto, podría crear su propio evento para esto, pero esto solo puede describirse como un aviso . No capturará código que no sea tuyo haciéndolo.

Alternativamente, podría anular / reemplazar los addClass()métodos (etc.) en jQuery, pero esto no capturará cuando se haga a través de Javascript vainilla (aunque supongo que también podría reemplazar esos métodos).

cletus
fuente
8

Hay una forma más sin activar un evento personalizado

Un complemento de jQuery para monitorear los cambios de CSS del elemento HTML por Rick Strahl

Citando desde arriba

El complemento de reloj funciona al conectarse a DOMAttrModified en FireFox, a onPropertyChanged en Internet Explorer, o al usar un temporizador con setInterval para manejar la detección de cambios para otros navegadores. Desafortunadamente, WebKit no es compatible con DOMAttrModified de manera consistente en este momento, por lo que Safari y Chrome actualmente tienen que usar el mecanismo setInterval más lento.

Amitd
fuente
6

Buena pregunta. Estoy usando el menú desplegable Bootstrap y necesitaba ejecutar un evento cuando un menú desplegable Bootstrap estaba oculto. Cuando se abre el menú desplegable, el div que lo contiene con un nombre de clase de "grupo de botones" agrega una clase de "abierto"; y el botón en sí tiene un atributo "expandido en aria" establecido en verdadero. Cuando se cierra el menú desplegable, esa clase de "abrir" se elimina del div que contiene y aria-expandido se cambia de verdadero a falso.

Eso me llevó a esta pregunta, de cómo detectar el cambio de clase.

Con Bootstrap, hay "Eventos desplegables" que se pueden detectar. Busque "Eventos desplegables" en este enlace. http://www.w3schools.com/bootstrap/bootstrap_ref_js_dropdown.asp

Aquí hay un ejemplo rápido y sucio de usar este evento en un menú desplegable Bootstrap.

$(document).on('hidden.bs.dropdown', function(event) {
    console.log('closed');
});

Ahora me doy cuenta de que esto es más específico que la pregunta general que se hace. Pero me imagino que otros desarrolladores que intentan detectar un evento abierto o cerrado con un menú desplegable Bootstrap encontrarán esto útil. Al igual que yo, inicialmente pueden seguir el camino de simplemente tratar de detectar un cambio de clase de elemento (que aparentemente no es tan simple). Gracias.

Ken Palmer
fuente
5

Si necesita activar un evento específico, puede anular el método addClass () para activar un evento personalizado llamado 'classadded'.

Aquí como:

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

$('#myElement').on('classadded', function(ev, newClasses) {
    console.log(newClasses + ' added!');
    console.log(this);
    // Do stuff
    // ...
});
micred
fuente
5

Puede vincular el evento DOMSubtreeModified. Añado un ejemplo aquí:

HTML

<div id="mutable" style="width:50px;height:50px;">sjdfhksfh<div>
<div>
  <button id="changeClass">Change Class</button>
</div>

JavaScript

$(document).ready(function() {
  $('#changeClass').click(function() {
    $('#mutable').addClass("red");
  });

  $('#mutable').bind('DOMSubtreeModified', function(e) {
      alert('class changed');
  });
});

http://jsfiddle.net/hnCxK/13/

Sativa Diva
fuente
0

si sabe un evento que cambió la clase en primer lugar, puede usar un ligero retraso en el mismo evento y verificar la clase. ejemplo

//this is not the code you control
$('input').on('blur', function(){
    $(this).addClass('error');
    $(this).before("<div class='someClass'>Warning Error</div>");
});

//this is your code
$('input').on('blur', function(){
    var el= $(this);
    setTimeout(function(){
        if ($(el).hasClass('error')){ 
            $(el).removeClass('error');
            $(el).prev('.someClass').hide();
        }
    },1000);
});

http://jsfiddle.net/GuDCp/3/

Nikos Tsagkas
fuente
0
var timeout_check_change_class;

function check_change_class( selector )
{
    $(selector).each(function(index, el) {
        var data_old_class = $(el).attr('data-old-class');
        if (typeof data_old_class !== typeof undefined && data_old_class !== false) 
        {

            if( data_old_class != $(el).attr('class') )
            {
                $(el).trigger('change_class');
            }
        }

        $(el).attr('data-old-class', $(el).attr('class') );

    });

    clearTimeout( timeout_check_change_class );
    timeout_check_change_class = setTimeout(check_change_class, 10, selector);
}
check_change_class( '.breakpoint' );


$('.breakpoint').on('change_class', function(event) {
    console.log('haschange');
});
Jérome Obbiet
fuente