jQuery SVG, pourquoi ne puis-je pas ajouterClass?

289

J'utilise jQuery SVG. Je ne peux pas ajouter ou supprimer une classe à un objet. Quelqu'un connaît mon erreur?

Le SVG:

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

Le jQuery qui n'ajoutera pas la classe:

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

Je sais que le SVG et jQuery fonctionnent bien ensemble car je peux cibler l'objet et déclencher une alerte lorsqu'il est cliqué:

$(".jimmy").click(function() {
    alert('Handler for .click() called.');
});
Don P
la source
4
Bon article ici: toddmotto.com/…
Kris
8
Donc la vraie question de POURQUOI ne puis-je pas utiliser les fonctions de classe jQuery * reste sans réponse ... Si je peux accéder aux propriétés des éléments SVG via la fonction jQuery attr, pourquoi les fonctions de classe * ne peuvent-elles pas également faire cela? jQuery FAIL?!?
Ryan Griggs
2
Sérieusement, quelqu'un sait pourquoi ?
Ben Wilde
Petite bibliothèque qui ajoute cette fonctionnalité pour les fichiers SVG: github.com/toddmotto/lunar
Chuck Le Butt
6
Le "pourquoi" est dû au fait que jQuery utilise la propriété "className", qui est une propriété de chaîne pour les éléments HTML, mais est une chaîne animée SVG pour les éléments SVG. Qui ne peut pas être assigné à des éléments HTML. Ainsi, le code jQuery explose en essayant d'attribuer la valeur.
Jon Watte

Réponses:

426

Edit 2016: lisez les deux réponses suivantes.

  • JQuery 3 résout le problème sous-jacent
  • Vanilla JS: element.classList.add('newclass')fonctionne dans les navigateurs modernes

JQuery (moins de 3) ne peut pas ajouter une classe à un SVG.

.attr() fonctionne avec SVG, donc si vous voulez dépendre de jQuery:

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

Et si vous ne voulez pas dépendre de jQuery:

var element = document.getElementById("item");
// Instead of .addClass("newclass")
element.setAttribute("class", "oldclass newclass");
// Instead of .removeClass("newclass")
element.setAttribute("class", "oldclass");
forresto
la source
À l'appui de la suggestion de Forresto - discutercode.blogspot.in/2012/08/…
helloworld
parfait. ce devrait être la réponse. bien que la VRAIE réponse soit que jquery l'inclue par défaut, au moins en tant que paramètre ou quelque chose.
AwokeKnowing
2
.attr("class", "oldclass")(ou le plus lourd .setAttribute("class", "oldclass")) n'est vraiment pas équivalent à l'enlever newclass!
Tom
Excellente réponse (et technique v.nice) ... mais serait-ce une idée de modifier la question afin qu'elle commence par déclarer que jquery ne peut pas ajouter une classe à un SVG (si c'est le cas ... comme il semble )?
byronyasgur
1
Juste un addendum: j'ai essayé et JQuery 3 n'a pas résolu le problème.
Joao Arruda du
76

Il y a element.classList dans l'API DOM qui fonctionne à la fois pour les éléments HTML et SVG. Pas besoin de plugin jQuery SVG ou même de jQuery.

$(".jimmy").click(function() {
    this.classList.add("clicked");
});
Tomas Mikula
la source
3
J'ai testé cela, et .classList semble être indéfini pour les sous-éléments svg, au moins dans Chrome.
Chris Jaynes
3
Fonctionne pour moi dans Chrome. J'ai SVG intégré dans HTML, donc ma structure de document ressemble à ceci: <html> <svg> <circle class = "jimmy" ...> </svg> </html>
Tomas Mikula
1
J'ai utilisé cela pour ajouter une classe dans un élément <g> SVG, fonctionne très bien dans Chrome.
Rachelle Uy
20
IE n'implémente pas .classList et API sur les éléments SVG. Voir caniuse.com/#feat=classlist et la liste des "problèmes connus". Cela mentionne également les navigateurs WebKit.
Shenme
9
Et avec le temps, cette réponse devient encore plus correcte (et la meilleure réponse)
Gerrat
69

jQuery 3 n'a pas ce problème

L'une des modifications répertoriées dans les révisions de jQuery 3.0 est:

ajouter la manipulation de classe SVG ( # 2199 , 20aaed3 )

Une solution à ce problème serait de mettre à niveau vers jQuery 3. Cela fonctionne très bien:

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>


Le problème:

La raison pour laquelle les fonctions de manipulation de classe jQuery ne fonctionnent pas avec les éléments SVG est que les versions jQuery antérieures à 3.0 utilisent la classNamepropriété de ces fonctions.

Extrait de jQuery attributes/classes.js:

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

Cela se comporte comme prévu pour les éléments HTML, mais pour les éléments SVG, classNamec'est un peu différent. Pour un élément SVG, classNamen'est pas une chaîne, mais une instance de SVGAnimatedString.

Considérez le code suivant:

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>

Si vous exécutez ce code, vous verrez quelque chose comme le suivant dans votre console de développeur.

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

Si nous devions convertir cet SVGAnimatedStringobjet en chaîne comme le fait jQuery, nous l'aurions fait [object SVGAnimatedString], c'est là que jQuery échoue.

Comment le plugin jQuery SVG gère cela:

Le plugin jQuery SVG contourne cela en corrigeant les fonctions pertinentes pour ajouter le support SVG.

Extrait de jQuery SVG jquery.svgdom.js:

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

Cette fonction détectera si un élément est un élément SVG, et s'il l'est, il utilisera la baseValpropriété de l' SVGAnimatedStringobjet si elle est disponible, avant de retomber sur l' classattribut.

Position historique de jQuery sur la question:

jQuery répertorie actuellement ce problème sur leur page Won't Fix . Voici les parties pertinentes.

Bogues SVG / VML ou éléments à espace de noms

jQuery est principalement une bibliothèque pour le DOM HTML, donc la plupart des problèmes liés aux documents SVG / VML ou aux éléments à espace de noms sont hors de portée. Nous essayons de résoudre les problèmes qui «traversent» les documents HTML, tels que les événements qui sortent de SVG.

Évidemment, jQuery considérait la prise en charge complète de SVG en dehors de la portée du noyau jQuery, et mieux adaptée aux plugins.

Alexander O'Mara
la source
38

Si vous avez des classes dynamiques ou ne savez pas quelles classes pourraient déjà être appliquées, cette méthode est, je crois, la meilleure approche:

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

// removeClass
$('path').attr('class', function(index, classNames) {
    return classNames.replace('class-name', '');
});
nav
la source
1
Ce fut une bouée de sauvetage pour moi car je devais changer beaucoup d'éléments SVG pour faire avancer les choses. +999 de moi :)
Karl
Il est difficile de croire que jQuery aurait encore ses talons enfoncés, même après 2 ans et la sortie de 2.xx Votre correctif, nav, m'a sauvé la journée. Les autres correctifs étaient inutilement complexes. Merci!
créations inégalées
17

Sur la base des réponses ci-dessus, j'ai créé l'API suivante

/*
 * .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;
};
Sagar Gala
la source
3
Ceci est utile, mais les algorithmes ont des problèmes: 1. S'il n'y a pas de chaîne de classe existante, vous obtenez «nom de classe non défini» lors de l'ajout d'une classe. 2. Si la chaîne de classe contient une classe qui est le sur-ensemble de l'expression régulière, vous laissez des éléments indésirables dans la chaîne de classe. Par exemple, si vous essayez de supprimer la classe 'dog' et que la chaîne de classe contient 'dogfood', vous vous retrouverez avec 'food' dans la chaîne de classe. Voici quelques correctifs: jsfiddle.net/hellobrett/c2c2zfto
Brett
1
Ce n'est pas une solution parfaite. Il remplace tous les mots contenant la classe que vous souhaitez supprimer.
tom10271
13

Après le chargement , jquery.svg.jsvous devez charger ce fichier: http://keith-wood.name/js/jquery.svgdom.js.

Source: http://keith-wood.name/svg.html#dom

Exemple de travail: http://jsfiddle.net/74RbC/99/

Bennedich
la source
Ajouté - ne semble toujours pas fonctionner. J'ai parcouru la documentation sur le site de jquery SVG, mais il n'y a aucune explication claire de ce que vous devez faire pour utiliser ses fonctionnalités supplémentaires.
Don P
7

Ajoutez simplement le constructeur prototype manquant à tous les nœuds 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);
  }
};

Vous pouvez ensuite l'utiliser de cette façon sans nécessiter jQuery:

this.addClass('clicked');

this.removeClass('clicked');

Tout le mérite revient à Todd Moto .

Ziad
la source
ie11 s'étouffe avec ce code. ce polyfill était un meilleur itinéraire: github.com/eligrey/classList.js
ryanrain
5

jQuery ne prend pas en charge les classes d'éléments SVG. Vous pouvez obtenir l'élément directement $(el).get(0)et utiliser classListet add / remove. Il y a aussi une astuce avec cela, car l'élément SVG le plus haut est en fait un objet DOM normal et peut être utilisé comme tous les autres éléments de jQuery. Dans mon projet, j'ai créé cela pour prendre soin de ce dont j'avais besoin, mais la documentation fournie sur le Mozilla Developer Network a une cale qui peut être utilisée comme alternative.

exemple

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

Une alternative plus difficile consiste à utiliser D3.js comme moteur de sélection à la place. Mes projets ont des graphiques qui sont construits avec, donc c'est aussi dans la portée de mon application. D3 modifie correctement les attributs de classe des éléments DOM vanilla et des éléments SVG. Bien que l'ajout de D3 pour ce cas serait probablement exagéré.

d3.select(el).classed('myclass', true);
QueueHammer
la source
1
Merci pour ce bit d3 ... En fait, j'avais déjà d3 sur la page alors j'ai juste décidé de l'utiliser. Très utile :)
Muers
5

jQuery 2.2 prend en charge la manipulation de classe SVG

La publication de jQuery 2.2 et 1.12 comprend la citation suivante:

Bien que jQuery soit une bibliothèque HTML, nous avons convenu que le support de classe pour les éléments SVG pourrait être utile. Les utilisateurs pourront désormais appeler les méthodes .addClass () , .removeClass () , .toggleClass () et .hasClass () sur SVG. jQuery modifie désormais l'attribut de classe plutôt que la propriété className . Cela rend également les méthodes de classe utilisables dans les documents XML généraux. Gardez à l'esprit que beaucoup d'autres choses ne fonctionneront pas avec SVG, et nous recommandons toujours d'utiliser une bibliothèque dédiée à SVG si vous avez besoin de quelque chose au-delà de la manipulation de classe.

Exemple utilisant jQuery 2.2.0

Il teste:

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

Si vous cliquez sur ce petit carré, il changera de couleur car l' classattribut est ajouté / supprimé.

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

ROUMANIE_ingénieur
la source
3

Voici mon code plutôt inélégant mais fonctionnel qui traite les problèmes suivants (sans aucune dépendance):

  • classListn'existe pas sur les <svg>éléments dans IE
  • classNamene représentant pas l' classattribut sur les <svg>éléments dans IE
  • Anciens IE cassés getAttribute()et setAttribute()implémentations

Il utilise classListautant que possible.

Code:

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

Exemple d'utilisation:

var el = document.getElementById("someId");
if (hasClass(el, "someClass")) {
    removeClass(el, "someClass");
}
addClass(el, "someOtherClass");
Tim Down
la source
L'avez-vous testé avec IE7? Parce que LTE IE7 requiert que strAttributeName soit "className" lors de la définition, de l'obtention ou de la suppression d'un attribut CLASS .
Knu
@Knu: Cela fonctionne certainement dans IE 6 et 7 pour les éléments HTML normaux car dans ces navigateurs, il utilise la classNamepropriété plutôt que d'essayer de définir un attribut. La raison pour laquelle "LTE IE7 requiert que strAttributeName soit" className "lors de la définition, de l'obtention ou de la suppression d'un attribut CLASS" est que ces navigateurs ont une implémentation rompue de getAttribute(x)et setAttribute(x)qui obtiennent ou définissent simplement la propriété avec un nom x, il est donc plus facile et plus compatible pour définir la propriété directement.
Tim Down
@Knu: OK. Je n'étais pas sûr car SVG n'est pas pris en charge dans IE 7, donc je ne suis pas sûr de ce que vous espérez obtenir en incluant un <svg>élément dans une page qui est conçue pour fonctionner dans IE 7. Pour ce que ça vaut, mon code ajoute un attribut de classe avec succès aux <svg>éléments dans IE 7 car un tel élément se comporte comme n'importe quel autre élément personnalisé.
Tim Down
J'ai complètement oublié cela, merci pour le rappel. J'essaierai de mettre la main sur Adobe SVG Viewer pour vérifier si cela fonctionne comme prévu.
Knu
2

J'utilise Snap.svg pour ajouter une classe à et SVG.

var jimmy = Snap(" .jimmy ")

jimmy.addClass("exampleClass");

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

cjohndesign
la source
1
Bien que ce lien puisse répondre à la question, il est préférable d'inclure les parties essentielles de la réponse ici et de fournir le lien de référence. Les réponses de lien uniquement peuvent devenir invalides si la page liée change.
Tristan
1

Une solution de contournement pourrait être d'ajouter addClass à un conteneur de l'élément 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
la source
1

J'ai écrit ça dans mon projet, et ça marche ... probablement;)

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

exemples

$('path').addSvgClass('fillWithOrange')
$('path').removeSvgClass('fillWithOrange')
Dariusz Majchrzak
la source
0

Inspiré par les réponses ci-dessus, en particulier par Sagar Gala, j'ai créé cette API . Vous pouvez l'utiliser si vous ne voulez pas ou ne pouvez pas mettre à jour votre version jquery.

Concasseur
la source
-1

Ou utilisez simplement les méthodes DOM à l'ancienne lorsque JQ a un singe au milieu quelque part.

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

Apprenez les gens du DOM. Même dans le cadre-palooza de 2016, cela aide assez régulièrement. De plus, si vous entendez quelqu'un comparer le DOM à l'assemblage, donnez-lui un coup de pied.

Erik Reppen
la source