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

240

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

user237060
источник
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
});
RamboNo5
источник
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');
yckart
источник
Это сработало для меня, за исключением того, что я не мог прочитать старый или новый класс с помощью метода 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.

Amitd
источник
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
    // ...
});
micred
источник
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');
});
Жером Оббиет
источник