jQuery SVG, perché non posso aggiungereClass?


289

Sto usando jQuery SVG. Non riesco ad aggiungere o rimuovere una classe a un oggetto. Qualcuno sa il mio errore?

SVG:

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

Il jQuery che non aggiungerà la classe:

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

So che SVG e jQuery stanno funzionando bene insieme perché posso scegliere come target l'oggetto e lanciare un avviso quando viene cliccato:

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

4
Buon articolo qui: toddmotto.com/…
Kris

8
Quindi la vera domanda del PERCHÉ non posso usare le funzioni della classe jQuery * rimane senza risposta ... Se posso accedere alle proprietà degli elementi SVG tramite la funzione jQuery attr, perché anche le funzioni della classe * non possono farlo? jQuery FAIL?!?
Ryan Griggs,

2
Seriamente, qualcuno sa PERCHÉ ??
Ben Wilde,

Piccola libreria che aggiunge questa funzionalità per i file SVG: github.com/toddmotto/lunar
Chuck Le Butt

6
Il "perché" è perché jQuery utilizza la proprietà "className", che è una proprietà stringa per gli elementi HTML, ma è una stringa animata SVG per elementi SVG. Che non può essere assegnato a Mi piace per gli elementi HTML. Pertanto, il codice jQuery esplode nel tentativo di assegnare il valore.
Jon Watte,

Risposte:


426

Modifica 2016: leggi le prossime due risposte.

  • JQuery 3 risolve il problema sottostante
  • Vanilla JS: element.classList.add('newclass')funziona nei browser moderni

JQuery (meno di 3) non può aggiungere una classe a un SVG.

.attr() funziona con SVG, quindi se vuoi dipendere da jQuery:

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

E se non vuoi dipendere da jQuery:

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

A sostegno del suggerimento forresto
discusscode.blogspot.in/2012/08/…

Perfetto. questa dovrebbe essere la risposta. sebbene la risposta REALE sia che jquery includa questo per impostazione predefinita, almeno come impostazione o qualcosa del genere.
AwokeKnowing

2
.attr("class", "oldclass")(o il più ingombrante .setAttribute("class", "oldclass")) non è in realtà equivalente alla rimozione newclass!
Tom,

Ottima risposta (e tecnica v.nice) ... ma sarebbe un'idea modificare la domanda in modo che inizi affermando che jquery non può aggiungere una classe a un SVG (se è così ... come sembra )?
byronyasgur,

1
Solo un addendum: ho provato e JQuery 3 non ha risolto il problema.
Joao Arruda,

76

C'è element.classList nell'API DOM che funziona sia con elementi HTML che SVG. Non è necessario il plug-in SVG di jQuery o anche jQuery.

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

3
Ho provato questo, e .classList sembra essere indefinito per i sotto-elementi svg, almeno in Chrome.
Chris Jaynes,

3
Funziona per me in Chrome. Ho SVG incorporato in HTML, quindi la mia struttura del documento è simile alla seguente: <html> <svg> <circle class = "jimmy" ...> </svg> </html>
Tomas Mikula,

1
Ho usato questo per aggiungere classe in un elemento <g> SVG, funziona bene in Chrome.
Rachelle Uy,

20
IE non implementa .classList e API su SVG Elements. Vedi caniuse.com/#feat=classlist e l'elenco "Problemi noti". Ciò menziona anche i browser WebKit.
Shenme,

9
E col passare del tempo, questa risposta diventa ancora più corretta (e la migliore risposta)
Gerrat

69

jQuery 3 non presenta questo problema

Una delle modifiche elencate nelle revisioni jQuery 3.0 è:

aggiungi manipolazione classe SVG ( # 2199 , 20aaed3 )

Una soluzione per questo problema sarebbe l'aggiornamento a jQuery 3. Funziona alla grande:

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>


Il problema:

Il motivo per cui le funzioni di manipolazione della classe jQuery non funzionano con gli elementi SVG è perché le versioni jQuery precedenti alla 3.0 utilizzano la classNameproprietà per queste funzioni.

Estratto da jQuery attributes/classes.js:

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

Questo si comporta come previsto per gli elementi HTML, ma per gli elementi SVG classNameè leggermente diverso. Per un elemento SVG, classNamenon è una stringa, ma un'istanza di SVGAnimatedString.

Considera il seguente codice:

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>

Se esegui questo codice, vedrai qualcosa di simile al seguente nella tua console di sviluppo.

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

Se dovessimo lanciare SVGAnimatedStringquell'oggetto su una stringa come fa jQuery, avremmo [object SVGAnimatedString], che è dove jQuery fallisce.

In che modo il plug-in SVG di jQuery gestisce questo:

Il plug-in SVG di jQuery aggira questo problema rattoppando le relative funzioni per aggiungere il supporto SVG.

Estratto da jQuery SVG jquery.svgdom.js:

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

Questa funzione rileverà se un elemento è un elemento SVG e, se lo è, utilizzerà la baseValproprietà SVGAnimatedStringdell'oggetto se disponibile, prima di ricadere sull'attributo class.

Posizione storica di jQuery sulla questione:

jQuery attualmente elenca questo problema sulla loro pagina Won't Fix . Ecco le parti rilevanti.

Bug SVG / VML o Namespaced Elements

jQuery è principalmente una libreria per il DOM HTML, quindi la maggior parte dei problemi relativi ai documenti SVG / VML o agli elementi con spazio dei nomi non rientrano nell'ambito. Cerchiamo di risolvere i problemi che "sfociano" nei documenti HTML, come eventi che escono da SVG.

Evidentemente jQuery ha considerato il pieno supporto SVG al di fuori dell'ambito del core jQuery e più adatto per i plugin.


38

Se hai classi dinamiche o non sai quali classi potrebbero essere già applicate, questo metodo credo sia l'approccio migliore:

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

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

1
Questo è stato un vero toccasana per me dato che avevo bisogno di cambiare molti elementi SVG per fare le cose. +999 da me :)
Karl,

È difficile credere che jQuery avrebbe ancora scavato i talloni in questo, anche dopo 2 anni e il rilascio di 2.xx La tua soluzione, nav, mi ha salvato la giornata. Il resto di queste correzioni era inutilmente complesso. Grazie!
creazioni senza rivali

17

Sulla base delle risposte sopra ho creato la seguente 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
Questo è utile, ma gli algoritmi hanno dei problemi: 1. Se non esiste una stringa di classe esistente, si ottiene un 'nome di classe non definito' quando si aggiunge una classe. 2. Se la stringa di classe contiene una classe che è il superset dell'espressione regolare, si lascia dietro la spazzatura nella stringa di classe. Ad esempio, se provi a rimuovere la classe "dog" e la stringa della classe contiene "dogfood", rimarrai con "food" nella stringa della classe. Ecco alcune correzioni: jsfiddle.net/hellobrett/c2c2zfto
Brett

1
Questa non è una soluzione perfetta. Sostituisce tutte le parole che contengono la classe che desideri rimuovere.
tom10271,


7

Basta aggiungere il costruttore prototipo mancante a tutti i nodi 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);
  }
};

È quindi possibile utilizzarlo in questo modo senza richiedere jQuery:

this.addClass('clicked');

this.removeClass('clicked');

Tutto il merito va a Todd Moto .


ie11 soffoca su questo codice. questo polyfill è stato un percorso migliore: github.com/eligrey/classList.js
ryanrain,

5

jQuery non supporta le classi di elementi SVG. È possibile ottenere direttamente l'elemento $(el).get(0)e utilizzare classListe add / remove. C'è anche un trucco con questo in quanto l'elemento SVG più in alto è in realtà un normale oggetto DOM e può essere usato come qualsiasi altro elemento in jQuery. Nel mio progetto l'ho creato per prendermi cura di ciò di cui avevo bisogno, ma la documentazione fornita su Mozilla Developer Network ha uno spessore che può essere usato come alternativa.

esempio

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

Un'alternativa più dura è invece utilizzare D3.js come motore di selezione. I miei progetti hanno grafici che sono costruiti con esso, quindi è anche nell'ambito della mia app. D3 modifica correttamente gli attributi di classe degli elementi DOM vaniglia e degli elementi SVG. Anche se aggiungere D3 solo per questo caso sarebbe probabilmente eccessivo.

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

1
Grazie per quel bit d3 ... In realtà avevo già d3 sulla pagina, quindi ho deciso di usarlo. Molto utile :)
Muers

5

jQuery 2.2 supporta la manipolazione di classi SVG

Il post rilasciato da jQuery 2.2 e 1.12 include la seguente citazione:

Mentre jQuery è una libreria HTML, abbiamo concordato che il supporto di classe per gli elementi SVG potrebbe essere utile. Gli utenti saranno ora in grado di chiamare i metodi .addClass () , .removeClass () , .toggleClass () e .hasClass () su SVG. jQuery ora modifica l'attributo class anziché la proprietà className . Ciò rende inoltre utilizzabili i metodi di classe nei documenti XML generali. Tieni presente che molte altre cose non funzioneranno con SVG e ti consigliamo comunque di utilizzare una libreria dedicata a SVG se hai bisogno di qualcosa oltre la manipolazione della classe.

Esempio usando jQuery 2.2.0

Verifica:

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

Se fai clic su quel quadratino, cambierà il suo colore perché l' classattributo viene aggiunto / rimosso.

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

Ecco il mio codice piuttosto inelegante ma funzionante che affronta i seguenti problemi (senza dipendenze):

  • classListnon esistente su <svg>elementi in IE
  • classNamenon rappresenta l' classattributo sugli <svg>elementi in IE
  • Il vecchio IE è rotto getAttribute()e le setAttribute()implementazioni

Usa classListdove possibile.

Codice:

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

Esempio di utilizzo:

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

L'hai provato usando IE7? Perché LTE IE7 richiede che strAttributeName sia "className" quando si imposta, ottiene o rimuove un attributo CLASS .
Knu,

@Knu: Funziona sicuramente in IE 6 e 7 per i normali elementi HTML perché in quei browser usa la classNameproprietà invece di provare a impostare un attributo. Il motivo per cui "LTE IE7 richiede che strAttributeName sia" className "quando si imposta, si ottiene o si rimuove un attributo CLASS" è che quei browser hanno un'implementazione non funzionante getAttribute(x)e setAttribute(x)che ottengono o impostano semplicemente la proprietà con il nome x, quindi è più facile e più compatibile per impostare direttamente la proprietà.
Tim Down,

@Knu: OK. Non ero sicuro poiché SVG non è supportato in IE 7, quindi non sono sicuro di ciò che speri di ottenere includendo un <svg>elemento in una pagina progettato per funzionare in IE 7. Per quello che vale, il mio codice aggiunge correttamente un attributo di classe agli <svg>elementi in IE 7 poiché tale elemento si comporta come qualsiasi altro elemento personalizzato.
Tim Down,

Dimenticato completamente questo grazie per il promemoria. Cercherò di mettere le mani su Adobe SVG Viewer per verificare se funziona come previsto.
Knu,

2

Uso Snap.svg per aggiungere una classe a e SVG.

var jimmy = Snap(" .jimmy ")

jimmy.addClass("exampleClass");

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


1
Sebbene questo collegamento possa rispondere alla domanda, è meglio includere qui le parti essenziali della risposta e fornire il collegamento come riferimento. Le risposte di solo collegamento possono diventare non valide se la pagina collegata cambia.
Tristan,

1

Una soluzione alternativa potrebbe essere quella di aggiungereClass a un contenitore dell'elemento 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

L'ho scritto nel mio progetto e funziona ... probabilmente;)

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

esempi

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

0

Ispirato dalle risposte sopra, in particolare da Sagar Gala, ho creato questa API . È possibile utilizzarlo se non si desidera o non è possibile aggiornare la versione di jquery.


-1

O semplicemente usa i metodi DOM della vecchia scuola quando JQ ha una scimmia nel mezzo da qualche parte.

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

Impara le persone DOM. Anche nel quadro-palooza del 2016 aiuta abbastanza regolarmente. Inoltre, se hai mai sentito qualcuno confrontare il DOM con l'assembly, prendilo a calci per me.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.