jQuery SVG, ¿por qué no puedo agregar Class?

289

Estoy usando jQuery SVG. No puedo agregar o eliminar una clase a un objeto. Alguien sabe mi error?

El SVG:

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

El jQuery que no agregará la clase:

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

Sé que SVG y jQuery funcionan bien juntos porque puedo apuntar al objeto y disparar una alerta cuando se hace clic:

$(".jimmy").click(function() {
    alert('Handler for .click() called.');
});
Don P
fuente
44
Buen artículo aquí: toddmotto.com/…
Kris
8
Entonces, la pregunta real de POR QUÉ no puedo usar las funciones de la clase jQuery * permanece sin respuesta ... Si puedo acceder a las propiedades de los elementos SVG a través de la función jQuery attr, ¿por qué las funciones de la clase * también no pueden hacer esto? jQuery FAIL?!?
Ryan Griggs
2
En serio, ¿alguien sabe por qué ?
Ben Wilde
Pequeña biblioteca que agrega esta funcionalidad para archivos SVG: github.com/toddmotto/lunar
Chuck Le Butt
66
El "por qué" se debe a que jQuery usa la propiedad "className", que es una propiedad de cadena para elementos HTML, pero es una cadena animada SVG para elementos SVG. Que no puede asignarse a Me gusta para elementos HTML. Por lo tanto, el código jQuery explota al intentar asignar el valor.
Jon Watte

Respuestas:

426

Editar 2016: lea las siguientes dos respuestas.

  • JQuery 3 soluciona el problema subyacente
  • Vanilla JS: element.classList.add('newclass')funciona en navegadores modernos

JQuery (menos de 3) no puede agregar una clase a un SVG.

.attr() funciona con SVG, así que si quieres depender de jQuery:

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

Y si no quieres depender 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
fuente
En apoyo a la sugerencia de forresto, discuta code.blogspot.in/2012/08/…
helloworld
Perfecto. Esta debería ser la respuesta. aunque la respuesta REAL es que jquery incluya esto por defecto, al menos como una configuración o algo así.
AwokeKnowing
2
.attr("class", "oldclass")(o lo más engorroso .setAttribute("class", "oldclass")) realmente no es equivalente a eliminar newclass!
Tom
Gran respuesta (y técnica v.nice) ... pero sería una idea editar la pregunta para que comience diciendo que jquery no puede agregar una clase a un SVG (si ese es el caso ... como parece )?
byronyasgur
1
Solo un apéndice: lo intenté y JQuery 3 no solucionó el problema.
Joao Arruda
76

Hay element.classList en la API DOM que funciona tanto para elementos HTML como SVG. No es necesario el complemento jQuery SVG o incluso jQuery.

$(".jimmy").click(function() {
    this.classList.add("clicked");
});
Tomás Mikula
fuente
3
Probé esto, y .classList parece estar indefinido para los subelementos svg, al menos en Chrome.
Chris Jaynes
3
Funciona para mí en Chrome. Tengo SVG incrustado en HTML, por lo que la estructura de mi documento se ve así: <html> <svg> <circle class = "jimmy" ...> </svg> </html>
Tomas Mikula
1
Utilicé esto para agregar clase en un elemento <g> SVG, funciona bien en Chrome.
Rachelle Uy
20
IE no implementa .classList y API en SVG Elements. Consulte caniuse.com/#feat=classlist y la lista de "problemas conocidos". Eso menciona los navegadores WebKit también.
Shenme
99
Y a medida que pasa el tiempo, esta respuesta se vuelve aún más correcta (y la mejor respuesta)
Gerrat
69

jQuery 3 no tiene este problema

Uno de los cambios enumerados en las revisiones de jQuery 3.0 es:

agregar manipulación de clase SVG ( # 2199 , 20aaed3 )

Una solución para este problema sería actualizar a jQuery 3. Funciona muy 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>


El problema:

La razón por la cual las funciones de manipulación de clase jQuery no funcionan con los elementos SVG es porque las versiones jQuery anteriores a la 3.0 usan className propiedad para estas funciones.

Extracto de jQuery attributes/classes.js:

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

Esto se comporta como se espera para los elementos HTML, pero para los elementos SVG classNamees un poco diferente. Para un elemento SVG, classNameno es una cadena, sino una instancia deSVGAnimatedString .

Considere el siguiente código:

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 ejecuta este código, verá algo como lo siguiente en su consola de desarrollador.

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

Si tuviéramos que convertir ese SVGAnimatedStringobjeto en una cadena como lo hace jQuery, tendríamos[object SVGAnimatedString] , que es donde falla jQuery.

Cómo maneja esto el complemento jQuery SVG:

El complemento jQuery SVG soluciona este problema parcheando las funciones relevantes para agregar soporte SVG.

Extracto de jQuery SVG jquery.svgdom.js:

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

Esta función detectará si un elemento es un elemento SVG y, si lo es, utilizará la baseValpropiedad del SVGAnimatedStringobjeto si está disponible, antes de recurrir al classatributo.

La postura histórica de jQuery sobre el tema:

jQuery actualmente enumera este problema en su página No se solucionará . Aquí están las partes relevantes.

SVG / VML o errores de elementos de espacio de nombres

jQuery es principalmente una biblioteca para el DOM HTML, por lo que la mayoría de los problemas relacionados con documentos SVG / VML o elementos con espacios de nombres están fuera de alcance. Intentamos abordar los problemas que "se filtran" a los documentos HTML, como los eventos que salen de SVG.

Evidentemente, jQuery consideró el soporte SVG completo fuera del alcance del núcleo jQuery, y mejor adaptado para complementos.

Alexander O'Mara
fuente
38

Si tiene clases dinámicas o no sabe qué clases ya podrían aplicarse, entonces este método, creo, es el mejor enfoque:

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

// removeClass
$('path').attr('class', function(index, classNames) {
    return classNames.replace('class-name', '');
});
nav
fuente
1
Esto fue un salvavidas para mí, ya que necesitaba cambiar muchos elementos SVG para hacer las cosas. +999 de mi parte :)
Karl
Es difícil creer que a jQuery todavía se le clavaran los talones, incluso después de 2 años y el lanzamiento de 2.xx Su solución, nav, me salvó el día. El resto de estas soluciones fueron innecesariamente complejas. ¡Gracias!
creaciones inigualables
17

Basado en las respuestas anteriores, creé la siguiente 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;
};
Sagar Gala
fuente
3
Esto es útil, pero los algoritmos tienen problemas: 1. Si no hay una cadena de clase existente, se obtiene 'className sin definir' al agregar una clase. 2. Si la cadena de clase contiene una clase que es el superconjunto de la expresión regular, deja basura en la cadena de clase. Por ejemplo, si intenta eliminar la clase 'perro' y la cadena de clase contiene 'comida de perro', se quedará con 'comida' en la cadena de clase. Aquí hay algunas soluciones: jsfiddle.net/hellobrett/c2c2zfto
Brett
1
Esta no es una solución perfecta. Reemplaza todas las palabras que contienen la clase que desea eliminar.
tom10271
13

Después de cargar jquery.svg.jsdebe cargar este archivo:http://keith-wood.name/js/jquery.svgdom.js .

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

Ejemplo de trabajo: http://jsfiddle.net/74RbC/99/

bennedich
fuente
Lo agregó, todavía no parece funcionar. He estado revisando la documentación en el sitio de jquery SVG, pero no hay una explicación clara de lo que debe hacer para utilizar su funcionalidad adicional.
Don P
7

Simplemente agregue el constructor prototipo faltante a todos los nodos 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);
  }
};

Luego puede usarlo de esta manera sin requerir jQuery:

this.addClass('clicked');

this.removeClass('clicked');

Todo el crédito va a Todd Moto .

Ziad
fuente
ie11 se ahoga con este código. este polyfill fue una mejor ruta: github.com/eligrey/classList.js
ryanrain
5

jQuery no admite las clases de elementos SVG. Puede obtener el elemento directamente $(el).get(0)y usar classListy add / remove. También hay un truco con esto en que el elemento SVG superior es en realidad un objeto DOM normal y puede usarse como cualquier otro elemento en jQuery. En mi proyecto, creé esto para encargarme de lo que necesitaba, pero la documentación proporcionada en la red de desarrolladores de Mozilla tiene una cuña que se puede usar como alternativa.

ejemplo

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

Una alternativa más difícil es usar D3.js como su motor de selección. Mis proyectos tienen gráficos que se crean con él, por lo que también está en el alcance de mi aplicación. D3 modifica correctamente los atributos de clase de los elementos DOM de vainilla y los elementos SVG. Aunque agregar D3 solo para este caso probablemente sería exagerado.

d3.select(el).classed('myclass', true);
QueueHammer
fuente
1
Gracias por ese bit d3 ... En realidad ya tenía d3 en la página, así que decidí usar eso. Muy útil :)
Mueres
5

jQuery 2.2 admite la manipulación de clases SVG

La publicación publicada de jQuery 2.2 y 1.12 incluye la siguiente cita:

Si bien jQuery es una biblioteca HTML, acordamos que el soporte de clase para elementos SVG podría ser útil. Los usuarios ahora podrán llamar a .addClass () , .removeClass () , .toggleClass () y .hasClass () en SVG. jQuery ahora cambia el atributo de clase en lugar de la propiedad className . Esto también hace que los métodos de clase sean utilizables en documentos XML generales. Tenga en cuenta que muchas otras cosas no funcionarán con SVG, y aún así recomendamos usar una biblioteca dedicada a SVG si necesita algo más allá de la manipulación de clases.

Ejemplo usando jQuery 2.2.0

Prueba:

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

Si hace clic en ese cuadrado pequeño, cambiará su color porque el classatributo se agrega / elimina.

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

RUMANIA_ingeniero
fuente
3

Aquí está mi código poco elegante pero funcional que se ocupa de los siguientes problemas (sin ninguna dependencia):

  • classListno existe en <svg>elementos en IE
  • classNameno representa el classatributo en <svg>elementos en IE
  • IE viejo roto getAttribute()e setAttribute()implementaciones

Utiliza classListdonde sea posible.

Código:

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

Ejemplo de uso:

var el = document.getElementById("someId");
if (hasClass(el, "someClass")) {
    removeClass(el, "someClass");
}
addClass(el, "someOtherClass");
Tim Down
fuente
¿Lo probaste usando IE7? Debido a que LTE IE7 requiere que strAttributeName sea "className" al configurar, obtener o eliminar un atributo CLASS .
Knu
@Knu: Definitivamente funciona en IE 6 y 7 para elementos HTML normales porque en esos navegadores usa la classNamepropiedad en lugar de intentar establecer un atributo. La razón por la que "LTE IE7 requiere que strAttributeName sea" className "al configurar, obtener o eliminar un atributo CLASS" es que esos navegadores tienen una implementación defectuosa getAttribute(x)y setAttribute(x)que simplemente obtienen o establecen la propiedad con nombre x, por lo que es más fácil y más compatible para establecer la propiedad directamente.
Tim Down
@Knu: OK. No estaba seguro ya que SVG no es compatible con IE 7, por lo que no estoy seguro de lo que esperarías lograr al incluir un <svg>elemento en una página que está diseñada para funcionar en IE 7. Por lo que vale, mi código agrega un atributo de clase con éxito a los <svg>elementos en IE 7 ya que dicho elemento se comporta como cualquier otro elemento personalizado.
Tim Down
Se olvidó por completo de eso gracias por el recordatorio. Trataré de tener en mis manos el Adobe SVG Viewer para verificar si funciona según lo previsto.
Knu
2

Yo uso Snap.svg para agregar una clase y SVG.

var jimmy = Snap(" .jimmy ")

jimmy.addClass("exampleClass");

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

cjohndesign
fuente
1
Si bien este enlace puede responder la pregunta, es mejor incluir las partes esenciales de la respuesta aquí y proporcionar el enlace como referencia. Las respuestas de solo enlace pueden dejar de ser válidas si la página vinculada cambia.
Tristan
1

Una solución alternativa podría ser agregar Class a un contenedor del 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>

Claytronicon
fuente
1

Escribí esto en mi proyecto, y funciona ... probablemente;)

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

ejemplos

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

Inspirado por las respuestas anteriores, especialmente por Sagar Gala, he creado esta API . Puede usarlo si no desea o no puede actualizar su versión de jquery.

Trituradora de piedra
fuente
-1

O simplemente use los métodos DOM de la vieja escuela cuando JQ tiene un mono en el medio en alguna 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

Aprende las personas DOM. Incluso en el framework-palooza de 2016, ayuda con bastante regularidad. Además, si alguna vez escuchas a alguien comparar el DOM con el ensamblado, dale una patada por mí.

Erik Reppen
fuente