O anexo do jquery não funciona com o elemento svg?

199

Assumindo isso:

<html>
<head>
 <script type="text/javascript" src="jquery.js"></script>
 <script type="text/javascript">
 $(document).ready(function(){
  $("svg").append('<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>');
 });
 </script>
</head>
<body>
 <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
 </svg>
</body>

Por que não vejo nada?


fonte

Respostas:

249

Quando você passa uma string de marcação $, ela é analisada como HTML usando a innerHTMLpropriedade do navegador em um <div>(ou outro contêiner adequado para casos especiais como <tr>). innerHTMLnão pode analisar SVG ou outro conteúdo que não seja HTML e, mesmo que pudesse, não seria capaz de dizer que <circle>deveria estar no espaço de nome SVG.

innerHTMLnão está disponível no SVGElement - é uma propriedade apenas do HTMLElement. Atualmente, não há uma innerSVGpropriedade ou outra maneira (*) de analisar o conteúdo em um SVGElement. Por esse motivo, você deve usar métodos no estilo DOM. O jQuery não oferece acesso fácil aos métodos no espaço de nomes necessários para criar elementos SVG. Realmente o jQuery não foi projetado para uso com SVG e muitas operações podem falhar.

O HTML5 promete permitir que você use <svg>sem um documento xmlnsHTML ( text/html) dentro do futuro. Mas isso é apenas um hack do analisador (**), o conteúdo SVG ainda será SVGElements no espaço de nome SVG, e não HTMLElements, portanto, você não poderá usá-lo innerHTMLmesmo que pareça parte de um documento HTML.

No entanto, para os navegadores de hoje, você deve usar X HTML (servido corretamente como application/xhtml+xml; salve com a extensão de arquivo .xhtml para testes locais) para fazer com que o SVG funcione. (De qualquer forma, faz sentido; SVG é um padrão adequadamente baseado em XML.) Isso significa que você precisaria escapar dos <símbolos dentro do bloco de script (ou incluir em uma seção CDATA) e incluir a xmlnsdeclaração XHTML . exemplo:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head>
</head><body>
    <svg id="s" xmlns="http://www.w3.org/2000/svg"/>
    <script type="text/javascript">
        function makeSVG(tag, attrs) {
            var el= document.createElementNS('http://www.w3.org/2000/svg', tag);
            for (var k in attrs)
                el.setAttribute(k, attrs[k]);
            return el;
        }

        var circle= makeSVG('circle', {cx: 100, cy: 50, r:40, stroke: 'black', 'stroke-width': 2, fill: 'red'});
        document.getElementById('s').appendChild(circle);
        circle.onmousedown= function() {
            alert('hello');
        };
    </script>
</body></html>

*: bem, há o parseWithContext do DOM Nível 3 LS , mas o suporte ao navegador é muito ruim. Edite para adicionar: no entanto, embora não seja possível injetar marcação em um SVGElement, você pode injetar um novo SVGElement em um HTMLElement usando innerHTMLe transferi-lo para o destino desejado. Provavelmente será um pouco mais lento:

<script type="text/javascript"><![CDATA[
    function parseSVG(s) {
        var div= document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
        div.innerHTML= '<svg xmlns="http://www.w3.org/2000/svg">'+s+'</svg>';
        var frag= document.createDocumentFragment();
        while (div.firstChild.firstChild)
            frag.appendChild(div.firstChild.firstChild);
        return frag;
    }

    document.getElementById('s').appendChild(parseSVG(
        '<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" onmousedown="alert(\'hello\');"/>'
    ));
]]></script>

**: Eu odeio a maneira como os autores do HTML5 parecem ter medo de XML e determinados a colocar recursos baseados em XML na bagunça suja que é HTML. O XHTML resolveu esses problemas anos atrás.

bobince
fonte
7
Esta resposta ainda é relevante! Eu tinha um bug bizarro no qual elementos adicionados eram exibidos no inspetor de elementos do Chrome, mas não eram renderizados. Se eu RMB> edit as htmlna tag html e pressionar enter, tudo será exibido (mas todos os ouvintes de eventos desaparecerão). Depois de ler esta resposta, mudei minhas chamadas createElement para createElementNS e agora tudo funciona!
Kitsu.eb
7
Você pode manipular os elementos SVG com jquery, uma vez criados, usando o método DOM createElementNS. Você pode alterar a função makeSVG para 'return $ (el)' e agora possui um elemento svg que pode usar os métodos jquery.
Hoffmann #
6
Ah! É por isso que não vai funcionar. Eu tentei exatamente a mesma coisa com duas bibliotecas diferentes, um em jQuery e um em D3.js . Eu obtive exatamente a mesma saída de origem em HTML usando os dois, mas os elementos gerados pelo jQuery não renderizariam enquanto os gerados pelo D3! Eu recomendo usar o D3:d3.select('body').append('svg').attr('width','100%');
chharvey
3
@ MadsSkjern: O DOM Level 3 LS nunca chegou aos navegadores. No entanto, o DOMParser originalmente exclusivo da Mozilla agora é mais amplamente suportado, e o jQuery possui $.parseXML.
21915
1
Ainda é relevante. bobince odeia a bagunça do HTML5, eu odeio a bagunça de toda a web, porque em nenhum lugar você pode encontrar um bom código sem hacks por causa dessa bagunça. WWW deve ser renomeado para WWM World Wide Mess.
amigos estão dizendo sobre olivier pons
149

A resposta aceita mostra uma maneira muito complicada. Como Forresto afirma em sua resposta , " parece adicioná-los no explorador DOM, mas não na tela " e a razão para isso são diferentes namespaces para html e svg.

A solução mais fácil é "atualizar" svg inteiro. Após anexar o círculo (ou outros elementos), use o seguinte:

$("body").html($("body").html());

Isso faz o truque. O círculo está na tela.

Ou, se desejar, use um contêiner div:

$("#cont").html($("#cont").html());

E envolva seu svg dentro do container div:

<div id="cont">
    <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
    </svg>
</div>

O exemplo funcional:
http://jsbin.com/ejifab/1/edit

As vantagens desta técnica:

  • você pode editar svg existente (que já está no DOM), por exemplo. criado usando Raphael ou como no seu exemplo "codificado" sem script.
  • você pode adicionar estruturas de elementos complexos como strings, por exemplo. $('svg').prepend('<defs><marker></marker><mask></mask></defs>');como você faz no jQuery.
  • depois que os elementos são anexados e tornados visíveis na tela usando $("#cont").html($("#cont").html());seus atributos, podem ser editados usando o jQuery.

EDITAR:

A técnica acima funciona apenas com SVG "codificado" ou manipulado pelo DOM (= document.createElementNS etc.). Se o Raphael for usado para criar elementos, (de acordo com meus testes), a vinculação entre objetos do Raphael e o SVG DOM será interrompida se $("#cont").html($("#cont").html());for usada. A solução alternativa para isso é não usar $("#cont").html($("#cont").html());nada e, em vez disso, usar documento SVG fictício.

Este SVG fictício é primeiro uma representação textual do documento SVG e contém apenas os elementos necessários. Se queremos, por exemplo. Para adicionar um elemento de filtro ao documento do Raphael, o manequim pode ser algo parecido <svg id="dummy" style="display:none"><defs><filter><!-- Filter definitons --></filter></defs></svg>. A representação textual é primeiro convertida para DOM usando o método $ ("body"). Append () do jQuery. E quando o elemento (filter) está no DOM, ele pode ser consultado usando métodos jQuery padrão e anexado ao documento SVG principal criado por Raphael.

Por que esse manequim é necessário? Por que não adicionar um elemento de filtro estritamente ao documento criado pelo Rafael? Se você tentar usando, por exemplo. $("svg").append("<circle ... />"), ele é criado como elemento html e nada está na tela, conforme descrito nas respostas. Mas se todo o documento SVG for anexado, o navegador manipulará automaticamente a conversão de namespace de todos os elementos no documento SVG.

Um exemplo esclarece a técnica:

// Add Raphael SVG document to container element
var p = Raphael("cont", 200, 200);
// Add id for easy access
$(p.canvas).attr("id","p");
// Textual representation of element(s) to be added
var f = '<filter id="myfilter"><!-- filter definitions --></filter>';

// Create dummy svg with filter definition 
$("body").append('<svg id="dummy" style="display:none"><defs>' + f + '</defs></svg>');
// Append filter definition to Raphael created svg
$("#p defs").append($("#dummy filter"));
// Remove dummy
$("#dummy").remove();

// Now we can create Raphael objects and add filters to them:
var r = p.rect(10,10,100,100);
$(r.node).attr("filter","url(#myfilter)");

A demonstração completa dessa técnica está disponível em: http://jsbin.com/ilinan/1/edit .

(Ainda não faço ideia, por $("#cont").html($("#cont").html());que não funciona ao usar o Raphael. Seria um corte muito curto.)

Timo Kähkönen
fonte
Estou usando minha solução (feita em casa) para manipular SVG e tenho o mesmo problema que Raphael, ao usar o "truque de reparação" com $ (# picture) .html ..., mas minha solução foi reiniciar alguns SVG em cache elementos (marcação de retângulo, transformação e assim por diante)
halfbit 12/01
Você é um mago. Eu estava olhando para a resposta aceita e fiquei tipo cara, isso vai ser uma dor. Então, eu vi isso e ele faz tudo o que eu preciso em uma linha curta. Obrigado!
The Compositor
1
Isso estava me fazendo perder meus mármores ... pensei que poderia usar magicamente os elmos js DOM no namespace SVG, como se poderia pensar ... fez com que o inspetor de tags reconhecesse as inserções ... mas não os dados até agora!
Gus Crawford
Funcionou muito bem ao anexar minhas tags de polígono e caminho existentes a uma tag svg pai recém-criada. Obrigado!
Kivak Wolf
1
$("#cont").html($("#cont").html());funcionou muito bem no Chrome, mas não funcionou no IE 11 para mim.
CoderDennis
37

A biblioteca D3, cada vez mais popular, lida muito bem com as peculiaridades de anexar / manipular svg. Você pode considerar usá-lo em oposição aos hacks do jQuery mencionados aqui.

HTML

<svg xmlns="http://www.w3.org/2000/svg"></svg>

Javascript

var circle = d3.select("svg").append("circle")
    .attr("r", "10")
    .attr("style", "fill:white;stroke:black;stroke-width:5");
nategood
fonte
boa resposta. isso me ajudou em alguma questão aqui.
Ismail baig #: 16803
O jQuery também engasga com a configuração / obtenção de classes do SVG também. Apenas um aparte.
precisa saber é o seguinte
24

O JQuery não pode anexar elementos <svg>(parece adicioná-los no explorador DOM, mas não na tela).

Uma solução alternativa é anexar um <svg>com todos os elementos necessários à página e modificar os atributos dos elementos usando .attr().

$('body')
  .append($('<svg><circle id="c" cx="10" cy="10" r="10" fill="green" /></svg>'))
  .mousemove( function (e) {
      $("#c").attr({
          cx: e.pageX,
          cy: e.pageY
      });
  });

http://jsfiddle.net/8FBjb/1/

forresto
fonte
Obrigado, me ajudou muito! Esta é uma abordagem muito boa. Em vez de anexar <circle>ou outros elementos individualmente, usando métodos DOM complexos e lentos (por exemplo, createDocumentFragment()ou createElementNS()), anexe todo o documento svg ao container. Em vez de $ ('body'), é claro que também pode haver alguns $ ('div'). Depois disso, o documento svg está no DOM e pode ser consultado de maneira familiar usando, por exemplo. $ ('c'). attr ('preenchimento', 'vermelho').
Timo Kähkönen 3/11
Fiz um exemplo adicional disso: jsbin.com/inevaz/1 . Ele obtém o código-fonte tiger.svg da internet para o iframe e pode ser copiado e colado para a área de texto. O conteúdo da área de texto é então usado como uma fonte para svg embutido, que é acessível através do DOM (para alterar o estilo do traçado).
Timo Kähkönen 3/11
Isso é ótimo, não sei por que todo mundo está votando nessas outras respostas que simplesmente não funcionam.
Dustin Poissant
Esta foi a melhor resposta para mim também. Eu precisava de um svg com um <use xlink: href = "# icon"> e essa foi a resposta mais curta possível.
28516 Eydamos
17

Não vi alguém mencionar esse método, mas document.createElementNS()é útil nesse caso.

Você pode criar os elementos usando Javascript baunilha como nós DOM normais com o espaço para nome correto e, em seguida, jQuery-ify-los a partir daí. Igual a:

var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
    circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');

var $circle = $(circle).attr({ //All your attributes });

$(svg).append($circle);

O único lado negativo é que você precisa criar cada elemento SVG com o espaço para nome certo individualmente ou isso não funcionará.

Chris Dolphin
fonte
2
A resposta de Timo (mais votada) funcionou no Chrome, mas não no IE. Esta resposta resolveu o problema de ambos os navegadores!
CoderDennis
1
Agradável! Bom saber
Chris Dolphin
14

Encontrei uma maneira fácil que funciona com todos os navegadores que tenho (Chrome 49, Edge 25, Firefox 44, IE11, Safari 5 [Win], Safari 8 (MacOS)):

// Clean svg content (if you want to update the svg's objects)
// Note : .html('') doesn't works for svg in some browsers
$('#svgObject').empty();
// add some objects
$('#svgObject').append('<polygon class="svgStyle" points="10,10 50,10 50,50 10,50 10,10" />');
$('#svgObject').append('<circle class="svgStyle" cx="100" cy="30" r="25"/>');

// Magic happens here: refresh DOM (you must refresh svg's parent for Edge/IE and Safari)
$('#svgContainer').html($('#svgContainer').html());
.svgStyle
{
  fill:cornflowerblue;
  fill-opacity:0.2;
  stroke-width:2;
  stroke-dasharray:5,5;
  stroke:black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="svgContainer">
  <svg id="svgObject" height="100" width="200"></svg>
</div>

<span>It works if two shapes (one square and one circle) are displayed above.</span>

Matthieu Charbonnier
fonte
5
Essa linha de atualização é exatamente o que eu estava procurando. Navegadores tolos. Obrigado!
Sam Soffes
8

Eu posso ver um círculo no firefox, fazendo duas coisas:

1) Renomeando arquivo de html para xhtml

2) Mude o script para

<script type="text/javascript">
$(document).ready(function(){
    var obj = document.createElementNS("http://www.w3.org/2000/svg", "circle");
    obj.setAttributeNS(null, "cx", 100);
    obj.setAttributeNS(null, "cy", 50);
    obj.setAttributeNS(null, "r",  40);
    obj.setAttributeNS(null, "stroke", "black");
    obj.setAttributeNS(null, "stroke-width", 2);
    obj.setAttributeNS(null, "fill", "red");
    $("svg")[0].appendChild(obj);
});
</script>
Topera
fonte
1
Oito anos depois e isso ainda é útil!
01001100 Mar 01 '11
5

Com base na resposta de @ chris-dolphin, mas usando a função auxiliar:

// Creates svg element, returned as jQuery object
function $s(elem) {
  return $(document.createElementNS('http://www.w3.org/2000/svg', elem));
}

var $svg = $s("svg");
var $circle = $s("circle").attr({...});
$svg.append($circle);
Jonas Berlin
fonte
1
Naturalmente, você também pode:$svg.append($s('<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'));
Jonas Berlin
4

Se a sequência que você precisa anexar for SVG e você adicionar o espaço para nome adequado, poderá analisar a sequência como XML e anexar ao pai.

var xml = jQuery.parseXML('<circle xmlns="http://www.w3.org/2000/svg" cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>');
$("svg").append(xml.documentElement))
jdesjean
fonte
3

A resposta aceita por Bobince é uma solução curta e portátil. Se você não apenas anexar SVG, mas também manipulá-lo, tente a biblioteca JavaScript "Pablo" (escrevi). Isso parecerá familiar para os usuários do jQuery.

Seu exemplo de código ficaria assim:

$(document).ready(function(){
    Pablo("svg").append('<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>');
});

Você também pode criar elementos SVG em tempo real, sem especificar a marcação:

var circle = Pablo.circle({
    cx:100,
    cy:50,
    r:40
}).appendTo('svg');
Premasagar
fonte
1

Eu sugeriria que seria melhor usar o ajax e carregar o elemento svg de outra página.

$('.container').load(href + ' .svg_element');

Onde href é o local da página com o svg. Dessa forma, você pode evitar qualquer efeito instável que possa ocorrer ao substituir o conteúdo html. Além disso, não se esqueça de desembrulhar o svg após o carregamento:

$('.svg_element').unwrap();
Dan185
fonte
0

Isso está funcionando para mim hoje com o FF 57:

function () {
    // JQuery, today, doesn't play well with adding SVG elements - tricks required
    $(selector_to_node_in_svg_doc).parent().prepend($(this).clone().text("Your"));
    $(selector_to_node_in_svg_doc).text("New").attr("x", "340").text("New")
        .attr('stroke', 'blue').attr("style", "text-decoration: line-through");
}

Faz:

esta imagem SVG como vista no Firefox 57

paul_h
fonte
Não são elementos SVG que você está anexando, é conteúdo de texto.
Robert Longson
Pode ser, mas foi renderizado após o carregamento inicial da página do SVG incorporado estaticamente.
paul_h
Então, o que, não é a pergunta que está sendo feita, que é sobre elementos svg e não conteúdo de texto.
Robert Longson
$(this).clone()está clonando um elemento SVG (e são filhos, se houver). Olhe além do uso de text(). Estou pegando um único tspan com "Your New" e terminando com duas colheres de chá, uma com "Your" (preto) e outra com "New" (azul com linha). Caramba, de 0 votos para -1 :-(
paul_h
0
 var svg; // if you have variable declared and not assigned value.
 // then you make a mistake by appending elements to that before creating element    
 svg.appendChild(document.createElement("g"));
 // at some point you assign to svg
 svg = document.createElementNS('http://www.w3.org/2000/svg', "svg")
 // then you put it in DOM
 document.getElementById("myDiv").appendChild(svg)
 // it wont render unless you manually change myDiv DOM with DevTools

// to fix assign before you append
var svg = createElement("svg", [
    ["version", "1.2"],
    ["xmlns:xlink", "http://www.w3.org/1999/xlink"],
    ["aria-labelledby", "title"],
    ["role", "img"],
    ["class", "graph"]
]);
function createElement(tag, attributeArr) {
      // .createElementNS  NS is must! Does not draw without
      let elem = document.createElementNS('http://www.w3.org/2000/svg', tag);             
      attributeArr.forEach(element => elem.setAttribute(element[0], element[1]));
      return elem;
}
// extra: <circle> for example requires attributes to render. Check if missing.
4baad4
fonte
-1

Uma maneira muito mais simples é gerar seu SVG em uma string, criar um elemento HTML do wrapper e inserir a string svg no elemento HTML usando $("#wrapperElement").html(svgString). Isso funciona muito bem no Chrome e Firefox.

Jakob Jenkov
fonte