jQuery SVG, почему я не могу добавить класс?


289

Я использую 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.');
});

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

8
Таким образом, актуальный вопрос, ПОЧЕМУ я не могу использовать функции класса jQuery *, остается без ответа ... Если я могу получить доступ к свойствам элементов SVG через функцию attr jQuery, почему функции класса * также не могут этого делать? JQuery FAIL?!?
Райан Григгс

2
Серьезно, кто-нибудь знает, ПОЧЕМУ ?
Бен Уайльд

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

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

Ответы:


426

Изменить 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");

В поддержку предложения forresto
обсудите

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

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

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

1
Просто дополнение: я попытался, и JQuery 3 не решил проблему.
Жоао Арруда

76

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

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

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

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

1
Я использовал это, чтобы добавить класс в элемент <g> SVG, отлично работает в Chrome.
Рейчел Уй

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

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

69

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 && ( 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 и лучше подходил для плагинов.


38

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

// 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 от меня :)
Карл

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

17

На основе приведенных выше ответов я создал следующий 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;
};

3
Это полезно, но у алгоритмов есть проблемы: 1. Если строки класса не существует, вы получите «undedfined className» при добавлении класса. 2. Если строка класса содержит класс, который является надмножеством регулярного выражения, вы оставляете мусор в строке класса. Например, если вы попытаетесь удалить класс «собака», а строка класса содержит «собачью еду», в строке класса останется «еда». Вот некоторые исправления: jsfiddle.net/hellobrett/c2c2zfto
Бретт

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

13

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

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

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


Добавил - все еще не работает. Я просматривал документацию на сайте 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');

Весь кредит идет на Тодда Мото .


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

5

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

пример

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 на странице, поэтому я просто решил использовать его. Очень полезно :)
Muers

5

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

В выпущенном посте jQuery 2.2 и 1.12 есть следующая цитата:

Хотя jQuery - это библиотека HTML, мы согласились, что поддержка классов для элементов SVG может быть полезной. Теперь пользователи смогут вызывать методы .addClass () , .removeClass () , .toggleClass () и .hasClass () в SVG. jQuery теперь меняет атрибут класса, а не свойство 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>


3

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

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

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

Код:

var classNameContainsClass = function(fullClassName, className) {
    return !!fullClassName &&
           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) && 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 && 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");

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

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

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

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

2

Я использую Snap.svg, чтобы добавить класс и SVG.

var jimmy = Snap(" .jimmy ")

jimmy.addClass("exampleClass");

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


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

1

Одним из обходных путей может быть добавление класса в контейнер элемента 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>


1

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

$.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')

0

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


-1

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.