jQuery SVG, dlaczego nie mogę dodać klasy?

289

Korzystam z jQuery SVG. Nie mogę dodać ani usunąć klasy do obiektu. Czy ktoś zna mój błąd?

SVG:

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

JQuery, która nie doda klasy:

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

Wiem, że jQuery SVG i wspólnie pracują grzywny, bo może kierować obiekt i ognia alert, gdy jest kliknięty:

$(".jimmy").click(function() {
    alert('Handler for .click() called.');
});
Don P.
źródło
4
Dobry artykuł tutaj: toddmotto.com/…
Kris
8
Tak więc faktyczne pytanie DLACZEGO nie mogę użyć funkcji klasy jQuery * pozostaje bez odpowiedzi ... Jeśli mogę uzyskać dostęp do właściwości elementów SVG za pomocą funkcji attr jQuery, dlaczego funkcje * klasy również tego nie robią? jQuery FAIL?!?
Ryan Griggs,
2
Poważnie, czy ktoś wie DLACZEGO ?
Ben Wilde,
Mała biblioteka, która dodaje tę funkcjonalność do plików SVG: github.com/toddmotto/lunar
Chuck Le Butt
6
„Dlaczego” to dlatego, że jQuery używa właściwości „className”, która jest właściwością string dla elementów HTML, ale jest animowanym ciągiem SVG dla elementów SVG. Którego nie można przypisać do elementów HTML. Tak więc kod jQuery wysadza się w powietrze, próbując przypisać wartość.
Jon Watte

Odpowiedzi:

426

Edytuj 2016: przeczytaj następne dwie odpowiedzi.

  • JQuery 3 rozwiązuje podstawowy problem
  • Vanilla JS: element.classList.add('newclass')działa w nowoczesnych przeglądarkach

JQuery (mniej niż 3) nie może dodać klasy do SVG.

.attr() współpracuje z SVG, więc jeśli chcesz polegać na jQuery:

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

A jeśli nie chcesz polegać na jQuery:

var element = document.getElementById("item");
// Instead of .addClass("newclass")
element.setAttribute("class", "oldclass newclass");
// Instead of .removeClass("newclass")
element.setAttribute("class", "oldclass");
forresto
źródło
Na poparcie sugestii forresto
omówieniecode.blogspot.in/2012/
idealny. to powinna być odpowiedź. chociaż PRAWDZIWA odpowiedź to domyślna jquery, przynajmniej jako ustawienie lub coś takiego.
AwokeKnowing
2
.attr("class", "oldclass")(lub bardziej kłopotliwe .setAttribute("class", "oldclass")) tak naprawdę nie jest równoważne z usunięciem newclass!
Tom
Świetna odpowiedź (i technika v.nice) ... ale czy pomysłem byłoby edytowanie pytania, aby zaczynało się od stwierdzenia, że ​​jquery nie może dodać klasy do SVG (jeśli tak jest ... jak się wydaje ... )?
byronyasgur
1
Tylko dodatek: próbowałem i JQuery 3 nie naprawił problemu.
Joao Arruda
76

Jest element.classList w API DOM, który pracuje dla obu elementów HTML i SVG. Nie potrzebujesz wtyczki jQuery SVG ani nawet jQuery.

$(".jimmy").click(function() {
    this.classList.add("clicked");
});
Tomas Mikula
źródło
3
Przetestowałem to i wydaje się, że .classList jest niezdefiniowany dla podelementów svg, przynajmniej w Chrome.
Chris Jaynes
3
Działa dla mnie w Chrome. Mam plik SVG osadzony w HTML, więc moja struktura dokumentu wygląda następująco: <html> <svg> <circle class = "jimmy" ...> </svg> </html>
Tomas Mikula
1
Użyłem tego, aby dodać klasę w elemencie <g> SVG, działa dobrze w Chrome.
Rachelle Uy
20
IE nie implementuje .classList i API w SVG Elements. Zobacz caniuse.com/#feat=classlist i listę „znanych problemów”. To wspomina także przeglądarki WebKit.
Shenme
9
Z biegiem czasu ta odpowiedź staje się jeszcze bardziej poprawna (i najlepsza odpowiedź)
Gerrat
69

jQuery 3 nie ma tego problemu

Jedną ze zmian wymienionych w wersjach jQuery 3.0 jest:

dodaj manipulację klasy SVG ( # 2199 , 20aaed3 )

Jednym rozwiązaniem tego problemu byłoby uaktualnienie do jQuery 3. Działa świetnie:

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>


Problem:

Powodem, dla którego funkcje manipulacji klasą jQuery nie działają z elementami SVG, jest to, że wersje jQuery wcześniejsze niż 3.0 używają tej classNamewłaściwości do tych funkcji.

Fragment jQuery attributes/classes.js:

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

Zachowuje się to zgodnie z oczekiwaniami w przypadku elementów HTML, ale w przypadku elementów SVG classNamejest nieco inaczej. Dla elementu SVG classNamenie jest ciągiem, lecz wystąpieniem SVGAnimatedString.

Rozważ następujący kod:

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>

Jeśli uruchomisz ten kod, zobaczysz w konsoli programisty coś takiego:

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

Gdybyśmy rzutowali ten SVGAnimatedStringobiekt na ciąg znaków, tak jak robi to jQuery, mielibyśmy [object SVGAnimatedString], czyli tam, gdzie jQuery zawodzi.

Jak radzi sobie z tym wtyczka jQuery SVG:

Wtyczka jQuery SVG działa w ten sposób, łatając odpowiednie funkcje w celu dodania obsługi SVG.

Fragment jQuery SVG jquery.svgdom.js:

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

Ta funkcja wykryje, czy element jest elementem SVG, a jeśli tak, użyje baseValwłaściwości SVGAnimatedStringobiektu, jeśli jest dostępny, przed powrotem do classatrybutu.

Historyczne stanowisko jQuery w tej kwestii:

jQuery obecnie wyświetla ten problem na stronie Nie naprawi . Oto odpowiednie części.

Błędy elementów SVG / VML lub elementów

jQuery jest przede wszystkim biblioteką dla HTML DOM, więc większość problemów związanych z dokumentami SVG / VML lub elementami przestrzeni nazw jest poza zakresem. Staramy się rozwiązywać problemy, które „przenikają” do dokumentów HTML, takie jak zdarzenia, które wyskakują z SVG.

Najwyraźniej jQuery uznało pełną obsługę SVG poza zakresem rdzenia jQuery i lepiej nadaje się do wtyczek.

Alexander O'Mara
źródło
38

Jeśli masz klasy dynamiczne lub nie wiesz, które klasy można już zastosować, to ta metoda jest moim zdaniem najlepsza:

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

// removeClass
$('path').attr('class', function(index, classNames) {
    return classNames.replace('class-name', '');
});
nav
źródło
1
To był dla mnie ratownik, ponieważ musiałem zmienić wiele elementów SVG, aby załatwić sprawę. +999 ode mnie :)
Karl
Trudno uwierzyć, że jQuery nadal miałoby w sobie coś takiego, nawet po 2 latach i wydaniu 2.xx Twoja poprawka, nawigacja, uratowała mi dzień. Pozostałe poprawki były niepotrzebnie złożone. Dzięki!
bezkonkurencyjne tworzenie
17

Na podstawie powyższych odpowiedzi stworzyłem następujący interfejs 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;
};
Gala Sagar
źródło
3
Jest to pomocne, ale algorytmy mają problemy: 1. Jeśli nie ma istniejącego ciągu klas, po dodaniu klasy otrzymujesz „nieokreśloną nazwę klasy”. 2. Jeśli ciąg klasy zawiera klasę będącą nadzbiorem wyrażenia regularnego, pozostawiasz śmieci w ciągu klasy. Na przykład, jeśli spróbujesz usunąć klasę „pies”, a łańcuch klasy zawiera „karmę dla psów”, pozostanie „łańcuch” w łańcuchu klasy. Oto kilka poprawek: jsfiddle.net/hellobrett/c2c2zfto
Brett
1
To nie jest idealne rozwiązanie. Zastępuje wszystkie słowa zawierające klasę, którą chcesz usunąć.
tom10271,
13

Po załadowaniu jquery.svg.jsnależy załadować ten plik http://keith-wood.name/js/jquery.svgdom.js.

Źródło: http://keith-wood.name/svg.html#dom

Przykład roboczy: http://jsfiddle.net/74RbC/99/

bennedich
źródło
Dodano go - nadal nie działa. Przeglądałem dokumentację witryny jquery SVG, ale nie ma jasnego wyjaśnienia, co należy zrobić, aby skorzystać z jej dodatkowej funkcjonalności.
Don P
7

Wystarczy dodać brakujący konstruktor prototypów do wszystkich węzłów 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);
  }
};

Możesz następnie użyć go w ten sposób, nie wymagając jQuery:

this.addClass('clicked');

this.removeClass('clicked');

Wszystkie zasługi należą się Toddowi Moto .

Ziad
źródło
tj. 11 dławików w tym kodzie. ten polypełniacz był lepszą drogą: github.com/eligrey/classList.js
ryanrain
5

jQuery nie obsługuje klas elementów SVG. Możesz pobrać element bezpośrednio $(el).get(0)i użyć classListi add / remove. Jest w tym także pewna sztuczka polegająca na tym, że najwyższy element SVG jest w rzeczywistości normalnym obiektem DOM i może być używany jak każdy inny element w jQuery. W swoim projekcie stworzyłem to, aby zadbać o to, czego potrzebowałem, ale dokumentacja w sieci Mozilla Developer Network ma pewien podkład, który można wykorzystać jako alternatywę.

przykład

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

Alternatywą jest jeszcze trudniejsze użycie zamiast tego silnika D3.js. Moje projekty mają wykresy, które są z nim zbudowane, więc jest to również w zakresie mojej aplikacji. D3 poprawnie modyfikuje atrybuty klasy waniliowych elementów DOM i elementów SVG. Chociaż dodanie D3 tylko dla tego przypadku byłoby prawdopodobnie przesadą.

d3.select(el).classed('myclass', true);
QueueHammer
źródło
1
Dzięki za ten kawałek d3 ... Właściwie miałem już d3 na stronie, więc po prostu zdecydowałem się go użyć. Bardzo przydatne :)
Muers
5

jQuery 2.2 obsługuje manipulację klasami SVG

JQuery 2.2 i 1.12 Wydany po obejmuje następujący cytat:

Chociaż jQuery jest biblioteką HTML, zgodziliśmy się, że wsparcie klasy dla elementów SVG może być przydatne. Użytkownicy będą mogli teraz wywoływać metody .addClass () , .removeClass () , .toggleClass () i .hasClass () w SVG. jQuery zmienia teraz atrybut class zamiast właściwości className . Dzięki temu metody klasowe są użyteczne w ogólnych dokumentach XML. Pamiętaj, że wiele innych rzeczy nie będzie działać z SVG i nadal zalecamy korzystanie z biblioteki dedykowanej dla SVG, jeśli potrzebujesz czegoś poza manipulacjami klasowymi.

Przykład użycia jQuery 2.2.0

Testuje:

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

Kliknięcie tego małego kwadratu spowoduje zmianę jego koloru, ponieważ classatrybut zostanie dodany / usunięty.

$("#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>

ROMANIA_inżynier
źródło
3

Oto mój raczej nieelegancki, ale działający kod, który zajmuje się następującymi problemami (bez żadnych zależności):

  • classListnie istnieje na <svg>elementach w IE
  • classNamenie reprezentuje classatrybutu na <svg>elementach w IE
  • Stare IE jest zepsute getAttribute()i setAttribute()implementacje

Używa classListtam, gdzie to możliwe.

Kod:

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

Przykładowe użycie:

var el = document.getElementById("someId");
if (hasClass(el, "someClass")) {
    removeClass(el, "someClass");
}
addClass(el, "someOtherClass");
Tim Down
źródło
Czy przetestowałeś go przy użyciu IE7? Ponieważ LTE IE7 wymaga, aby parametr strAttributeName był „className” podczas ustawiania, pobierania lub usuwania atrybutu CLASS .
Knu
@Knu: Zdecydowanie działa w IE 6 i 7 dla zwykłych elementów HTML, ponieważ w tych przeglądarkach używa classNamewłaściwości zamiast próbować ustawić atrybut. Powodem, dla którego „LTE IE7 wymaga, aby parametr strAttributeName był„ className ”podczas ustawiania, pobierania lub usuwania atrybutu CLASS”, jest taki, że przeglądarki te mają wadliwą implementację getAttribute(x)i setAttribute(x)po prostu pobierają lub ustawiają właściwość z nazwą x, dzięki czemu jest łatwiejsza i bardziej kompatybilna aby ustawić właściwość bezpośrednio.
Tim Down
@Knu: OK. Nie byłem pewien, ponieważ SVG nie jest obsługiwany w IE 7, więc nie jestem pewien, co możesz osiągnąć, umieszczając <svg>element na stronie zaprojektowanej do pracy w IE 7. Za to, co jest warte, mój kod dodaje z powodzeniem atrybut klasy do <svg>elementów w IE 7, ponieważ taki element zachowuje się jak każdy inny niestandardowy element.
Tim Down
Całkowicie zapomniałem o tym dzięki za przypomnienie. Zrobię próbę pobrania Adobe SVG Viewer, aby sprawdzić, czy działa zgodnie z przeznaczeniem.
Knu,
2

Używam Snap.svg, aby dodać klasę i SVG.

var jimmy = Snap(" .jimmy ")

jimmy.addClass("exampleClass");

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

cjohndesign
źródło
1
Chociaż ten link może odpowiedzieć na pytanie, lepiej jest dołączyć tutaj istotne części odpowiedzi i podać link w celach informacyjnych. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli połączona strona ulegnie zmianie.
Tristan,
1

Jednym z obejść może być dodanieClass do kontenera elementu 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>

claytronicon
źródło
1

Napisałem to w moim projekcie i działa ... prawdopodobnie;)

$.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)
    })
};    

przykłady

$('path').addSvgClass('fillWithOrange')
$('path').removeSvgClass('fillWithOrange')
Dariusz Majchrzak
źródło
0

Zainspirowany powyższymi odpowiedziami, zwłaszcza Sagar Gala, stworzyłem ten interfejs API . Możesz go użyć, jeśli nie chcesz lub nie możesz zaktualizować swojej wersji jquery.

Stonecrusher
źródło
-1

Lub po prostu użyj old-schoolowych metod DOM, gdy JQ ma gdzieś pośrodku małpę.

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

Naucz się ludzi DOM. Nawet w 2016 roku-palooza pomaga dość regularnie. Ponadto, jeśli kiedykolwiek usłyszysz, że ktoś porównuje DOM do zestawu, wykop go dla mnie.

Erik Reppen
źródło