É possível ouvir um evento de "mudança de estilo"?

206

É possível criar um ouvinte de evento no jQuery que possa ser vinculado a qualquer alteração de estilo? Por exemplo, se eu quiser "fazer" algo quando um elemento alterar dimensões, ou qualquer outra alteração no atributo style, eu poderia fazer:

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

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

Seria realmente útil.

Alguma ideia?

ATUALIZAR

Desculpe por responder isso sozinho, mas escrevi uma solução interessante que pode se encaixar em outra pessoa:

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

Isso substituirá temporariamente o método prototype.css interno e o redefinirá com um gatilho no final. Então funciona assim:

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

$('p').width(100);
$('p').css('color','red');
David Hellsing
fonte
Sim, boa solução, mas em um aplicativo real, suspeito que isso possa levar muitos recursos e até faça o navegador parecer lento para acompanhar as interações do usuário. Eu adoraria saber se é esse o caso ou não.
Pixeline
1
@pixeline: Não vejo que seja muito mais lento. jQuery ainda chama o protótipo css de qualquer maneira, estou apenas adicionando um gatilho a ele. Portanto, é basicamente apenas uma chamada de método extra.
David Hellsing
Eu entendo isso: parece que no fluxo de um usuário interagindo com um aplicativo, css () é chamado muito tempo. Dependendo do que você faz com esse gatilho (se é apenas um log do console, acho que você está seguro, é claro), isso pode criar problemas. Estava só a pensar. Depende da aplicação também. Pessoalmente, costumo fazer muito feedback visual nas minhas coisas.
Pixeline
Há mais algumas coisas a fazer para fazê-lo funcionar com as chamadas jquery css existentes: 1) você gostaria de retornar o elemento da sua nova função css, caso contrário, ela quebrará as chamadas css de estilo fluente. por exemplo. $ ('p'). css ('exibição', 'bloco'). css ('cor', 'preto'); etc. 2) se for uma chamada para um valor de propriedade (por exemplo, 'obj.css (' height ');'), você deseja retornar o valor de retorno da função original - faço isso verificando se os argumentos estão listados para a função contém apenas um argumento.
theringostarrs
1
Fiz uma versão mais robusta dessa abordagem que também funciona quando o próprio atributo de estilo está sendo removido. Ele lançará um evento 'style' para cada estilo que está sendo removido dessa maneira. gist.github.com/bbottema/426810c21ae6174148d4
Benny Bottema

Respostas:

19

Como o jQuery é de código aberto, acho que você poderia ajustar a cssfunção para chamar uma função de sua escolha toda vez que ela for chamada (passando o objeto jQuery). Obviamente, você desejará vasculhar o código jQuery para garantir que não haja mais nada que ele use internamente para definir propriedades CSS. Idealmente, você gostaria de escrever um plugin separado para o jQuery, para que não interfira na própria biblioteca do jQuery, mas você terá que decidir se isso é ou não possível para o seu projeto.

Josh Stodola
fonte
1
Mas, neste caso, o que aconteceria se eu definir a propriedade style diretamente com funções DOM?
Jaime Hablutzel
Aqui está um exemplo completo desta solução: gist.github.com/bbottema/426810c21ae6174148d4
Benny Bottema
272

As coisas mudaram um pouco desde que a pergunta foi feita - agora é possível usar um MutationObserver para detectar alterações no atributo 'style' de um elemento, não é necessário 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'] });

O argumento que é passado para a função de retorno de chamada é um objeto MutationRecord que permite obter os valores de estilo antigo e novo.

O suporte é bom em navegadores modernos, incluindo o IE 11+.

codebox
fonte
19
Lembre-se de que isso funciona apenas com estilos embutidos, não quando o estilo muda como consequência da alteração ou @mediaalteração de classe .
22816 fregante
2
@ bfred.it você teria uma idéia de como conseguir exatamente isso? Eu estou querendo executar alguns js depois que as regras de css de uma classe foram aplicadas a um elemento.
Kragalon
Você poderia usar getComputedStylecom setIntervalisso, mas isso atrasaria muito o seu site.
Fregante
Nota: O MutationObserver, na verdade, não funciona em todas as versões do Android 4.4, apesar do que diz caniuse.
James Gould
Veja este exemplo para alterar um exemplo de como alterar a cor do plano de fundo de uma área de texto para azul quando a largura da área de texto ultrapassar 300 px. jsfiddle.net/RyanNerd/y2q8wuf0 (infelizmente parece haver um erro no FF que trava o script emHTMLElement.style.prop = "value"
RyanNerd
18

A declaração do seu objeto de evento deve estar dentro da sua nova função css. Caso contrário, o evento poderá ser acionado apenas uma vez.

(function() {
    orig = $.fn.css;
    $.fn.css = function() {
        var ev = new $.Event('style');
        orig.apply(this, arguments);
        $(this).trigger(ev);
    }
})();
Mike Allen
fonte
Ótimo, obrigado. Algumas pessoas dizem para mudar a fonte original, mas sua extensão é a melhor. Eu tive alguns problemas, mas finalmente funciona.
C3rc #
Ele dispara continuamente, depois de executar esse código, e nem atinge o resultado requerido.
smkrn110
13

Eu acho que a melhor resposta é do Mike, caso você não consiga iniciar o seu evento porque não é do seu código. Mas eu recebo alguns erros quando o usei. Então, escrevo uma nova resposta para mostrar o código que eu uso.

Extensão

// 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');

Eu recebi um erro porque var ev = new $ .Event ('style'); Algo como estilo não foi definido no HtmlDiv. Eu o removi e inicio agora $ (this) .trigger ("stylechanged"). Outro problema foi que Mike não retornou o resultado de $ (css, ..) e pode causar problemas em alguns casos. Então eu pego o resultado e o devolvo. Agora funciona ^^ Em todas as alterações no CSS, incluo algumas bibliotecas que não consigo modificar e acionar um evento.

ccsakuweb
fonte
Muito útil e inteligente
dgo 19/03/18
5

Como outros sugeriram, se você tiver controle sobre qualquer código que esteja alterando o estilo do elemento, poderá disparar um evento personalizado ao alterar a altura do elemento:

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

Caso contrário, embora consuma muitos recursos, você pode definir um cronômetro para verificar periodicamente se há alterações na altura do elemento ...

droo
fonte
Esta é realmente uma solução muito interessante se alguém tiver acesso ao código que está alterando os estilos.
Hafeez
2

Não há suporte embutido para o evento de mudança de estilo no jQuery ou no script java. Mas o jQuery oferece suporte para criar eventos personalizados e ouvi-los, mas toda vez que houver uma alteração, você deve ter uma maneira de acioná-lo. Portanto, não será uma solução completa.

Teja Kantamneni
fonte
2

você pode experimentar o plugin Jquery, ele aciona eventos quando o css é alterado e é 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
  }
});
user889030
fonte
1

Pergunta interessante. O problema é que height () não aceita um retorno de chamada, portanto você não poderá acioná-lo. Use animate () ou css () para definir a altura e, em seguida, acionar o evento personalizado no retorno de chamada. Aqui está um exemplo usando animate (), testado e funciona ( demo ), como uma prova de conceito:

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

$('#test').animate({height: 100},function(){
$(this).trigger('style');
}); 
pixeline
fonte
8
Desculpe, mas o que isso provou? Você trigger()o evento manualmente. Eu acho que o OP está após algum evento acionado automaticamente quando um atributo de estilo é modificado.
JPot 28/01/10
@JPot, ele está mostrando que o atributo height não lança eventos quando alterado.
AnthonyJClink
1

Apenas adicionando e formalizando a solução do @David acima:

Observe que as funções jQuery são encadeadas e retornam 'this' para que várias invocações possam ser chamadas uma após a outra (por exemplo $container.css("overflow", "hidden").css("outline", 0);).

Portanto, o código aprimorado deve 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
fonte
0

E o jQuery cssHooks?

Talvez eu não entenda a pergunta, mas o que você está procurando é fácil cssHooks, sem alterar a css()função.

copiar da documentação:

(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
fonte
-1

Eu tinha o mesmo problema, então escrevi isso. Funciona bastante bem. Parece ótimo se você o misturar com algumas transições CSS.

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