เหตุการณ์การยิงในการเปลี่ยนแปลงระดับ CSS ใน jQuery


240

ฉันจะเริ่มต้นเหตุการณ์ได้อย่างไรหากมีการเพิ่มหรือเปลี่ยนแปลงคลาส CSS โดยใช้ jQuery การเปลี่ยนคลาส CSS เป็นการเริ่มต้นchange()เหตุการณ์jQuery หรือไม่


8
7 ปีต่อมามีการยอมรับกว้างเป็นธรรมของ MutationObservers ในเบราว์เซอร์ที่ทันสมัย, คำตอบที่ได้รับการยอมรับที่นี่ควรจะมีการปรับปรุงจริงๆที่จะนาย Br
joelmdev

สิ่งนี้อาจช่วยคุณได้ stackoverflow.com/questions/2157963/…
PSR

เมื่อเสร็จสิ้นการตอบของ Jason ฉันพบสิ่งนี้: stackoverflow.com/a/19401707/1579667
Benj

คำตอบ:


223

เมื่อใดก็ตามที่คุณเปลี่ยนชั้นเรียนในสคริปต์ของคุณคุณสามารถใช้triggerเพื่อยกระดับเหตุการณ์ของคุณเอง

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

แต่ไม่เช่นนั้นจะไม่มีวิธีการที่จะยิงเหตุการณ์เมื่อมีการเปลี่ยนแปลงชั้นเรียน change()ยิงหลังจากโฟกัสแล้วปล่อยให้อินพุตถูกเปลี่ยน

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

ข้อมูลเพิ่มเติมเกี่ยวกับ jQuery Triggers


4
การจัดการกับเหตุการณ์ที่เรียกสิ่งที่เรียกว่าฟังก์ชั่นคืออะไร? ทำไมไม่เรียกใช้ฟังก์ชั่นโดยตรงแทนที่จะสั่งให้ทำงาน?
RamboNo5

43
การทริกเกอร์เป็นกลไกที่จะอนุญาตให้องค์ประกอบบางอย่าง "สมัครสมาชิก" กับกิจกรรม ด้วยวิธีนี้เมื่อมีการเรียกใช้เหตุการณ์กิจกรรมเหล่านั้นสามารถเกิดขึ้นพร้อมกันและเรียกใช้ฟังก์ชันการทำงานที่เกี่ยวข้อง ตัวอย่างเช่นถ้าฉันมีวัตถุหนึ่งชิ้นที่เปลี่ยนจากสีแดงเป็นสีน้ำเงินและสามวัตถุที่รอให้เปลี่ยนเมื่อมันเปลี่ยนฉันก็สามารถทริกเกอร์changeColorเหตุการณ์และวัตถุทั้งหมดที่สมัครเป็นสมาชิกของเหตุการณ์นั้นสามารถตอบสนองได้
เจสัน

1
ดูเหมือนจะแปลกที่ OP ต้องการฟังเหตุการณ์ 'cssClassChanged' แน่นอนว่าคลาสนั้นถูกเพิ่มเข้ามาเนื่องจากเหตุการณ์ 'แอปพลิเคชัน' อื่น ๆ เช่น 'colourChanged' ซึ่งจะเหมาะสมกว่าที่จะประกาศพร้อมกับทริกเกอร์เนื่องจากฉันไม่สามารถจินตนาการได้เลยว่าทำไมสิ่งใดจึงมีความสนใจในการเปลี่ยน className โดยเฉพาะ ผลของ classNameChange แน่นอนหรือไม่
riscarrott

ถ้ามันเป็นการเปลี่ยน className โดยเฉพาะที่น่าสนใจฉันก็นึกภาพการตกแต่ง jQuery.fn.addClass เพื่อทริกเกอร์ 'cssClassChanged' จะมีเหตุผลมากกว่าที่ @micred พูด
riscarrott

5
@riscarrott ฉันไม่คิดว่าจะรู้แอปพลิเคชันของ OP ฉันแค่นำเสนอพวกเขาด้วยแนวคิดของ pub / sub และพวกเขาสามารถใช้มันได้ตามที่พวกเขาต้องการ การโต้แย้งว่าชื่อเหตุการณ์จริงควรมีปริมาณข้อมูลเท่าไร
Jason

140

IMHO ทางออกที่ดีกว่าคือการรวมสองคำตอบโดย @ RamboNo5 และ @Jason

ฉันหมายถึงฟังก์ชั่นการลบล้าง addClass และเพิ่มเหตุการณ์ที่กำหนดเองที่เรียกว่า 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
    });
});

นี่ดูเหมือนจะเป็นวิธีที่ดีที่สุด แต่แม้ว่าฉันจะผูกเหตุการณ์เฉพาะกับ "#YourExampleElementID" แต่ดูเหมือนว่าการเปลี่ยนแปลงระดับทั้งหมด (เช่นในองค์ประกอบใด ๆ ในหน้าของฉัน) ได้รับการจัดการโดยผู้จัดการนี้ ... ความคิดใด ๆ ?
Filipe Correia

มันทำงานได้ดีสำหรับฉัน @FilipeCorreia คุณช่วยระบุ jsfiddle ด้วยการจำลองสถานการณ์ของคุณได้ไหม?
Arash Milani

@ Goldentoa11 คุณควรใช้เวลาช่วงเย็นหรือวันหยุดสุดสัปดาห์และตรวจสอบทุกสิ่งที่เกิดขึ้นในเจดีย์ทั่วไปซึ่งใช้การโฆษณาอย่างหนัก คุณจะถูกลมพัดปลิวไปตามปริมาณของสคริปต์ที่พยายามทำ (บางครั้งไม่ประสบความสำเร็จในวิธีที่งดงามที่สุด) เพื่อทำสิ่งนี้ ฉันทำงานในหลาย ๆ หน้าซึ่งมีการใช้งานเหตุการณ์ DOMNodeInserted / DOMSubtreeModified นับสิบต่อวินาทีรวมเป็นร้อย ๆ เหตุการณ์ในเวลาที่คุณไปถึง$()เมื่อการกระทำจริงเริ่มต้นขึ้น
L0j1k

คุณอาจต้องการสนับสนุนการทริกเกอร์เหตุการณ์เดียวกันใน removeClass
Darius

4
Arash ฉันเข้าใจว่าทำไม @FilipeCorreia กำลังประสบปัญหาบางอย่างกับวิธีการนี้: ถ้าคุณผูกcssClassChangedเหตุการณ์นี้กับองค์ประกอบคุณต้องระวังว่ามันจะถูกไล่ออกเมื่อลูกเปลี่ยนชั้นเรียน ดังนั้นหากตัวอย่างคุณไม่ต้องการใช้เหตุการณ์นี้ให้เพิ่มสิ่งที่ชอบ$("#YourExampleElementID *").on('cssClassChanged', function(e) { e.stopPropagation(); });หลังจากการผูกของคุณ อย่างไรก็ตามทางออกที่ดีจริงๆขอบคุณ!
tonix

59

หากคุณต้องการตรวจสอบการเปลี่ยนแปลงชั้นเรียนวิธีที่ดีที่สุดคือใช้ Mutation Observers ซึ่งช่วยให้คุณควบคุมการเปลี่ยนแปลงคุณสมบัติได้อย่างสมบูรณ์ อย่างไรก็ตามคุณต้องกำหนดฟังเองและผนวกเข้ากับองค์ประกอบที่คุณกำลังฟัง สิ่งที่ดีคือคุณไม่จำเป็นต้องเรียกอะไรด้วยตนเองเมื่อผู้ฟังต่อท้าย

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

});
});

ในตัวอย่างเหตุการณ์นี้ฟังต่อท้ายทุกองค์ประกอบ แต่คุณไม่ต้องการในกรณีส่วนใหญ่ (บันทึกหน่วยความจำ) ผนวกฟัง "attrchange" นี้เพื่อองค์ประกอบที่คุณต้องการสังเกต


1
ดูเหมือนว่ามันกำลังทำสิ่งเดียวกันกับที่คำตอบที่ยอมรับนั้นกำลังทำอยู่ แต่ด้วยรหัสที่มากขึ้นไม่รองรับเบราว์เซอร์บางตัวและมีความยืดหยุ่นน้อยลง ได้รับประโยชน์เพียงปรากฏขึ้นนี้จะมีก็คือว่ามันจะยิงทุกครั้งที่มีการเปลี่ยนแปลงบางสิ่งบางอย่างบนหน้าเว็บซึ่งแน่นอนจะทำลายประสิทธิภาพของคุณ ...
เจสัน

10
คำตอบของคุณในคำตอบไม่ถูกต้อง @Json นี่เป็นวิธีที่ทำได้จริงหากคุณไม่ต้องการเปิดใช้งานทริกเกอร์ด้วยตนเอง แต่เปิดใช้งานโดยอัตโนมัติ มันไม่เพียง แต่ให้ประโยชน์ แต่เป็นประโยชน์เท่านั้นเพราะมันถูกถาม ข้อที่สองนี่เป็นเพียงตัวอย่างและอย่างที่มันบอกไว้ในตัวอย่างไม่แนะนำให้ผนวกผู้ฟังเหตุการณ์ต่อทุกองค์ประกอบ แต่เพียงองค์ประกอบที่คุณต้องการฟังดังนั้นฉันไม่เข้าใจว่าทำไมมันจะทำลายประสิทธิภาพ และสิ่งสุดท้ายคืออนาคตเบราว์เซอร์รุ่นล่าสุดส่วนใหญ่รองรับcaniuse.com/#feat=mutationobserverโปรดทบทวนการแก้ไขมันเป็นวิธีที่ถูกต้อง
นาย Br

1
ห้องสมุด insertionQช่วยให้คุณจับโหนด DOM แสดงขึ้นถ้าพวกเขาตรงกับผู้เลือก DOMMutationObserverมีการสนับสนุนเบราว์เซอร์ที่กว้างขึ้นเพราะมันไม่ได้ใช้
Dan Dascalescu

นี่เป็นทางออกที่ดี โดยเฉพาะอย่างยิ่งสำหรับการเริ่มต้นและหยุดภาพเคลื่อนไหว Canvas หรือ SVG เมื่อมีการเพิ่มหรือลบคลาส ในกรณีของฉันเพื่อให้แน่ใจว่าพวกเขาจะไม่ทำงานเมื่อเลื่อนออกจากมุมมอง หวาน!
BBaysinger

1
สำหรับปี 2562 คำตอบนี้ควรได้รับการยอมรับ ดูเหมือนว่าเบราว์เซอร์หลักทั้งหมดจะสนับสนุนผู้สังเกตการณ์การกลายพันธุ์ในขณะนี้และโซลูชันนี้ไม่ต้องการดำเนินการเหตุการณ์ด้วยตนเองหรือเขียนฟังก์ชันการทำงานขั้นพื้นฐาน jQuery
sp00n

53

ฉันอยากจะแนะนำให้คุณแทนที่ฟังก์ชั่น addClass คุณสามารถทำได้ด้วยวิธีนี้:

// 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
การรวมสิ่งนี้เข้ากับ $ (mySelector) .trigger ('cssClassChanged') ของเจสันจะเป็นวิธีที่ดีที่สุดที่ฉันคิด
kosoant

4
มีปลั๊กอินที่ทำสิ่งนี้โดยทั่วไป: github.com/aheckmann/jquery.hook/blob/master/jquery.hook.js
Jerph

37
ดูเหมือนว่าเป็นวิธีที่ดีในการทำให้ผู้ดูแลสับสนในอนาคต
roufamatic

1
อย่าลืมส่งคืนผลลัพธ์ของวิธีการดั้งเดิม
Petah

1
สำหรับการอ้างอิงคุณกำลังใช้รูปแบบพร็อกซีen.wikipedia.org/wiki/Proxy_pattern
Darius

22

เพียงหลักฐานของแนวคิด:

ดูส่วนสำคัญเพื่อดูคำอธิบายประกอบและติดตามข้อมูลล่าสุด:

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

การใช้งานค่อนข้างง่ายเพียงเพิ่ม listender ใหม่บนโหนดที่คุณต้องการสังเกตและจัดการคลาสตามปกติ:

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

สิ่งนี้ใช้ได้สำหรับฉันยกเว้นฉันไม่สามารถอ่านคลาสเก่าหรือใหม่ด้วยเมธอด removeClass
slashdottir

1
@slashdottir คุณสามารถสร้างซอและอธิบายสิ่งที่ว่าไม่ทำงาน
yckart

ใช่ ... ไม่ทำงาน TypeError: [0] นี้ไม่ได้ถูกกำหนดใน« return this [0] .className; »
Pedro Ferreira

มันใช้งานได้ แต่บางครั้ง (ทำไม?), this[0]ไม่ได้กำหนด ... เพียงแค่แทนที่จุดสิ้นสุดของบรรทัดด้วยvar oldClassและvar newClassด้วยการกำหนดดังต่อไปนี้:= (this[0] ? this[0].className : "");
fvlinden

9

ใช้การกลายพันธุ์ 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
            });

// รหัสใด ๆ ที่ปรับปรุง div มีคลาสที่ต้องการรายการซึ่งอยู่ใน $ target เช่น $ target.addClass ('search-class');


วิธีแก้ปัญหาที่ดีฉันใช้มัน อย่างไรก็ตามฉันกำลังใช้การโต้ตอบและหนึ่งในปัญหาคือฉันเพิ่มผู้สังเกตการณ์ทุกครั้งที่องค์ประกอบของเว็บถูกสร้างขึ้นจริง มีวิธีการตรวจสอบว่ามีผู้สังเกตการณ์อยู่แล้วหรือไม่?
Mikaël Mayer

ฉันคิดว่าสิ่งนี้ได้รับการคิดค่าเสื่อมราคาและไม่ควรใช้ตาม [link] developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
WebDude0482

@ WebDude0482 MutationObserver.observe ไม่ได้ถูกคัดค้านเนื่องจากเป็น "วิธีการอินสแตนซ์" ของ "MutationObserver" ดูdeveloper.mozilla.org/en-US/docs/Web/API/MutationObserver
nu everest

8

change()ไม่เริ่มทำงานเมื่อมีการเพิ่มหรือลบคลาส CSS หรือการเปลี่ยนแปลงคำจำกัดความ มันจะยิงในสถานการณ์เช่นเมื่อเลือกค่าของกล่องที่เลือกหรือไม่ได้เลือก

ฉันไม่แน่ใจว่าคุณหมายถึงถ้าคำจำกัดความของคลาส CSS มีการเปลี่ยนแปลง (ซึ่งสามารถทำได้โดยทางโปรแกรม แต่น่าเบื่อและไม่แนะนำโดยทั่วไป) หรือถ้ามีการเพิ่มหรือลบคลาสในองค์ประกอบ ไม่มีวิธีใดในการบันทึกเหตุการณ์นี้ได้อย่างน่าเชื่อถือ

แน่นอนคุณสามารถสร้างกิจกรรมของคุณเองสำหรับสิ่งนี้ แต่สามารถอธิบายเป็นคำแนะนำได้เท่านั้น มันจะไม่จับรหัสที่คุณไม่ได้ทำ

อีกวิธีหนึ่งคุณสามารถแทนที่ / แทนที่addClass()(ฯลฯ ) วิธีการใน jQuery แต่สิ่งนี้จะไม่จับเมื่อมันทำผ่านทางวานิลลา Javascript (แม้ว่าฉันเดาว่าคุณสามารถแทนที่วิธีเหล่านั้นได้เช่นกัน)


8

มีอีกวิธีหนึ่งที่ไม่มีเรียกเหตุการณ์ที่กำหนดเอง

ปลั๊กอิน jQuery เพื่อตรวจสอบการเปลี่ยนแปลง CSS องค์ประกอบ Html โดย Rick Strahl

ข้อความจากด้านบน

ปลั๊กอินเฝ้าดูทำงานโดยเชื่อมต่อกับ DOMAttrModified ใน FireFox ไปยัง onPropertyChanged ใน Internet Explorer หรือโดยใช้ตัวจับเวลาด้วย setInterval เพื่อจัดการการตรวจจับการเปลี่ยนแปลงสำหรับเบราว์เซอร์อื่น น่าเสียดายที่ WebKit ไม่สนับสนุน DOMAttrModified อย่างสม่ำเสมอในขณะนี้ดังนั้น Safari และ Chrome ต้องใช้กลไก setInterval ที่ช้ากว่า


6

คำถามที่ดี. ฉันใช้เมนู Bootstrap ดร็อปดาวน์และจำเป็นต้องดำเนินการกิจกรรมเมื่อมีการซ่อน Bootstrap Dropdown เมื่อดร็อปดาวน์ถูกเปิด div ที่มีชื่อคลาสของ "button-group" จะเพิ่มคลาสของ "open"; และปุ่มนั้นมีแอตทริบิวต์ "aria-expand" ที่ตั้งค่าเป็นจริง เมื่อดรอปดาวน์ถูกปิดคลาสของ "open" นั้นจะถูกลบออกจาก div ที่มีอยู่และ aria-expanded จะถูกเปลี่ยนจาก true เป็น false

นั่นทำให้ฉันเกิดคำถามนี้ถึงวิธีการตรวจสอบการเปลี่ยนแปลงของชั้นเรียน

ด้วย Bootstrap จะมี "เหตุการณ์ดรอปดาวน์" ที่สามารถตรวจพบได้ มองหา "เหตุการณ์แบบเลื่อนลง" ที่ลิงค์นี้ http://www.w3schools.com/bootstrap/bootstrap_ref_js_dropdown.asp

นี่คือตัวอย่างรวดเร็วและสกปรกของการใช้เหตุการณ์นี้ใน Bootstrap Dropdown

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

ตอนนี้ฉันรู้แล้วว่านี่เป็นคำถามที่เฉพาะเจาะจงกว่าคำถามทั่วไปที่ถูกถาม แต่ฉันคิดว่านักพัฒนาซอฟต์แวร์รายอื่นพยายามตรวจจับเหตุการณ์เปิดหรือปิดด้วย Bootstrap Dropdown จะพบว่ามีประโยชน์ เช่นเดียวกับฉันพวกเขาอาจเริ่มเส้นทางของการพยายามตรวจสอบการเปลี่ยนแปลงระดับองค์ประกอบ (ซึ่งเห็นได้ชัดว่าไม่ง่ายเลย) ขอบคุณ


5

หากคุณต้องการทริกเกอร์เหตุการณ์เฉพาะคุณสามารถแทนที่เมธอด addClass () เพื่อเรียกใช้เหตุการณ์ที่กำหนดเองที่เรียกว่า 'classadded'

นี่คือวิธี:

(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

คุณสามารถผูกเหตุการณ์ DOMSubtreeModified ฉันเพิ่มตัวอย่างที่นี่:

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

หากคุณทราบว่ามีเหตุการณ์ใดที่เปลี่ยนคลาสในตอนแรกคุณอาจใช้ความล่าช้าเล็กน้อยในเหตุการณ์เดียวกันและตรวจสอบคลาส ตัวอย่าง

//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');
});
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.