jQuery SVG, почему я не могу addClass?

#javascript #jquery #css #animation #svg

#javascript #jquery #css #svg #jquery-svg

Вопрос:

Я использую jQuery SVG. Я не могу добавить или удалить класс к объекту. Кто-нибудь знает о моей ошибке?

SVG:

 <rect class="jimmy" id="p5" x="200" y="200" width="100" height="100" />
 

jQuery, который не добавит класс:

 $(".jimmy").click(function() {
    $(this).addClass("clicked");
});
 

Я знаю, что SVG и jQuery отлично работают вместе, потому что я могу настроить таргетинг на объект и выдать предупреждение при нажатии на него:

 $(".jimmy").click(function() {
    alert('Handler for .click() called.');
});
 

Комментарии:

1. Хорошая статья здесь: toddmotto.com /…

2. Итак, актуальный вопрос о ТОМ, ПОЧЕМУ я не могу использовать функции класса jQuery *, остается без ответа… Если я могу получить доступ к свойствам элементов SVG с помощью функции jQuery attr, почему функции *class не могут этого сделать? СБОЙ jQuery ?!?

3. Серьезно, кто-нибудь знает ПОЧЕМУ ??

4. Крошечная библиотека, которая добавляет эту функциональность для файлов SVG: github.com/toddmotto/lunar

5. «Почему» связано с тем, что jQuery использует свойство «className», которое является строковым свойством для элементов HTML, но является анимированной строкой SVG для элементов SVG. Который не может быть назначен like для элементов HTML. Таким образом, код jQuery взрывается при попытке присвоить значение.

Ответ №1:

Редактировать 2016: прочитайте следующие два ответа.

  • jQuery 3 устраняет основную проблему
  • Vanilla JS: element.classList.add('newclass') работает в современных браузерах

jQuery (менее 3) не может добавить класс в SVG.

.attr() работает с SVG, поэтому, если вы хотите зависеть от jQuery:

 // Instead of .addClass("newclass")
$("#item").attr("class", "oldclass newclass");
// Instead of .removeClass("newclass")
$("#item").attr("class", "oldclass");
 

И если вы не хотите зависеть от jQuery:

 var element = document.getElementById("item");
// Instead of .addClass("newclass")
element.setAttribute("class", "oldclass newclass");
// Instead of .removeClass("newclass")
element.setAttribute("class", "oldclass");
 

Комментарии:

1. В поддержку предложения forresto — discusscode.blogspot.in/2012/08 /…

2. Идеальный. это должно быть ответом. хотя РЕАЛЬНЫЙ ответ заключается в том, чтобы jquery включал это по умолчанию, по крайней мере, как параметр или что-то в этом роде.

3. .attr("class", "oldclass") (или более громоздкий .setAttribute("class", "oldclass") ) на самом деле не эквивалентен удалению newclass !

4. Отличный ответ (и v. хорошая техника)… но было бы неплохо отредактировать вопрос так, чтобы он начинался с указания, что jquery не может добавить класс в SVG (если это так… как кажется)?

5. Просто дополнение: я попробовал, и jQuery 3 не устранил проблему.

Ответ №2:

В DOM API есть element.classList, который работает как для элементов HTML, так и для SVG. Нет необходимости в плагине jQuery SVG или даже jQuery.

 $(".jimmy").click(function() {
    this.classList.add("clicked");
});
 

Комментарии:

1. Я протестировал это, и .classList, похоже, не определен для подэлементов svg, по крайней мере, в Chrome.

2. Работает для меня в Chrome. У меня есть SVG, встроенный в HTML, поэтому структура моего документа выглядит следующим образом: <html><svg><circle class=»jimmy» …></svg></html>

3. Я использовал это для добавления класса в элемент <g> SVG, отлично работает в Chrome.

4. IE не реализует .classList и API для элементов SVG. Смотрите caniuse.com/#feat=classlist и список «известных проблем». Это также упоминает браузеры WebKit.

5. И со временем этот ответ становится еще более правильным (и лучшим ответом)

Ответ №3:

В jQuery 3 этой проблемы нет

Одним из изменений, перечисленных в версиях jQuery 3.0, является:

добавить манипуляцию с классом SVG (#2199, 20aaed3)

Одним из решений этой проблемы было бы обновление до jQuery 3. Это отлично работает:

 var flip = true;
setInterval(function() {
    // Could use toggleClass, but demonstrating addClass.
    if (flip) {
        $('svg circle').addClass('green');
    }
    else {
        $('svg circle').removeClass('green');
    }
    flip = !flip;
}, 1000); 
 svg circle {
    fill: red;
    stroke: black;
    stroke-width: 5;
}
svg circle.green {
    fill: green;
} 
 <script src="https://code.jquery.com/jquery-3.0.0.min.js"></script>

<svg>
    <circle cx="50" cy="50" r="25" />
</svg> 

Проблема:

Причина, по которой функции управления классом jQuery не работают с элементами SVG, заключается в том, что версии jQuery до версии 3.0 используют className свойство для этих функций.

Выдержка из jQuery attributes/classes.js :

 cur = elem.nodeType === 1 amp;amp; ( elem.className ?
    ( " "   elem.className   " " ).replace( rclass, " " ) :
    " "
);
 

Это ведет себя так, как ожидалось для элементов HTML, но для элементов SVG className немного отличается. Для элемента SVG className это не строка, а экземпляр SVGAnimatedString .

Рассмотрим следующий код:

 var test_div = document.getElementById('test-div');
var test_svg = document.getElementById('test-svg');
console.log(test_div.className);
console.log(test_svg.className); 
 #test-div {
    width: 200px;
    height: 50px;
    background: blue;
} 
 <div class="test div" id="test-div"></div>

<svg width="200" height="50" viewBox="0 0 200 50">
  <rect width="200" height="50" fill="green" class="test svg" id="test-svg" />
</svg> 

Если вы запустите этот код, вы увидите что-то вроде следующего в консоли разработчика.

 test div
SVGAnimatedString { baseVal="test svg",  animVal="test svg"}
 

Если бы мы должны были преобразовать этот SVGAnimatedString объект в строку, как это делает jQuery, мы бы это сделали [object SVGAnimatedString] , и именно здесь jQuery терпит неудачу.

Как плагин jQuery SVG обрабатывает это:

Плагин jQuery SVG решает эту проблему, исправляя соответствующие функции для добавления поддержки SVG.

Выдержка из jQuery SVG jquery.svgdom.js :

 function getClassNames(elem) {
    return (!$.svg.isSVGElem(elem) ? elem.className :
        (elem.className ? elem.className.baseVal : elem.getAttribute('class'))) || '';
}
 

Эта функция определит, является ли элемент элементом SVG, и если это так, она будет использовать baseVal свойство SVGAnimatedString объекта, если оно доступно, прежде чем вернуться к class атрибуту.

Историческая позиция jQuery по этому вопросу:

В настоящее время jQuery перечисляет эту проблему на своей странице «Не исправит«. Вот соответствующие части.

Ошибки SVG / VML или элементов с пространством имен

jQuery — это в первую очередь библиотека для HTML DOM, поэтому большинство проблем, связанных с документами SVG / VML или элементами с пространством имен, выходят за рамки. Мы пытаемся решить проблемы, которые «просачиваются» в HTML-документы, такие как события, которые всплывают из SVG.

Очевидно, jQuery считал полную поддержку SVG вне рамок ядра jQuery и лучше подходит для плагинов.

Ответ №4:

Если у вас есть динамические классы или вы не знаете, какие классы уже могут быть применены, то этот метод, я считаю, является лучшим подходом:

 // addClass
$('path').attr('class', function(index, classNames) {
    return classNames   ' class-name';
});

// removeClass
$('path').attr('class', function(index, classNames) {
    return classNames.replace('class-name', '');
});
 

Комментарии:

1. Это было спасением для меня, так как мне нужно было изменить много SVG-элементов, чтобы добиться цели. 999 от меня 🙂

2. Трудно поверить, что jQuery все еще будет настаивать на этом, даже спустя 2 года и выпуск 2.x.x. Ваше исправление, nav, спасло мне день. Остальные исправления были излишне сложными. Спасибо!

Ответ №5:

Основываясь на приведенных выше ответах, я создал следующий API

 /*
 * .addClassSVG(className)
 * Adds the specified class(es) to each of the set of matched SVG elements.
 */
$.fn.addClassSVG = function(className){
    $(this).attr('class', function(index, existingClassNames) {
        return ((existingClassNames !== undefined) ? (existingClassNames   ' ') : '')   className;
    });
    return this;
};

/*
 * .removeClassSVG(className)
 * Removes the specified class to each of the set of matched SVG elements.
 */
$.fn.removeClassSVG = function(className){
    $(this).attr('class', function(index, existingClassNames) {
        var re = new RegExp('\b'   className   '\b', 'g');
        return existingClassNames.replace(re, '');
    });
    return this;
};
 

Комментарии:

1. Это полезно, но у алгоритмов есть проблемы: 1. Если нет существующей строки класса, вы получаете ‘undefined className’ при добавлении класса. 2. Если строка класса содержит класс, который является надмножеством регулярного выражения, вы оставляете мусор в строке класса. Например, если вы попытаетесь удалить класс ‘dog’, а строка класса содержит ‘dogfood’, в строке класса останется ‘food’. Вот некоторые исправления: jsfiddle.net/hellobrett/c2c2zfto

2. Это не идеальное решение. Он заменяет все слова, содержащие класс, который вы хотите удалить.

3. сообщение редактировалось с учетом обеих вышеуказанных проблем.

4. Возможно, вы захотите добавить ` *` в начале регулярного выражения, чтобы не оставлять пробелы при удалении класса

Ответ №6:

После загрузки jquery.svg.js вы должны загрузить этот файл : http://keith-wood.name/js/jquery.svgdom.js .

Источник: http://keith-wood.name/svg.html#dom

Рабочий пример: http://jsfiddle.net/74RbC/99 /

Комментарии:

1. Добавил его — похоже, все еще не работает. Я просматривал документацию на сайте jquery SVG, но там нет четкого объяснения того, что вам нужно сделать, чтобы использовать его дополнительные функции.

Ответ №7:

Просто добавьте отсутствующий конструктор прототипа ко всем узлам SVG:

 SVGElement.prototype.hasClass = function (className) {
  return new RegExp('(\s|^)'   className   '(\s|$)').test(this.getAttribute('class'));
};

SVGElement.prototype.addClass = function (className) { 
  if (!this.hasClass(className)) {
    this.setAttribute('class', this.getAttribute('class')   ' '   className);
  }
};

SVGElement.prototype.removeClass = function (className) {
  var removedClass = this.getAttribute('class').replace(new RegExp('(\s|^)'   className   '(\s|$)', 'g'), '$2');
  if (this.hasClass(className)) {
    this.setAttribute('class', removedClass);
  }
};
 

Затем вы можете использовать его таким образом, не требуя jQuery:

 this.addClass('clicked');

this.removeClass('clicked');
 

Вся заслуга принадлежит Тодду Мото.

Комментарии:

1. ie11 захлебывается этим кодом. это полизаполнение было лучшим маршрутом: github.com/eligrey/classList.js

Ответ №8:

jQuery не поддерживает классы элементов SVG. Вы можете получить элемент напрямую $(el).get(0) и использовать classList and add / remove . В этом тоже есть хитрость: самый верхний элемент SVG на самом деле является обычным объектом DOM и может использоваться как любой другой элемент в jQuery. В моем проекте я создал это, чтобы позаботиться о том, что мне было нужно, но в документации, предоставленной в сети разработчиков Mozilla, есть прокладка, которую можно использовать в качестве альтернативы.

пример

 function addRemoveClass(jqEl, className, addOrRemove) 
{
  var classAttr = jqEl.attr('class');
  if (!addOrRemove) {
    classAttr = classAttr.replace(new RegExp('\s?'   className), '');
    jqEl.attr('class', classAttr);
  } else {
    classAttr = classAttr   (classAttr.length === 0 ? '' : ' ')   className;
    jqEl.attr('class', classAttr);
  }
}
 

Альтернатива, более сложная, заключается в использовании D3.js вместо этого в качестве вашего селекторного двигателя. В моих проектах есть диаграммы, построенные с его помощью, поэтому они также входят в область моего приложения. D3 корректно изменяет атрибуты класса ванильных элементов DOM и элементов SVG. Хотя добавление D3 только для этого случая, вероятно, было бы излишним.

 d3.select(el).classed('myclass', true);
 

Комментарии:

1. Спасибо за этот бит d3… На самом деле у меня уже был d3 на странице, поэтому я просто решил использовать это. Очень полезно 🙂

Ответ №9:

jQuery 2.2 поддерживает управление классом SVG

Опубликованный пост jQuery 2.2 и 1.12 содержит следующую цитату:

Хотя jQuery — это библиотека HTML, мы согласились, что поддержка классов для элементов SVG может быть полезной. Теперь пользователи смогут вызывать методы .addClass(), .removeClass(), .toggleClass() и .hasClass() в SVG. Теперь jQuery изменяет атрибут class, а не свойство className. Это также делает методы класса пригодными для использования в общих XML-документах. Имейте в виду, что многие другие вещи не будут работать с SVG, и мы по-прежнему рекомендуем использовать библиотеку, предназначенную для SVG, если вам нужно что-то помимо манипулирования классами.

Пример использования jQuery 2.2.0

Он проверяет:

  • .addClass()
  • .removeClass()
  • .hasClass()

Если вы нажмете на этот маленький квадрат, он изменит свой цвет, потому class что атрибут добавлен / удален.

 $("#x").click(function() {
    if ( $(this).hasClass("clicked") ) {
        $(this).removeClass("clicked");
    } else {
        $(this).addClass("clicked");
    }
}); 
 .clicked {
    fill: red !important;  
} 
 <html>

<head>
    <script src="https://code.jquery.com/jquery-2.2.0.js"></script>
</head>

<body>
    <svg width="80" height="80">
        <rect id="x" width="80" height="80" style="fill:rgb(0,0,255)" />
    </svg>
</body>

</html> 

Ответ №10:

Я использую Snap.svg для добавления класса в и SVG.

 var jimmy = Snap(" .jimmy ")

jimmy.addClass("exampleClass");
 

http://snapsvg.io/docs/#Element.addClass

Комментарии:

1. Хотя эта ссылка может ответить на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы только для ссылок могут стать недействительными, если связанная страница изменится.

Ответ №11:

Вот мой довольно неэлегантный, но рабочий код, который решает следующие проблемы (без каких-либо зависимостей):

  • classList не существует для <svg> элементов в IE
  • className не представляет class атрибут для <svg> элементов в IE
  • Старый IE сломан getAttribute() и setAttribute() реализации

Он использует classList , где это возможно.

Код:

 var classNameContainsClass = function(fullClassName, className) {
    return !!fullClassName amp;amp;
           new RegExp("(?:^|\s)"   className   "(?:\s|$)").test(fullClassName);
};

var hasClass = function(el, className) {
    if (el.nodeType !== 1) {
        return false;
    }
    if (typeof el.classList == "object") {
        return (el.nodeType == 1) amp;amp; el.classList.contains(className);
    } else {
        var classNameSupported = (typeof el.className == "string");
        var elClass = classNameSupported ? el.className : el.getAttribute("class");
        return classNameContainsClass(elClass, className);
    }
};

var addClass = function(el, className) {
    if (el.nodeType !== 1) {
        return;
    }
    if (typeof el.classList == "object") {
        el.classList.add(className);
    } else {
        var classNameSupported = (typeof el.className == "string");
        var elClass = classNameSupported ?
            el.className : el.getAttribute("class");
        if (elClass) {
            if (!classNameContainsClass(elClass, className)) {
                elClass  = " "   className;
            }
        } else {
            elClass = className;
        }
        if (classNameSupported) {
            el.className = elClass;
        } else {
            el.setAttribute("class", elClass);
        }
    }
};

var removeClass = (function() {
    function replacer(matched, whiteSpaceBefore, whiteSpaceAfter) {
        return (whiteSpaceBefore amp;amp; whiteSpaceAfter) ? " " : "";
    }

    return function(el, className) {
        if (el.nodeType !== 1) {
            return;
        }
        if (typeof el.classList == "object") {
            el.classList.remove(className);
        } else {
            var classNameSupported = (typeof el.className == "string");
            var elClass = classNameSupported ?
                el.className : el.getAttribute("class");
            elClass = elClass.replace(new RegExp("(^|\s)"   className   "(\s|$)"), replacer);
            if (classNameSupported) {
                el.className = elClass;
            } else {
                el.setAttribute("class", elClass);
            }
        }
    }; //added semicolon here
})();
 

Пример использования:

 var el = document.getElementById("someId");
if (hasClass(el, "someClass")) {
    removeClass(el, "someClass");
}
addClass(el, "someOtherClass");
 

Комментарии:

1. Вы тестировали его с помощью IE7? Потому что LTE IE7 требует, чтобы strAttributeName было «className» при установке, получении или удалении атрибута КЛАССА .

2. @Knu: Это определенно работает в IE 6 и 7 для обычных HTML-элементов, потому что в этих браузерах он использует className свойство, а не пытается установить атрибут. Причина, по которой «LTE IE7 требует, чтобы strAttributeName было «className» при установке, получении или удалении атрибута КЛАССА», заключается в том, что эти браузеры имеют неработающую реализацию getAttribute(x) и setAttribute(x) просто получают или устанавливают свойство с помощью name x , поэтому проще и удобнее устанавливать свойство напрямую.

3. @Knu: ХОРОШО. Я не был уверен, поскольку SVG не поддерживается в IE 7, поэтому я не уверен, чего вы надеетесь достичь, включив <svg> элемент на страницу, предназначенную для работы в IE 7. Как бы то ни было, мой код успешно добавляет атрибут class к <svg> элементам в IE 7, поскольку такой элемент ведет себя как любой другой пользовательский элемент.

4. Совершенно забыл об этом, спасибо за напоминание. Я попытаюсь получить в свои руки Adobe SVG Viewer, чтобы проверить, работает ли он так, как задумано.

Ответ №12:

Одним из обходных путей может быть addClass в контейнер элемента svg:

 $('.svg-container').addClass('svg-red'); 
 .svg-red svg circle{
    fill: #ED3F32;
} 
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="svg-container">
  <svg height="40" width="40">
      <circle cx="20" cy="20" r="20"/>
  </svg>
</div> 

Ответ №13:

Я написал это в своем проекте, и это работает … наверное;)

 $.fn.addSvgClass = function(className) {

    var attr
    this.each(function() {
        attr = $(this).attr('class')
        if(attr.indexOf(className) < 0) {
            $(this).attr('class', attr ' ' className  ' ')
        }
    })
};    

$.fn.removeSvgClass = function(className) {

    var attr
    this.each(function() {
        attr = $(this).attr('class')
        attr = attr.replace(className , ' ')
        $(this).attr('class' , attr)
    })
};    
 

примеры

 $('path').addSvgClass('fillWithOrange')
$('path').removeSvgClass('fillWithOrange')
 

Ответ №14:

Вдохновленный ответами выше, особенно Сагар Галой, я создал этот API. Вы можете использовать его, если не хотите или не можете обновить свою версию jquery.

Ответ №15:

Или просто используйте методы DOM старой школы, когда у JQ где-то посередине есть обезьяна.

 var myElement = $('#my_element')[0];
var myElClass = myElement.getAttribute('class').split(/s /g);
//splits class into an array based on 1  white space characters

myElClass.push('new_class');

myElement.setAttribute('class', myElClass.join(' '));

//$(myElement) to return to JQ wrapper-land
 

Изучите людей DOM. Даже в framework-palooza 2016 года это помогает довольно регулярно. Кроме того, если вы когда-нибудь услышите, как кто-то сравнивает DOM со сборкой, ударьте их для меня.