Est-il possible d'écouter un événement «changement de style»?

206

Est-il possible de créer un écouteur d'événements dans jQuery qui peut être lié à tout changement de style? Par exemple, si je veux "faire" quelque chose lorsqu'un élément change de dimension, ou tout autre changement dans l'attribut de style, je pourrais faire:

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

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

Ce serait vraiment utile.

Des idées?

METTRE À JOUR

Désolé d'avoir répondu moi-même à cette question, mais j'ai écrit une solution intéressante qui pourrait convenir à quelqu'un d'autre:

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

Cela remplacera temporairement la méthode interne prototype.css et la redéfinira avec un déclencheur à la fin. Donc ça marche comme ça:

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

$('p').width(100);
$('p').css('color','red');
David Hellsing
la source
oui, belle solution, mais dans une application réelle, je pense que cela pourrait prendre beaucoup de ressources, et même rendre le navigateur lent à suivre les interactions de l'utilisateur. J'aimerais savoir si c'est le cas ou non.
pixeline
1
@pixeline: Je ne vois pas que ça devrait être beaucoup plus lent. jQuery appelle toujours le prototype css de toute façon, j'y ajoute juste un déclencheur. Il s'agit donc essentiellement d'un seul appel de méthode supplémentaire.
David Hellsing
Je comprends cela: il semble juste que dans le flux d'un utilisateur interagissant avec une application, css () s'appelle beaucoup de temps. Selon ce que vous faites avec ce déclencheur (si c'est juste un journal de console, je suppose que vous êtes en sécurité bien sûr), cela pourrait créer des problèmes. Je me demande simplement. Cela dépend aussi de l'application. Personnellement, j'ai tendance à faire beaucoup de commentaires visuels dans mes affaires.
pixeline
Il y a quelques autres choses à faire pour le faire fonctionner avec les appels CSS jquery existants: 1) vous voudriez renvoyer l'élément hors de votre nouvelle fonction CSS ou sinon il cassera les appels CSS de style fluide. par exemple. $ ('p'). css ('display', 'block'). css ('color', 'black'); etc. 2) s'il s'agit d'un appel pour une valeur de propriété (par exemple 'obj.css (' height ');') vous voudriez renvoyer la valeur de retour de la fonction d'origine - je le fais en vérifiant si la liste des arguments car la fonction ne contient qu'un seul argument.
theringostarrs
1
J'ai fait une version plus robuste de cette approche qui fonctionne également lorsque l' attribut style lui-même est supprimé. Il lancera un événement «style» pour chaque style supprimé de cette façon. gist.github.com/bbottema/426810c21ae6174148d4
Benny Bottema

Réponses:

19

Étant donné que jQuery est open-source, je suppose que vous pouvez modifier la cssfonction pour appeler une fonction de votre choix chaque fois qu'elle est invoquée (en passant l'objet jQuery). Bien sûr, vous voudrez parcourir le code jQuery pour vous assurer qu'il n'y a rien d'autre qu'il utilise en interne pour définir les propriétés CSS. Idéalement, vous voudriez écrire un plugin séparé pour jQuery afin qu'il n'interfère pas avec la bibliothèque jQuery elle-même, mais vous devrez décider si cela est faisable ou non pour votre projet.

Josh Stodola
la source
1
Mais dans ce cas, que se passerait-il si je définissais la propriété de style directement avec les fonctions DOM ??
Jaime Hablutzel
Voici un exemple complet de cette solution: gist.github.com/bbottema/426810c21ae6174148d4
Benny Bottema
272

Les choses ont évolué un peu depuis que la question a été posée - il est maintenant possible d'utiliser un MutationObserver pour détecter les changements dans l'attribut 'style' d'un élément, aucune jQuery requise:

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'] });

L'argument qui est transmis à la fonction de rappel est un objet MutationRecord qui vous permet de récupérer les anciennes et les nouvelles valeurs de style.

Le support est bon dans les navigateurs modernes, y compris IE 11+.

codebox
la source
19
Gardez à l'esprit que cela ne fonctionne qu'avec les styles en ligne, pas lorsque le style change à la suite d'un changement de classe ou d'un @mediachangement.
fregante
2
@ bfred.it, auriez-vous une idée de comment y parvenir? Je veux exécuter quelques js après que les règles CSS d'une classe ont été appliquées à un élément.
Kragalon
Vous pouvez utiliser getComputedStyleavec setIntervalmais cela ralentirait beaucoup votre site Web.
fregante
Remarque: MutationObserver ne fonctionne pas réellement dans toutes les versions d'Android 4.4, malgré ce que dit caniuse.
James Gould
Voir ce twiddle pour un exemple de changement de la couleur d'arrière-plan d'une zone de texte en bleu lorsque la largeur de la zone de texte est étendue au-delà de 300 pixels. jsfiddle.net/RyanNerd/y2q8wuf0 (malheureusement, il semble y avoir un bogue dans FF qui bloque le script àHTMLElement.style.prop = "value"
RyanNerd
18

La déclaration de votre objet événement doit se trouver dans votre nouvelle fonction css. Sinon, l'événement ne peut être déclenché qu'une seule fois.

(function() {
    orig = $.fn.css;
    $.fn.css = function() {
        var ev = new $.Event('style');
        orig.apply(this, arguments);
        $(this).trigger(ev);
    }
})();
Mike Allen
la source
Super merci. Certaines personnes disent de changer la source d'origine mais votre extension est la meilleure. J'ai eu quelques problèmes mais finalement ça marche.
ccsakuweb
Il tire en continu, après avoir exécuté ce code, et n'atteint même pas le résultat requis.
smkrn110
13

Je pense que la meilleure réponse si de Mike dans le cas où vous ne pouvez pas lancer votre événement parce qu'il ne provient pas de votre code. Mais je reçois des erreurs lorsque je l'ai utilisé. J'écris donc une nouvelle réponse pour vous montrer le code que j'utilise.

Extension

// 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;
    }
})();

Usage

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

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

J'ai eu une erreur car var ev = new $ .Event ('style'); Quelque chose comme le style n'était pas défini dans HtmlDiv .. Je l'ai supprimé et je lance maintenant $ (this) .trigger ("stylechanged"). Un autre problème est que Mike n'a pas renvoyé le résultat de $ (css, ..), ce qui peut poser des problèmes dans certains cas. Donc j'obtiens le résultat et je le retourne. Fonctionne maintenant ^^ Dans chaque changement CSS, incluez des libs que je ne peux pas modifier et déclencher un événement.

ccsakuweb
la source
Très utile et intelligent
dgo
5

Comme d'autres l'ont suggéré, si vous contrôlez le code qui modifie le style de l'élément, vous pouvez déclencher un événement personnalisé lorsque vous modifiez la hauteur de l'élément:

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

Sinon, bien que très gourmand en ressources, vous pouvez définir une minuterie pour vérifier périodiquement les modifications de la hauteur de l'élément ...

droo
la source
C'est en effet une solution très soignée si l'on a accès au code qui change les styles.
Umair Hafeez
2

Il n'y a pas de support intégré pour l'événement de changement de style dans jQuery ou dans le script java. Mais jQuery prend en charge la création d'un événement personnalisé et l'écoute, mais chaque fois qu'il y a un changement, vous devez avoir un moyen de le déclencher par vous-même. Ce ne sera donc pas une solution complète.

Teja Kantamneni
la source
2

vous pouvez essayer le plugin Jquery, il déclenche des événements lorsque css est modifié et il est facile à utiliser

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
  }
});
user889030
la source
1

Question interessante. Le problème est que height () n'accepte pas de rappel, vous ne pourrez donc pas lancer de rappel. Utilisez animate () ou css () pour définir la hauteur, puis déclenchez l'événement personnalisé dans le rappel. Voici un exemple utilisant animate (), testé et fonctionne ( démo ), comme preuve de concept:

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

$('#test').animate({height: 100},function(){
$(this).trigger('style');
}); 
pixeline
la source
8
Désolé, mais qu'est-ce que cela prouve? Vous trigger()l'événement manuellement. Je pense que l'OP est après un événement déclenché automatiquement lorsqu'un attribut de style est modifié.
JPot
@JPot, il montre que l'attribut height ne déclenche pas d'événements lorsqu'il est modifié.
AnthonyJClink
1

Il suffit d'ajouter et de formaliser la solution de @David par le haut:

Notez que les fonctions jQuery sont chaînables et renvoient «this» afin que plusieurs invocations puissent être appelées l'une après l'autre $container.css("overflow", "hidden").css("outline", 0); ).

Le code amélioré devrait donc être:

(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
la source
0

Que diriez-vous de jQuery cssHooks?

Peut-être que je ne comprends pas la question, mais ce que vous recherchez se fait facilement cssHookssans changercss() fonction.

copie de la documentation:

(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/

halfbit
la source
-1

J'ai eu le même problème, alors j'ai écrit ceci. Cela fonctionne plutôt bien. A l'air bien si vous le mélangez avec certaines transitions CSS.

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