Bắn các sự kiện trên lớp CSS thay đổi trong jQuery


240

Làm cách nào để khởi chạy một sự kiện nếu một lớp CSS được thêm hoặc thay đổi bằng jQuery? Việc thay đổi một lớp CSS có kích hoạt change()sự kiện jQuery không?


8
7 năm sau, với việc áp dụng M mutObservers khá rộng rãi trong các trình duyệt hiện đại, câu trả lời được chấp nhận ở đây nên thực sự được cập nhật để trở thành Mr Br's .
joelmdev

Điều này có thể giúp bạn. stackoverflow.com/questions/2157963/ cường
PSR

Trong hoàn để trả lời của Jason, tôi thấy điều này: stackoverflow.com/a/19401707/1579667
Benj

Câu trả lời:


223

Bất cứ khi nào bạn thay đổi một lớp trong kịch bản của mình, bạn có thể sử dụng một triggerđể nâng cao sự kiện của riêng bạn.

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

nhưng nếu không, không, không có cách nào để khởi động một sự kiện khi một lớp thay đổi. change()chỉ kích hoạt sau khi lấy nét để lại một đầu vào có đầu vào bị thay đổi.

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

Thông tin thêm về Triggers jQuery


4
Thỏa thuận với việc kích hoạt sự kiện mà sau đó gọi một hàm là gì? Tại sao không gọi hàm trực tiếp, thay vì kích hoạt nó?
RamboNo5

43
Kích hoạt là một cơ chế sẽ cho phép các yếu tố nhất định "đăng ký" vào một sự kiện. theo cách đó, khi sự kiện được kích hoạt, tất cả các sự kiện đó có thể xảy ra cùng một lúc và chạy chức năng tương ứng của chúng. chẳng hạn, nếu tôi có một đối tượng thay đổi từ màu đỏ sang màu xanh và ba đối tượng đang chờ nó thay đổi, khi nó thay đổi, tôi chỉ có thể kích hoạt changeColorsự kiện và tất cả những đối tượng đăng ký vào sự kiện đó có thể phản ứng tương ứng.
Jason

1
Có vẻ hơi lạ khi OP muốn nghe một sự kiện 'cssClassChanged'; chắc chắn lớp học đã được thêm vào do kết quả của một số sự kiện 'ứng dụng' khác, chẳng hạn như 'colourChanged', sẽ có ý nghĩa hơn khi thông báo với trình kích hoạt vì tôi không thể tưởng tượng được tại sao mọi thứ lại quan tâm đến một sự thay đổi tên lớp cụ thể, đó là kết quả của classNameChange chắc chắn?
riscarrott

Nếu đó cụ thể là một thay đổi className đáng quan tâm thì tôi tưởng tượng việc trang trí jQuery.fn.addClass để kích hoạt 'cssClassChanged' sẽ hợp lý hơn như @micred đã nói
riscarrott 8/213

5
@riscarrott Tôi không đoán được ứng dụng của OP. Tôi chỉ trình bày cho họ khái niệm về pub / sub và họ có thể áp dụng nó theo cách họ muốn. Việc tranh cãi tên sự kiện thực tế sẽ là bao nhiêu với lượng thông tin được cung cấp là ngớ ngẩn.
Jason

140

IMHO giải pháp tốt hơn là kết hợp hai câu trả lời của @ RamboNo5 và @Jason

Tôi có nghĩa là ghi đè hàm addClass và thêm một sự kiện tùy chỉnh được gọi là 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
    });
});

Đây thực sự có vẻ là cách tiếp cận tốt nhất, nhưng mặc dù tôi chỉ liên kết sự kiện với "#YourExampleEuityID", có vẻ như tất cả các thay đổi của lớp (tức là, trên bất kỳ yếu tố nào trên trang của tôi) đều được xử lý bởi ... xử lý này?
Filipe Correia

Nó hoạt động tốt đối với tôi @FilipeCorreia. Bạn có thể vui lòng cung cấp một jsfiddle với sao chép trường hợp của bạn?
Arash Milani

@ Goldentoa11 Bạn nên dành thời gian vào buổi tối hoặc cuối tuần và theo dõi mọi thứ xảy ra trên một trang tải thông thường sử dụng quảng cáo rất nhiều. Bạn sẽ bị thổi bay bởi khối lượng kịch bản cố gắng (đôi khi không thành công theo cách ngoạn mục nhất) để làm những việc như thế này. Tôi đã chạy trên các trang kích hoạt hàng chục sự kiện DOMNodeInserted / DOMSubtreeModified mỗi giây, tổng cộng hàng trăm sự kiện vào thời điểm bạn nhận được $(), khi hành động thực sự bắt đầu.
L0j1k

Có lẽ bạn cũng muốn hỗ trợ kích hoạt cùng một sự kiện trên removeClass
Darius

4
Arash, tôi đoán tôi hiểu tại sao @FilipeCorreia gặp phải một số vấn đề với cách tiếp cận này: nếu bạn liên kết cssClassChangedsự kiện này với một yếu tố, bạn phải biết rằng nó sẽ bị sa thải ngay cả khi con của nó thay đổi lớp. Do đó, ví dụ nếu bạn không muốn tổ chức sự kiện này cho họ, chỉ cần thêm một cái gì đó như $("#YourExampleElementID *").on('cssClassChanged', function(e) { e.stopPropagation(); });sau khi liên kết của bạn. Dù sao, giải pháp thực sự tốt đẹp, cảm ơn!
tonix

59

Nếu bạn muốn phát hiện thay đổi lớp, cách tốt nhất là sử dụng Trình quan sát đột biến, cho phép bạn kiểm soát hoàn toàn mọi thay đổi thuộc tính. Tuy nhiên, bạn cần tự xác định người nghe và nối nó vào phần tử bạn đang nghe. Điều tốt là bạn không cần kích hoạt bất cứ thứ gì một cách thủ công một khi người nghe được nối thêm.

$(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
    }

});
});

Trong ví dụ này, trình nghe sự kiện được thêm vào mọi phần tử, nhưng bạn không muốn điều đó trong hầu hết các trường hợp (lưu bộ nhớ). Nối trình nghe "attrchange" này vào phần tử bạn muốn quan sát.


1
điều này có vẻ như nó đang thực hiện chính xác điều mà câu trả lời được chấp nhận đang làm, nhưng với nhiều mã hơn, không được hỗ trợ trên một số trình duyệt và kém linh hoạt hơn. lợi ích duy nhất mà điều này dường như có là nó sẽ kích hoạt mỗi khi có gì đó thay đổi trên trang, điều này sẽ phá hủy hoàn toàn hiệu suất của bạn ...
Jason

10
Câu trả lời của bạn không chính xác @Json, đây thực sự là cách để làm điều đó nếu bạn không muốn kích hoạt bất kỳ trình kích hoạt nào theo cách thủ công, nhưng để nó tự động kích hoạt. Nó không chỉ mang lại lợi ích mà còn là lợi ích, vì điều này đã được hỏi. Thứ hai, đây chỉ là một ví dụ và như trong ví dụ, nó không được đề xuất để nối thêm người nghe sự kiện vào mọi yếu tố mà chỉ cho các yếu tố bạn muốn nghe, vì vậy tôi không hiểu tại sao nó sẽ phá hủy hiệu suất. Và điều cuối cùng, đó là tương lai, hầu hết các trình duyệt mới nhất đều hỗ trợ nó, caniuse.com/#feat=mutingobserver. Vui lòng xem xét lại chỉnh sửa, đó là cách đúng đắn.
Ông Br

1
Các thư viện insertionQ cho phép bạn bắt DOM node hiển thị nếu họ phù hợp với một selector. Nó có hỗ trợ trình duyệt rộng hơn vì không sử dụng DOMMutationObserver.
Dan Dascalescu

Đây là một giải pháp tuyệt vời. Đặc biệt để bắt đầu và dừng hoạt hình canvas hoặc hoạt hình SVG khi một lớp được thêm hoặc xóa. Trong trường hợp của tôi để đảm bảo chúng không chạy khi cuộn ra khỏi tầm nhìn. Ngọt!
BBaysinger

1
Đối với năm 2019, đây sẽ là câu trả lời được chấp nhận. Hiện tại tất cả các trình duyệt chính dường như đều hỗ trợ Trình quan sát đột biến và giải pháp này không yêu cầu thực hiện các sự kiện theo cách thủ công hoặc viết lại chức năng cơ bản của jQuery.
sp00n

53

Tôi sẽ đề nghị bạn ghi đè hàm addClass. Bạn có thể làm theo cách này:

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

16
Kết hợp điều này với $ (mySelector) .trigger ('cssClassChanged') của Jason sẽ là cách tiếp cận tốt nhất tôi nghĩ.
bất kỳ

4
Có một plugin thực hiện điều này một cách khái quát: github.com/aheckmann/jquery.hook/blob/master/jquery.hook.js
Jerph

37
Có vẻ như một cách tuyệt vời để gây nhầm lẫn cho một người duy trì trong tương lai.
roufamatic

1
Đừng quên trả về kết quả của phương thức ban đầu.
Petah

1
Để tham khảo, bạn đang sử dụng Mẫu Proxy en.wikipedia.org/wiki/Proxy_potype
Darius

22

Chỉ là một bằng chứng về khái niệm:

Nhìn vào ý chính để xem một số chú thích và cập nhật:

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

Cách sử dụng khá đơn giản, chỉ cần thêm một trình lắng nghe mới vào nút mà bạn muốn quan sát và thao tác các lớp như thường lệ:

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

Điều này làm việc cho tôi, ngoại trừ tôi không thể đọc lớp cũ hoặc mới bằng phương thức removeClass.
slashdottir

1
@slashdottir Bạn có thể tạo một câu đố và giải thích chính xác những gì không hoạt động .
yckart

Vâng ... không hoạt động. LoạiError: cái này [0] không được xác định trong «return this [0]. ClassName;»
Pedro Ferreira

nó hoạt động nhưng đôi khi (tại sao?), this[0]không được xác định ... chỉ cần thay thế phần cuối của dòng bằng var oldClassvar newClassgán sau:= (this[0] ? this[0].className : "");
fvlinden

9

sử dụng đột biến jquery mới nhất

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

// bất kỳ mã nào cập nhật div có lớp bắt buộc nhập trong mục tiêu $ như $ target.addClass ('search-class');


Giải pháp tuyệt vời, tôi đang sử dụng nó. Tuy nhiên, tôi đang sử dụng React và một trong những vấn đề là tôi thêm một người quan sát mỗi khi phần tử web hầu như được tạo. Có cách nào để kiểm tra xem một người quan sát đã có mặt chưa?
Mikaël Mayer

Tôi nghĩ rằng điều này đã bị khấu hao và không nên được sử dụng theo [link] developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
Kẻ

@ WebDude0482 MutingObserver.observe không bị phản đối vì đây là "Phương pháp sơ thẩm" của "MutingObserver". Xem developer.mozilla.org/en-US/docs/Web/API/MutingObserver
nu everest

8

change()không kích hoạt khi một lớp CSS được thêm hoặc xóa hoặc định nghĩa thay đổi. Nó kích hoạt trong các trường hợp như khi giá trị hộp chọn được chọn hoặc không được chọn.

Tôi không chắc nếu bạn định nghĩa nếu định nghĩa lớp CSS bị thay đổi (có thể được thực hiện theo chương trình nhưng tẻ nhạt và thường không được đề xuất) hoặc nếu một lớp được thêm hoặc xóa thành phần tử. Không có cách nào đáng tin cậy để nắm bắt điều này xảy ra trong cả hai trường hợp.

Tất nhiên bạn có thể tạo sự kiện của riêng bạn cho việc này nhưng điều này chỉ có thể được mô tả là tư vấn . Nó sẽ không bắt được mã mà không phải của bạn làm điều đó.

Ngoài ra, bạn có thể ghi đè / thay thế các addClass()phương thức (vv) trong jQuery nhưng điều này sẽ không nắm bắt được khi nó được thực hiện thông qua Javascript vanilla (mặc dù tôi đoán bạn cũng có thể thay thế các phương thức đó).


8

Có một cách nữa mà không kích hoạt một sự kiện tùy chỉnh

Trình cắm jQuery để theo dõi các thay đổi CSS của phần tử Html của Rick Strahl

Trích dẫn từ trên cao

Trình cắm đồng hồ hoạt động bằng cách kết nối với DOMAttrModified trong FireFox, onPropertyChanged trong Internet Explorer hoặc bằng cách sử dụng bộ hẹn giờ với setInterval để xử lý phát hiện các thay đổi cho các trình duyệt khác. Thật không may, WebKit không hỗ trợ DOMAttrModified một cách nhất quán vào lúc này vì vậy Safari và Chrome hiện phải sử dụng cơ chế setInterval chậm hơn.


6

Câu hỏi hay. Tôi đang sử dụng Menu thả xuống Bootstrap và cần thiết để thực hiện một sự kiện khi Dropstrap Dropdown bị ẩn. Khi thả xuống được mở, div chứa với tên lớp là "nhóm nút" sẽ thêm một lớp "mở"; và bản thân nút có thuộc tính "aria-extend" được đặt thành true. Khi đóng cửa thả xuống, lớp "mở" đó sẽ bị xóa khỏi div chứa và mở rộng aria được chuyển từ true thành false.

Điều đó dẫn tôi đến câu hỏi này, về cách phát hiện sự thay đổi của lớp.

Với Bootstrap, có "Sự kiện thả xuống" có thể được phát hiện. Tìm "Sự kiện thả xuống" tại liên kết này. http://www.w3schools.com/bootstrap/bootstrap_Vf_js_dropdown.asp

Dưới đây là một ví dụ nhanh và bẩn về việc sử dụng sự kiện này trên Dropstrap Dropstrap.

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

Bây giờ tôi nhận ra điều này cụ thể hơn câu hỏi chung đang được hỏi. Nhưng tôi tưởng tượng các nhà phát triển khác đang cố gắng phát hiện một sự kiện mở hoặc đóng bằng Dropstrap Dropdown sẽ thấy điều này hữu ích. Giống như tôi, ban đầu họ có thể đi theo con đường chỉ đơn giản là cố gắng phát hiện sự thay đổi của lớp phần tử (điều này dường như không đơn giản như vậy). Cảm ơn.


5

Nếu bạn cần kích hoạt một sự kiện cụ thể, bạn có thể ghi đè phương thức addClass () để khởi chạy một sự kiện tùy chỉnh có tên là 'classadded'.

Đây như thế nào:

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

5

Bạn có thể liên kết sự kiện DOMSubtreeModified. Tôi thêm một ví dụ ở đây:

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/


0

nếu bạn biết một sự kiện nào đó đã thay đổi lớp ở nơi đầu tiên, bạn có thể sử dụng một chút chậm trễ cho cùng một sự kiện và kiểm tra lớp. thí dụ

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


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');
});
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.