Запущення подій на зміні класу CSS в jQuery


240

Як я можу запустити подію, якщо клас CSS додано чи змінено за допомогою jQuery? Чи зміна класу CSS запускає change()подію jQuery ?


8
Через 7 років, із досить широким прийняттям MutationObservers у сучасних браузерах, прийнята відповідь тут справді повинна бути оновлена, щоб стати містером Бр .
joelmdev

Це може вам допомогти. stackoverflow.com/questions/2157963/…
PSR

На завершення відповіді Джейсона я знайшов це: stackoverflow.com/a/19401707/1579667
Benj

Відповіді:


223

Щоразу, коли ви змінюєте клас у своєму сценарії, ви можете використовувати a, 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
Здається трохи дивним, що ОП хоче слухати подію "cssClassChanged"; безумовно, клас було додано в результаті якоїсь іншої події "застосунку", наприклад "colourChanged", яка мала б більше сенсу оголошувати тригером, оскільки я не можу уявити, чому б щось цікавило конкретно зміну className, тим більше результат classNameChange напевно?
riscarrott

Якби це цікавило саме зміну className, тоді я б уявив, щоб прикрасити jQuery.fn.addClass для запуску "cssClassChanged" було б більш розумно, як сказав
@micred

5
@riscarrott Я не вважаю, що знаю про застосування ОП. Я просто представляю їм концепцію pub / sub і вони можуть застосовувати її, як би хотіли. Дискусія щодо того, якою має бути фактична назва події, відповідно до кількості наданої інформації - нерозумно.
Джейсон

140

ІМХО кращим рішенням є поєднання двох відповідей @ 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", виявляється, що всі зміни класу (тобто, на будь-якому з елементів моєї сторінки) обробляються цим обробником ... будь-які думки?
Філіпе Коррея

Це добре працює для мене @FilipeCorreia. Чи можете ви надати jsfiddle для копіювання вашої справи?
Араш Мілані

@ Goldentoa11 Ви повинні витратити час на вечір чи вихідні та стежити за тим, що відбувається на типових завантаженнях сторінок, де сильно використовується реклама. Вас буде здути об'єм сценаріїв, які намагаються (іноді невдало найефектнішим способом) зробити такі речі. Я перебігав сторінки, які запускають десятки подій DOMNodeInserted / DOMSubtreeModified в секунду, загалом сотні подій до моменту, коли ви потрапите $(), коли реальна дія починається.
L0j1k

Ймовірно, ви також хочете підтримати запуск тієї самої події на RemoveClass
Дарій

4
Араш, я думаю, я розумію, чому у @FilipeCorreia виникли деякі проблеми з таким підходом: якщо ви пов'язуєте цю cssClassChangedподію з елементом, ви повинні знати, що вона буде звільнена навіть тоді, коли її дитина змінить клас. Тому, якщо, наприклад, ви не хочете запускати цю подію для них, просто додайте щось на зразок $("#YourExampleElementID *").on('cssClassChanged', function(e) { e.stopPropagation(); });після прив’язки. У будь-якому випадку, дуже приємне рішення, дякую!
тонікс

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
    }

});
});

У цьому прикладі слухач подій додається до кожного елемента, але в більшості випадків цього не потрібно (зберегти пам'ять). Додайте цього слухача до "заміни" до елемента, який ви хочете спостерігати.


1
це здається, що він робить саме те, що робить прийнята відповідь, але з більшою кількістю коду, який не підтримується в деяких браузерах, і з меншою гнучкістю. Єдиною перевагою, яка, як видається, є те, що вона вистрілюватиме щоразу, коли на сторінці щось змінюється, що абсолютно знищить вашу ефективність ...
Jason

10
Ваша відповідь у відповідь неправильна @Json, це насправді спосіб зробити це, якщо ви не хочете активувати жоден тригер вручну, але активуйте його автоматично. Це не тільки користь, це й користь, адже це було задано питанням. По-друге, це був лише приклад, і як це, наприклад, сказано, пропонується не додавати слухача подій до кожного елемента, а лише до елементів, які ви хочете слухати, тому я не розумію, чому це знищить продуктивність. І останнє, це майбутнє, більшість останніх браузерів підтримують його, caniuse.com/#feat=mutationobserver. Будь ласка, перегляньте редагування, це правильний шлях.
Пан Бр

1
Бібліотека insertionQ дає змогу фіксувати вузли DOM, відображаючи, чи відповідають вони селектору. Він має більш широку підтримку браузера, оскільки він не використовує DOMMutationObserver.
Дан Даскалеску

Це чудове рішення. Зокрема для запуску та зупинки полотна чи SVG-анімації, коли клас додається чи видаляється. У моєму випадку, щоб переконатися, що вони не працюють, коли прокручується поле зору. Солодке!
BBaysinger

1
На 2019 рік це має бути прийнятою відповіддю. Здається, всі основні браузери зараз підтримують Mutation Observers, і це рішення не вимагає запускати події вручну або перезаписувати основні функції 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') Джейсона було б найкращим підходом, який я думаю.
косоант

4
Є плагін, який робить це загально: github.com/aheckmann/jquery.hook/blob/master/jquery.hook.js
Джерф

37
Здається, це чудовий спосіб заплутати майбутнього технічного персоналу.
roufamatic

1
Не забудьте повернути результат оригінального методу.
Пета

1
Для довідки ви використовуєте шаблон проксі en.wikipedia.org/wiki/Proxy_pattern
Дарій

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

Використання досить просте, просто додайте нового слухача на вузол, за яким потрібно спостерігати, та маніпулюйте класами, як зазвичай:

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] не визначено у «повернути це [0] .className;»
Педро Феррейра

він працює, але іноді (чому?), 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 ('пошуковий клас');


Гарне рішення, я його використовую. Однак я використовую react, і одна з проблем полягає в тому, що я додаю спостерігача кожного разу, коли веб-елемент практично генерується. Чи є спосіб перевірити, чи спостерігач вже присутній?
Mikaël Mayer

Я думаю, це було знецінено, і його не слід використовувати відповідно до [посилання] 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 Elements від Ріка Штраля

Цитуючи зверху

Плагін годинника працює за допомогою підключення до DOMAttrModified у FireFox, до onPropertyChanged в Internet Explorer або за допомогою таймера з setInterval для обробки виявлення змін для інших браузерів. На жаль, на даний момент WebKit не підтримує DOMAttrModified послідовно, тому в даний час Safari і Chrome мають використовувати більш повільний механізм setInterval.


6

Гарне питання. Я використовую спадне меню Bootstrap і мені потрібно було виконати подію, коли спадне завантаження було приховано. Коли відкривається спадне меню, що містить div з назвою класу "кнопка-група" додає клас "відкрито"; а сама кнопка має атрибут "розширена арія", встановлений на "true". Коли спадне меню закрите, цей клас "відкрито" видаляється з містить div, а розширений арією перемикається з істинного в хибне.

Це підштовхнуло мене до цього питання, як виявити зміну класу.

З Bootstrap є "події випадаючих", які можна виявити. Шукайте "Події, що випадають" за цим посиланням. http://www.w3schools.com/bootstrap/bootstrap_ref_js_dropdown.asp

Ось швидкий і брудний приклад використання цієї події у спадному меню Bootstrap.

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

Тепер я розумію, що це більш конкретно, ніж загальне питання, яке задають. Але я думаю, що інші розробники, які намагаються виявити відкриту або закриту подію за допомогою спадного завантаження Bootstrap, вважають це корисним. Як і я, вони можуть спочатку піти шляхом просто намагатися виявити зміну класу елементів (що, мабуть, не так просто). Дякую.


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');
});
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.