能否监听“样式更改”事件?

255

在jQuery中是否有可能创建一个事件监听器,可以绑定到任何样式变化?例如,如果我想在元素尺寸发生变化或样式属性的任何其他更改时“执行”某些操作,我可以这样做:

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

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

这将非常有用。

有什么想法吗?

更新

很抱歉自己回答了这个问题,但我写了一个好的解决方案,可能适合其他人:

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

这将暂时覆盖内部的prototype.css方法,并重新定义它,最后加上一个触发器。因此它的工作原理是这样的:

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

$('p').width(100);
$('p').css('color','red');

1
@pixeline: 我不觉得这会变得慢很多。jQuery仍然调用css原型,我只是在其中添加了一个触发器。因此,基本上只是多了一个方法调用。 - David Hellsing
还有几件事情要做才能使它与现有的jquery css调用配合使用:1)您需要从新的css函数中返回元素,否则它将破坏流畅样式的css调用。例如:$('p').css('display', 'block').css('color','black');等等。2)如果是属性值的调用(例如'obj.css('height');'),您需要返回原始函数的返回值-我通过检查函数的参数列表是否仅包含一个参数来实现这一点。 - theringostarrs
1
当你回答自己的问题时,答案应该作为一个答案发布,而不是更新问题本身。 - theMayer
@techfoobar的链接已损坏。您可以在此处查看该插件的Github存储库:https://github.com/techfoobar/jquery-style-listener。 - Mikayla Maki
1
我制作了一个更强大的版本,即使删除_style属性本身_时也可以工作。它将为每个以这种方式被删除的样式抛出一个“style”事件。 https://gist.github.com/bbottema/426810c21ae6174148d4 - Benny Bottema
显示剩余3条评论
11个回答

375

自从这个问题被提出以来,事情有所改变 - 现在可以使用MutationObserver来检测元素的"style"属性的更改,无需使用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'] });
回调函数接收一个参数,即MutationRecord对象,可获取旧样式和新样式的值。
现代浏览器(包括IE 11+)支持这一特性。查看支持情况

45
请记住,这仅适用于内联样式,而不是由类更改或@media更改导致的样式更改。 - fregante
3
你有没有想过如何实现这一点?我想在一个元素上应用类的 CSS 规则后运行一些 JS。 - Kragalon
1
你可以使用 getComputedStylesetInterval,但这会大大减慢你的网站速度。 - fregante
注意:MutationObserver在所有Android 4.4版本中实际上并不起作用,尽管caniuse显示它可以。 - James G.
这对于查看元素何时/是否已更改很有帮助,但我无法看到更改的原因 - 即是什么JavaScript导致了变异?这种方法提供了这些信息吗? - elPastor
显示剩余2条评论

22

由于jQuery是开源的,我猜想你可以调整css函数,在每次调用它时调用你选择的函数(传递jQuery对象)。当然,你需要仔细查看jQuery代码,以确保没有其他内部用于设置CSS属性的内容。理想情况下,你应该为jQuery编写一个单独的插件,这样它就不会干扰jQuery库本身,但你必须决定这是否适用于你的项目。


2
但是在这种情况下,如果我直接使用DOM函数设置样式属性会发生什么? - Jaime Hablutzel
这是该解决方案的完整示例:https://gist.github.com/bbottema/426810c21ae6174148d4 - Benny Bottema

20

你的事件对象声明必须在新的css函数内部。否则该事件只能被触发一次。

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

太好了,谢谢。有些人建议更改原始源代码,但是您的扩展程序是最好的。我遇到了一些问题,但最终它能够正常工作了。 - ccsakuweb
执行这段代码后,它会不断地触发,并且无法达到所需的结果。 - KumailR

16

我认为在你无法启动事件的情况下,Mike 的回答是最好的。但是当我使用它时,我遇到了一些错误。因此,我写了一个新答案来展示我使用的代码。

扩展

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

用法

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

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

我之前出错是因为 var ev = new $.Event('style'),似乎在 HtmlDiv 中未定义样式。我把它移除了,现在我使用 $(this).trigger("stylechanged")。另一个问题是 Mike 没有返回 $(css,..) 的结果,在某些情况下可能会导致问题。所以我获取结果并将其返回。现在好了^^ 在每次 CSS 更改时,都包括一些我无法修改的库,并触发事件。


非常有帮助和聪明。 - dgo
正如在这个答案中提到的那样,如果事件不是由您的代码生成,那么可以使用一个方法来帮助你。请注意,这仅针对 jQuery 中 css() 函数所做出的更改。如果外部代码没有通过 css 函数进行更改,则事件处理程序永远不会被调用。 - yougotiger

5

正如其他人所建议的那样,如果您可以控制更改元素样式的任何代码,您可以在更改元素高度时触发自定义事件:

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

否则,虽然它需要相当多的资源,但您可以设置一个定时器以定期检查元素高度的变化...

如果有权访问更改样式的代码,这确实是一个非常整洁的解决方案。 - Umair Hafeez

2

有趣的问题。问题在于height()不接受回调函数,因此您将无法触发回调。使用animate()或css()来设置高度,然后在回调中触发自定义事件。这是一个使用animate()的示例,经过测试可行(演示),作为概念证明:

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

$('#test').animate({height: 100},function(){
$(this).trigger('style');
}); 

9
抱歉,但这说明了什么?你手动trigger()了事件。我认为OP想要的是当样式属性被修改时自动触发的事件。 - JPot
@JPot 正在展示当更改高度属性时不会触发事件。 - AnthonyJClink

2

jQuery和JavaScript中没有内置的支持样式更改事件的功能。但是,jQuery支持创建自定义事件并监听它,但每次更改时,您都需要有一种方法来自己触发它。因此,这不是一个完整的解决方案。


2

您可以尝试使用Jquery插件,它可以在CSS更改时触发事件,并且易于使用。

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

1

仅仅是在上面 @David 的解决方案的基础上添加和规范化:

请注意,jQuery 函数是可链式调用的,并返回 'this',以便可以一个接一个地调用多个调用(例如 $container.css("overflow", "hidden").css("outline", 0);)。

所以改进后的代码应该是:

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

0
jQuery的cssHooks怎么样?
也许我没有理解你的问题,但是使用cssHooks可以很容易地完成你想要的操作,而不需要改变css()函数。
从文档中复制:
(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/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接