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?

jquery  html  svg 

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.


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.)


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

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/


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á.


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>


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>

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

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

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

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

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


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.

-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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.