jQuery SVG, por que não consigo adicionar a classe?

289

Estou usando o jQuery SVG. Não consigo adicionar ou remover uma classe para um objeto. Alguém conhece o meu erro?

O SVG:

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

O jQuery que não adicionará a classe:

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

Eu sei que o SVG e o jQuery estão funcionando bem, porque posso direcionar o objeto e disparar um alerta quando ele é clicado:

$(".jimmy").click(function() {
    alert('Handler for .click() called.');
});
Don P
fonte
4
Bom artigo aqui: toddmotto.com/…
Kris
8
Portanto, a questão real de POR QUE não posso usar as funções da classe jQuery * permanece sem resposta ... Se eu posso acessar as propriedades dos elementos SVG via função jQuery attr, por que as funções da classe * também não podem fazer isso? jQuery FAIL?!?
Ryan Griggs
2
Sério, alguém sabe por quê ?
Ben Wilde
Biblioteca pequena que adiciona essa funcionalidade para SVG arquivos: github.com/toddmotto/lunar
Chuck Le bumbum
6
O "porquê" é porque o jQuery usa a propriedade "className", que é uma propriedade de string para elementos HTML, mas é uma String Animada SVG para elementos SVG. O qual não pode ser atribuído para gostar de elementos HTML. Assim, o código jQuery explode tentando atribuir o valor.
Jon Watte 20/05

Respostas:

426

Editar 2016: leia as próximas duas respostas.

  • JQuery 3 corrige o problema subjacente
  • Vanilla JS: element.classList.add('newclass')funciona em navegadores modernos

JQuery (menor que 3) não pode adicionar uma classe a um SVG.

.attr() funciona com SVG, portanto, se você quiser depender do jQuery:

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

E se você não quiser depender do jQuery:

var element = document.getElementById("item");
// Instead of .addClass("newclass")
element.setAttribute("class", "oldclass newclass");
// Instead of .removeClass("newclass")
element.setAttribute("class", "oldclass");
forresto
fonte
Para dar suporte à sugestão de forresto - discussão em code.blogspot.in/2012/08/…
helloworld
perfeito. essa deve ser a resposta. embora a resposta REAL seja que o jquery inclua isso por padrão, pelo menos como uma configuração ou algo assim.
precisa saber é o seguinte
2
.attr("class", "oldclass")(ou o mais complicado .setAttribute("class", "oldclass")) não é realmente equivalente a remover newclass!
29414 Tom
Ótima resposta (e técnica v.nice) ... mas seria uma idéia editar a pergunta para que iniciasse afirmando que o jquery não pode adicionar uma classe a um SVG (se for esse o caso ... como parece )?
byronyasgur
1
Apenas um adendo: tentei e o JQuery 3 não corrigiu o problema.
Joao Arruda
76

element.classList na API do DOM que funciona para elementos HTML e SVG. Não é necessário o plugin jQuery SVG ou mesmo o jQuery.

$(".jimmy").click(function() {
    this.classList.add("clicked");
});
Tomas Mikula
fonte
3
Eu testei isso e o .classList parece estar indefinido para subelementos svg, pelo menos no Chrome.
precisa
3
Funciona para mim no Chrome. Eu tenho SVG incorporado em HTML, então minha estrutura de documento fica assim: <html> <svg> <circle class = "jimmy" ...> </svg> </html>
Tomas Mikula
1
Eu usei isso para adicionar classe em um elemento <g> SVG, funciona bem no Chrome.
Rachelle Uy
20
O IE não implementa .classList e API nos elementos SVG. Consulte caniuse.com/#feat=classlist e a lista "problemas conhecidos". Isso menciona também os navegadores WebKit.
Shenme 13/02/2014
9
E como o tempo passa, esta resposta se torna ainda mais correta (e a melhor resposta)
Gerrat
69

O jQuery 3 não tem esse problema

Uma das alterações listadas nas revisões do jQuery 3.0 é:

adicionar manipulação de classe SVG ( # 2199 , 20aaed3 )

Uma solução para esse problema seria atualizar para o jQuery 3. Ele funciona muito bem:

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>


O problema:

A razão pela qual as funções de manipulação da classe jQuery não funcionam com os elementos SVG é porque as versões do jQuery anteriores ao 3.0 usam a classNamepropriedade para essas funções.

Trecho do jQuery attributes/classes.js:

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

Isso se comporta conforme o esperado para elementos HTML, mas para elementos SVG classNameé um pouco diferente. Para um elemento SVG, classNamenão é uma sequência, mas uma instância de SVGAnimatedString.

Considere o seguinte 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>

Se você executar esse código, verá algo como o seguinte no console do desenvolvedor.

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

Se formos converter esse SVGAnimatedStringobjeto em uma string como o jQuery, teríamos [object SVGAnimatedString], e é aí que o jQuery falha.

Como o plugin SVG jQuery lida com isso:

O plug-in jQuery SVG soluciona esse problema corrigindo as funções relevantes para adicionar suporte ao SVG.

Trecho do jQuery SVG jquery.svgdom.js:

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

Essa função detectará se um elemento é um elemento SVG e, se for, usará a baseValpropriedade do SVGAnimatedStringobjeto, se disponível, antes de recorrer ao classatributo.

A posição histórica do jQuery sobre o assunto:

No momento, o jQuery lista esse problema na página Won't Fix . Aqui estão as partes relevantes.

Erros SVG / VML ou elementos com espaço para nome

O jQuery é principalmente uma biblioteca para o HTML DOM, portanto, a maioria dos problemas relacionados a documentos SVG / VML ou elementos no espaço para nome estão fora do escopo. Tentamos resolver problemas que "vazam" para documentos HTML, como eventos que surgem do SVG.

Evidentemente, o jQuery considerou o suporte completo ao SVG fora do escopo do núcleo do jQuery e mais adequado para plug-ins.

Alexander O'Mara
fonte
38

Se você tem classes dinâmicas ou não sabe quais classes já podem ser aplicadas, acredito que este método seja a melhor abordagem:

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

// removeClass
$('path').attr('class', function(index, classNames) {
    return classNames.replace('class-name', '');
});
nav
fonte
1
Isso foi um salva-vidas para mim, pois eu precisava alterar muitos elementos SVG para fazer as coisas. +999 de mim :)
Karl
É difícil acreditar que o jQuery ainda tenha se concentrado nisso, mesmo após 2 anos e o lançamento do 2.xx Sua correção, nav, salvou o dia para mim. O restante dessas correções eram desnecessariamente complexas. Obrigado!
Unrivaledcreations
17

Com base nas respostas acima, criei a seguinte 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
fonte
3
Isso é útil, mas os algoritmos têm problemas: 1. Se não houver uma sequência de classes existente, você obterá 'undedfined className' ao adicionar uma classe. 2. Se a sequência de classes contiver uma classe que é o superconjunto da expressão regular, você deixará para trás lixo na sequência de classes. Por exemplo, se você tentar remover a classe 'dog' e a string da classe contiver 'dogfood', você ficará com 'food' na string da classe. Aqui estão algumas correções: jsfiddle.net/hellobrett/c2c2zfto
Brett
1
Esta não é uma solução perfeita. Ele substitui todas as palavras que contêm a classe que você deseja remover.
tom10271
13

Após o carregamento jquery.svg.jsvocê deve carregar este arquivo: http://keith-wood.name/js/jquery.svgdom.js.

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

Exemplo de trabalho: http://jsfiddle.net/74RbC/99/

bennedich
fonte
Adicionado - ainda não parece funcionar. Eu estive examinando a documentação no site do jquery SVG, mas não há uma explicação clara do que você precisa fazer para usar sua funcionalidade extra.
D. P
7

Basta adicionar o construtor de protótipo ausente a todos os nós 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);
  }
};

Você pode usá-lo dessa maneira sem precisar do jQuery:

this.addClass('clicked');

this.removeClass('clicked');

Todo o crédito é para Todd Moto .

Ziad
fonte
ie11 engasga com este código. esse polyfill era uma rota melhor: github.com/eligrey/classList.js
ryanrain
5

O jQuery não suporta as classes de elementos SVG. Você pode obter o elemento diretamente $(el).get(0)e usar classListe add / remove. Há um truque com isso também, pois o elemento SVG mais alto é realmente um objeto DOM normal e pode ser usado como qualquer outro elemento no jQuery. No meu projeto, criei isso para cuidar do que eu precisava, mas a documentação fornecida na Mozilla Developer Network possui um calço que pode ser usado como uma alternativa.

exemplo

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

Uma alternativa ainda mais difícil é usar o D3.js como mecanismo de seleção. Meus projetos têm gráficos criados com ele, portanto também estão no escopo do meu aplicativo. D3 modifica corretamente os atributos de classe dos elementos DOM baunilha e SVG. Embora adicionar D3 apenas para este caso provavelmente seria um exagero.

d3.select(el).classed('myclass', true);
QueueHammer
fonte
1
Obrigado por esse bit d3 ... Na verdade, eu já tinha d3 na página, então decidi usá-lo. Muito útil :)
Muers
5

O jQuery 2.2 suporta manipulação de classe SVG

As publicações lançadas do jQuery 2.2 e 1.12 incluem a seguinte citação:

Embora o jQuery seja uma biblioteca HTML, concordamos que o suporte à classe para elementos SVG poderia ser útil. Agora, os usuários poderão chamar os métodos .addClass () , .removeClass () , .toggleClass () e .hasClass () no SVG. O jQuery agora altera o atributo da classe em vez da propriedade className . Isso também torna os métodos de classe utilizáveis ​​em documentos XML gerais. Lembre-se de que muitas outras coisas não funcionarão com o SVG, e ainda recomendamos o uso de uma biblioteca dedicada ao SVG se você precisar de algo além da manipulação de classe.

Exemplo usando o jQuery 2.2.0

Testa:

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

Se você clicar nesse pequeno quadrado, ele mudará de cor porque o classatributo foi adicionado / removido.

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

Aqui está o meu código bastante deselegante, mas funcional, que lida com os seguintes problemas (sem nenhuma dependência):

  • classListnão existe em <svg>elementos no IE
  • classNamenão representando o classatributo em <svg>elementos no IE
  • IE velho quebrado getAttribute()e setAttribute()implementações

Usa sempre classListque possível.

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

Exemplo de uso:

var el = document.getElementById("someId");
if (hasClass(el, "someClass")) {
    removeClass(el, "someClass");
}
addClass(el, "someOtherClass");
Tim Down
fonte
Você testou usando o IE7? Como o LTE IE7 exige que strAttributeName seja "className" ao configurar, obter ou remover um atributo CLASS .
Knu
@ Knu: Ele definitivamente funciona no IE 6 e 7 para elementos HTML regulares, porque nesses navegadores ele usa a classNamepropriedade em vez de tentar definir um atributo. O motivo pelo qual "LTE IE7 exige que strAttributeName seja" className "ao definir, obter ou remover um atributo CLASS" é que esses navegadores têm uma implementação interrompida getAttribute(x)e setAttribute(x)que simplesmente obtêm ou definem a propriedade com o nome x, tornando-o mais fácil e compatível para definir a propriedade diretamente.
Tim Down
@ Knu: OK. Eu não tinha certeza, já que o SVG não é suportado no IE 7, então não tenho certeza do que você esperaria alcançar ao incluir um <svg>elemento em uma página projetada para funcionar no IE 7. Quanto vale a pena, meu código adiciona com êxito um atributo de classe aos <svg>elementos do IE 7, pois esse elemento se comporta como qualquer outro elemento personalizado.
Tim Down
Esqueci completamente disso, obrigado pelo lembrete. Vou tentar colocar o Adobe SVG Viewer em minhas mãos para verificar se funciona como pretendido.
Knu
2

Eu uso o Snap.svg para adicionar uma classe ao SVG.

var jimmy = Snap(" .jimmy ")

jimmy.addClass("exampleClass");

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

cjohndesign
fonte
1
Embora esse link possa responder à pergunta, é melhor incluir aqui as partes essenciais da resposta e fornecer o link para referência. As respostas somente para links podem se tornar inválidas se a página vinculada for alterada.
Tristan
1

Uma solução alternativa poderia ser addClass em um contêiner do 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
fonte
1

Eu escrevi isso no meu projeto, e funciona ... provavelmente;)

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

exemplos

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

Inspirado pelas respostas acima, especialmente por Sagar Gala, criei esta API . Você pode usá-lo se não quiser ou não puder atualizar sua versão do jquery.

Triturador de pedras
fonte
-1

Ou apenas use métodos DOM da velha escola quando o JQ tiver um macaco no meio em algum lugar.

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

Aprenda o pessoal do DOM. Mesmo no framework-palooza de 2016, ajuda bastante regularmente. Além disso, se você ouvir alguém comparar o DOM com a montagem, chute-o para mim.

Erik Reppen
fonte