События запуска по изменениям класса CSS в jQuery


240

Как я могу запустить событие, если CSS-класс добавлен или изменен с помощью jQuery? Запускает ли изменение класса CSS change()событие jQuery ?


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

Это может помочь вам. stackoverflow.com/questions/2157963/…
— PSR

В завершение ответа Джейсона я нашел это: stackoverflow.com/a/19401707/1579667
— Бендж,

Ответы:


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


4
Какова причина запуска события, которое затем вызывает функцию? Почему бы не вызвать функцию напрямую, а не вызывать ее?
— RamboNo5

43
Запуск - это механизм, который позволит определенным элементам «подписаться» на событие. таким образом, когда событие инициируется, все эти события могут происходить одновременно и запускать соответствующие функции. например, если у меня есть один объект, который меняется с красного на синий, и три объекта, ожидающих его изменения, при его изменении я могу просто вызвать changeColorсобытие, и все те объекты, которые подписываются на это событие, могут реагировать соответствующим образом.
— Джейсон

1
Кажется немного странным, что ОП хочет прослушать событие 'cssClassChanged'; Конечно, класс был добавлен в результате какого-то другого события «application», такого как «colourChanged», которое было бы более целесообразно объявить с помощью триггера, так как я не могу себе представить, почему что-то может быть заинтересовано именно в изменении className, это скорее результат classNameChange наверняка?
— Рисккарротт

Если бы это было именно изменение 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
— Darius

4
Араш, наверное, я понимаю, почему у @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. Пожалуйста, пересмотрите возможность редактирования, это правильный путь.
— г-н Бр

1
Библиотека inserttionQ позволяет вам отследить 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
Для справки, вы используете Proxy Pattern 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.
— Слэшдоттир

1
@slashdottir Можете ли вы создать скрипку и объяснить, что именно не работает .
— yckart

Ага ... не работает. Ошибка типа: this [0] не определено в «return this [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 с классом required-entry, который находится в $ target как $ target.addClass ('search-class');


Хорошее решение, я им пользуюсь. Однако я использую реагирование, и одна из проблем заключается в том, что я добавляю наблюдателя каждый раз, когда веб-элемент создается практически. Есть ли способ проверить, присутствует ли наблюдатель?
— Микаэль Майер

Я думаю, что это устарело и не должно использоваться в соответствии с [link] developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
— WebDude0482

@ WebDude0482 MutationObserver.observe не считается устаревшим, поскольку он является «методом экземпляра» «MutationObserver». См developer.mozilla.org/en-US/docs/Web/API/MutationObserver
— ню Эвереста

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 было скрыто. Когда раскрывающийся список открыт, содержащий div с именем класса «button-group» добавляет класс «open»; и сама кнопка имеет расширенный атрибут aria, установленный в true. Когда раскрывающийся список закрыт, этот класс «open» удаляется из содержащего div, и aria-extended переключается с true на false.

Это привело меня к этому вопросу о том, как обнаружить изменение класса.

С помощью Bootstrap существуют «выпадающие события», которые можно обнаружить. Посмотрите "Dropdown Events" по этой ссылке. http://www.w3schools.com/bootstrap/bootstrap_ref_js_dropdown.asp

Вот быстрый и грязный пример использования этого события в выпадающем меню Bootstrap.

$(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');
});
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.