¿jquery's append no funciona con el elemento svg?

199

Asumiendo esto:

<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 qué no veo nada?


fuente

Respuestas:

249

Cuando pasa una cadena de marcado $, se analiza como HTML utilizando la innerHTMLpropiedad del navegador en un <div>(u otro contenedor adecuado para casos especiales como <tr>). innerHTMLno puede analizar SVG u otro contenido que no sea HTML, e incluso si pudiera no podría decir que <circle>se suponía que estaba en el espacio de nombres SVG.

innerHTMLno está disponible en SVGElement: es una propiedad de HTMLElement únicamente. Tampoco hay actualmente uninnerSVG propiedad u otra forma (*) para analizar el contenido en un elemento SVGE. Por esta razón, debe usar métodos de estilo DOM. jQuery no le brinda fácil acceso a los métodos de espacios de nombres necesarios para crear elementos SVG. Realmente jQuery no está diseñado para usarse con SVG y muchas operaciones pueden fallar.

HTML5 promete permitirte usar <svg>sin un documento xmlnsHTML ( text/html) plano en el futuro. Pero esto es solo un truco del analizador sintáctico (**), el contenido SVG seguirá siendo SVGElements en el espacio de nombres SVG y no HTMLElements, por lo que no podrá usarlo innerHTMLaunque parezca parte de un documento HTML.

Sin embargo, para los navegadores de hoy debe usar X HTML (servido correctamente como application/xhtml+xml; guardar con la extensión de archivo .xhtml para pruebas locales) para que SVG funcione. (De todos modos, tiene sentido; SVG es un estándar basado en XML). Esto significa que tendría que escapar de los <símbolos dentro de su bloque de script (o encerrarlo en una sección CDATA) e incluir la xmlnsdeclaración XHTML . ejemplo:

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

*: bueno, hay parseWithContext de DOM Level 3 LS , pero el soporte del navegador es muy pobre. Editar para agregar: sin embargo, aunque no puede inyectar marcado en un elemento SVGE, puede inyectar un nuevo elemento SVGE en un elemento HTMLE usando innerHTML, luego transferirlo al objetivo deseado. Sin embargo, es probable que sea un poco más 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>

**: Odio la forma en que los autores de HTML5 parecen tener miedo de XML y están decididos a calzar las características basadas en XML en el desorden que es HTML. XHTML resolvió estos problemas hace años.

bobince
fuente
77
¡Esta respuesta sigue siendo relevante! Acabo de tener un error extraño en el que los elementos agregados se muestran en el inspector de elementos de Chrome, pero no se muestran. Si I RMB> edit as htmlen la etiqueta html y presiono enter, se muestra todo (pero todos los oyentes de eventos desaparecen). Después de leer esta respuesta, cambié mis llamadas createElement a createElementNS y ahora todo funciona.
kitsu.eb
77
Puede manipular los elementos SVG con jquery una vez que se crean utilizando el método DOM createElementNS. Puede cambiar la función makeSVG a 'return $ (el)' y ahora tiene un elemento svg que puede usar métodos jquery.
Hoffmann el
66
Ah! Por eso no funcionará. Probé exactamente lo mismo con dos bibliotecas diferentes, uno en jQuery y uno en D3.js . Obtuve exactamente la misma fuente de salida en HTML usando ambos, pero los elementos generados por jQuery no se renderizaron mientras que los generados por D3 sí. Recomiendo usar D3:d3.select('body').append('svg').attr('width','100%');
chharvey
3
@MadsSkjern: DOM Level 3 LS nunca llegó a los navegadores. Sin embargo, el DOMParser originalmente exclusivo de Mozilla ahora es más ampliamente compatible, y jQuery sí $.parseXML.
bobince
1
Sigue siendo relevante. bobince odia el desorden de HTML5, odio el desorden de toda la web, porque en ningún lugar puedes encontrar un buen código sin hacks debido a ese desorden. WWW debería renombrarse a WWM World Wide Mess.
Olivier Pons
149

La respuesta aceptada muestra una forma demasiado complicada. Como Forresto afirma en su respuesta , " parece agregarlos en el explorador DOM, pero no en la pantalla " y la razón de esto son los diferentes espacios de nombres para html y svg.

La solución más fácil es "actualizar" todo el svg. Después de agregar un círculo (u otros elementos), use esto:

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

Esto hace el truco. El círculo está en la pantalla.

O si lo desea, use un contenedor div:

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

Y envuelva su svg dentro del contenedor 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>

El ejemplo funcional:
http://jsbin.com/ejifab/1/edit

Las ventajas de esta técnica:

  • puede editar svg existente (que ya está en DOM), por ejemplo. creado usando Rafael o similar en su ejemplo "codificado" sin scripting.
  • puede agregar estructuras de elementos complejos como cadenas, por ejemplo. $('svg').prepend('<defs><marker></marker><mask></mask></defs>');como haces en jQuery.
  • después de que los elementos se agreguen y se hagan visibles en la pantalla con $("#cont").html($("#cont").html());sus atributos, se pueden editar con jQuery.

EDITAR:

La técnica anterior funciona solo con SVG "codificado" o DOM manipulado (= document.createElementNS, etc.). Si se usa Raphael para crear elementos, (de acuerdo con mis pruebas) la conexión entre los objetos Raphael y SVG DOM se rompe si $("#cont").html($("#cont").html());se usa. La solución a esto es no usar $("#cont").html($("#cont").html());en absoluto y en lugar de usar un documento SVG ficticio.

Este SVG ficticio es primero una representación textual del documento SVG y contiene solo los elementos necesarios. Si queremos, por ejemplo. para agregar un elemento de filtro al documento de Raphael, el ficticio podría ser algo así <svg id="dummy" style="display:none"><defs><filter><!-- Filter definitons --></filter></defs></svg>. La representación textual se convierte primero a DOM utilizando el método $ ("body"). Append () de jQuery. Y cuando el elemento (filtro) está en DOM, puede consultarse utilizando métodos jQuery estándar y adjuntarse al documento SVG principal creado por Raphael.

¿Por qué se necesita este muñeco? ¿Por qué no agregar un elemento de filtro estrictamente al documento creado por Raphael? Si lo intentas usando eg. $("svg").append("<circle ... />"), se crea como elemento html y no hay nada en la pantalla como se describe en las respuestas. Pero si se agrega todo el documento SVG, entonces el navegador maneja automáticamente la conversión del espacio de nombres de todos los elementos en el documento SVG.

Un ejemplo ilustra la 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)");

La demostración de trabajo completa de esta técnica está aquí: http://jsbin.com/ilinan/1/edit .

(Todavía no tengo idea, por qué $("#cont").html($("#cont").html());no funciona cuando se usa Raphael. Sería un truco muy corto).

Timo Kähkönen
fuente
Estoy usando mi solución (Hecho en casa) para manejar SVG y tengo el mismo problema que Raphael, cuando uso el "truco de reparsing" con $ (# picture) .html ..., pero mi solución fue reiniciar algunos SVG en caché elementos (marcando rectángulo, transformación, etc.)
bit
Eres un mago Estaba mirando la respuesta aceptada y pensé que sería un dolor. Entonces, vi esto y hace todo lo que necesito en una línea corta. ¡Gracias!
El compositor
1
ESTO ME CAUSABA PARA PERDER MIS MÁRMOLES ... pensé que podía usar mágicamente los olmos js DOM en el espacio de nombres SVG como uno podría pensar ... conseguí que el inspector de etiquetas reconociera las inserciones ... ¡pero no dados hasta ahora!
Gus Crawford
Funcionó muy bien para mí al agregar mis etiquetas de polígono y ruta existentes a una etiqueta svg padre recién creada. ¡Gracias!
Kivak Wolf
1
$("#cont").html($("#cont").html());funcionó muy bien en Chrome, pero no funcionó en IE 11 para mí.
CoderDennis
37

La biblioteca D3 cada vez más popular maneja las rarezas de agregar / manipular svg muy bien. Es posible que desee considerar su uso en lugar de los hacks jQuery mencionados aquí.

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
fuente
buena respuesta. Me ayudó por algún problema aquí.
ismail baig
jQuery también se ahoga al establecer / obtener clases de SVG también. Solo un aparte.
QueueHammer
24

JQuery no puede agregar elementos a <svg>(parece agregarlos en el explorador DOM, pero no en la pantalla).

Una solución alternativa es agregar un archivo <svg>con todos los elementos que necesita a la página, y luego modificar los atributos de los 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
fuente
Gracias, me ayudo mucho! Este es un muy buen enfoque. En lugar de agregar <circle>u otros elementos individualmente utilizando métodos DOM complejos y lentos (por ejemplo, createDocumentFragment()o createElementNS()), agregue todo el documento svg al contenedor. En lugar de $ ('cuerpo') puede ser, por supuesto, también $ ('div'). Y después de eso, el documento svg está en DOM y se puede consultar de manera familiar usando, por ejemplo, $ ('c'). attr ('relleno', 'rojo').
Timo Kähkönen
Hice un ejemplo adicional de esto: jsbin.com/inevaz/1 . Obtiene el código fuente de tiger.svg de internet a iframe y se puede copiar y pegar en textarea. El contenido de textarea se usa como fuente para svg en línea, que luego se puede acceder a través de DOM (para cambiar el estilo de trazo).
Timo Kähkönen
Esto es genial, no sé por qué todos votan por esas otras respuestas que simplemente no funcionan.
Dustin Poissant
Esta fue la mejor respuesta para mí también. Necesitaba un svg con un <use xlink: href = "# icon"> y esta fue la respuesta de trabajo más corta.
Eydamos
17

No he visto a nadie mencionar este método, pero document.createElementNS()es útil en este caso.

Puede crear los elementos usando Javascript vainilla como nodos DOM normales con el espacio de nombres correcto y luego jQuery-ify desde allí. Al igual que:

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

El único inconveniente es que debe crear cada elemento SVG con el espacio de nombres correcto individualmente o no funcionará.

Chris Dolphin
fuente
2
La respuesta de Timo (mejor votada) funcionó en Chrome, pero no en IE. ¡Esta respuesta resolvió el problema para ambos navegadores!
CoderDennis
1
¡Agradable! Es bueno saberlo
Chris Dolphin
14

Encontré una manera fácil que funciona con todos los navegadores que tengo (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
fuente
55
Esa línea de actualización es exactamente lo que estaba buscando. Navegadores tontos. ¡Gracias!
Sam Soffes
8

Puedo ver el círculo en Firefox, haciendo 2 cosas:

1) Cambiar el nombre del archivo de html a xhtml

2) Cambiar script a

<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
fuente
1
¡Ocho años después y esto sigue siendo útil!
Don 01001100
5

Basado en la respuesta de @ chris-dolphin pero usando la función de ayuda:

// 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
fuente
1
Por supuesto, también puede hacer:$svg.append($s('<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'));
Jonas Berlin
4

Si la cadena que necesita agregar es SVG y agrega el espacio de nombres adecuado, puede analizar la cadena como XML y agregarla al padre.

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
fuente
3

La respuesta aceptada por Bobince es una solución breve y portátil. Si necesita no solo agregar SVG sino también manipularlo, puede probar la biblioteca de JavaScript "Pablo" (lo escribí). Se sentirá familiar para los usuarios de jQuery.

Su código de ejemplo se vería así:

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

También puede crear elementos SVG sobre la marcha, sin especificar el marcado:

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

Sugeriría que podría ser mejor usar ajax y cargar el elemento svg desde otra página.

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

Donde href es la ubicación de la página con el svg. De esta forma, puede evitar cualquier efecto nervioso que pueda ocurrir al reemplazar el contenido html. Además, no olvides desenvolver el svg después de cargarlo:

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

Esto funciona para mí hoy con 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");
}

Hace:

esta imagen SVG como se ve en Firefox 57

paul_h
fuente
Eso no son elementos SVG que está agregando, es contenido de texto.
Robert Longson
Puede ser, pero se procesó después de la carga inicial de la página de SVG estáticamente en línea.
paul_h
Entonces, qué, no es la pregunta que se hace, que se trata de elementos svg y no de contenido de texto.
Robert Longson
$(this).clone()está clonando un elemento SVG (y son hijos si tuviera alguno). Mira más allá del uso de text(). Estoy tomando una sola tspan que tenía "Your New" en ella, y terminé con dos tspans, una con "Your" en ella (negro) y otra con "New in it" (azul con línea). Dios, de 0 votos a -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
fuente
-1

Una forma mucho más simple es generar su SVG en una cadena, crear un elemento HTML envoltorio e insertar la cadena svg en el elemento HTML usando $("#wrapperElement").html(svgString). Esto funciona bien en Chrome y Firefox.

Jakob Jenkov
fuente