l'append di jquery non funziona con l'elemento svg?


199

Supponendo questo:

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

Perché non vedo niente?

jquery  html  svg 

Risposte:


249

Quando si passa una stringa di markup $, viene analizzata come HTML utilizzando la innerHTMLproprietà del browser su un <div>(o altro contenitore adatto per casi speciali come <tr>). innerHTMLnon è in grado di analizzare SVG o altri contenuti non HTML, e anche se fosse possibile, non sarebbe in grado di dire che <circle>si suppone si trovasse nello spazio dei nomi SVG.

innerHTMLnon è disponibile su SVGElement, è solo una proprietà di HTMLElement. Né esiste attualmente una innerSVGproprietà o altro (*) per analizzare il contenuto in un SVGElement. Per questo motivo è necessario utilizzare metodi in stile DOM. jQuery non ti dà un facile accesso ai metodi spaziali necessari per creare elementi SVG. Davvero jQuery non è progettato per l'uso con SVG e molte operazioni potrebbero non riuscire.

HTML5 promette di consentire di utilizzare <svg>senza una xmlnsall'interno di un semplice HTML ( text/htmldocumento) in futuro. Ma questo è solo un hacker parser (**), il contenuto SVG sarà comunque SVGElements nello spazio dei nomi SVG e non HTMLElements, quindi non sarai in grado di usarlo innerHTMLanche se sembrano parte di un documento HTML.

Tuttavia, per i browser di oggi è necessario utilizzare X HTML (correttamente servito come application/xhtml+xml; salvare con l'estensione .xhtml per i test locali) per far funzionare SVG. (In qualche modo ha senso; SVG è uno standard correttamente basato su XML.) Ciò significa che dovresti sfuggire ai <simboli all'interno del tuo blocco di script (o racchiuderlo in una sezione CDATA) e includere la xmlnsdichiarazione XHTML . esempio:

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

*: beh, c'è parseWithContext di DOM Level 3 LS , ma il supporto del browser è molto scarso. Modifica per aggiungere: tuttavia, sebbene non sia possibile iniettare markup in un SVGElement, è possibile iniettare un nuovo SVGElement in un HTMLElement utilizzando innerHTML, quindi trasferirlo nella destinazione desiderata. Probabilmente sarà un po 'più lento però:

<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 il modo in cui gli autori di HTML5 sembrano aver paura dell'XML e sono determinati a incorporare funzionalità basate su XML nel caos crudele dell'HTML. XHTML ha risolto questi problemi anni fa.


7
Questa risposta è ancora pertinente! Ho appena avuto un bizzarro bug in cui elementi aggiunti vengono visualizzati nella finestra di ispezione degli elementi di Chrome, ma non vengono visualizzati. Se I RMB> edit as htmlsul tag html e premi invio, viene visualizzato tutto (ma tutti i listener di eventi svaniscono). Dopo aver letto questa risposta ho cambiato le mie chiamate createElement per createElementNS e ora tutto funziona!
kitsu.eb,

7
È possibile manipolare gli elementi SVG con jquery dopo che sono stati creati utilizzando il metodo DOM createElementNS. Puoi cambiare la funzione makeSVG in 'return $ (el)' e ora hai un elemento svg che può usare i metodi jquery.
Hoffmann,

6
Ah! Ecco perché non funzionerà. Ho provato la stessa cosa con due diverse biblioteche, uno in jQuery e uno in d3.js . Ho ottenuto esattamente lo stesso output di origine in HTML usando entrambi, ma gli elementi generati da jQuery non venivano visualizzati mentre quelli generati da D3 lo facevano! Consiglio di usare D3:d3.select('body').append('svg').attr('width','100%');
chharvey il

3
@MadsSkjern: DOM Level 3 LS non è mai arrivato ai browser. Il DOMParser, originariamente solo per Mozilla, ora è supportato più ampiamente, e jQuery lo ha $.parseXML.
bobince

1
È ancora rilevante. bobince odia il casino di HTML5, odio il casino di tutto il web, perché da nessuna parte puoi trovare un buon codice senza hack a causa di quel casino. Il WWW dovrebbe essere rinominato WWM World Wide Mess.
Olivier Pons

149

La risposta accettata mostra un modo troppo complicato. Come afferma Forresto nella sua risposta , " sembra aggiungerli in DOM Explorer, ma non sullo schermo " e la ragione di ciò sono diversi spazi dei nomi per html e svg.

La soluzione più semplice è "aggiornare" l'intero svg. Dopo aver aggiunto la cerchia (o altri elementi), usa questo:

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

Questo fa il trucco. Il cerchio è sullo schermo.

O se vuoi, usa un div contenitore:

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

E avvolgi il tuo svg all'interno del contenitore 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>

L'esempio funzionale:
http://jsbin.com/ejifab/1/edit

I vantaggi di questa tecnica:

  • puoi modificare lo svg esistente (che è già in DOM), ad es. creato usando Raphael o come nel tuo esempio "hard coded" senza script.
  • è possibile aggiungere strutture di elementi complessi come stringhe, ad es. $('svg').prepend('<defs><marker></marker><mask></mask></defs>');come fai in jQuery.
  • dopo che gli elementi sono stati aggiunti e resi visibili sullo schermo usando i $("#cont").html($("#cont").html());loro attributi possono essere modificati usando jQuery.

MODIFICARE:

La tecnica sopra funziona solo con SVG "hard coded" o DOM manipolato (= document.createElementNS ecc.). Se Raphael viene utilizzato per creare elementi, (secondo i miei test), il collegamento tra oggetti Raphael e SVG DOM viene interrotto se $("#cont").html($("#cont").html());viene utilizzato. La soluzione a questo non è affatto usare $("#cont").html($("#cont").html());e invece di usare un documento SVG fittizio.

Questo SVG fittizio è innanzitutto una rappresentazione testuale del documento SVG e contiene solo gli elementi necessari. Se vogliamo ad es. per aggiungere un elemento filtro al documento Raphael, il manichino potrebbe essere qualcosa del genere <svg id="dummy" style="display:none"><defs><filter><!-- Filter definitons --></filter></defs></svg>. La rappresentazione testuale viene prima convertita in DOM usando il metodo $ ("body"). Append () di jQuery. E quando l'elemento (filtro) è in DOM, può essere interrogato utilizzando i metodi jQuery standard e aggiunto al documento SVG principale creato da Raphael.

Perché questo manichino è necessario? Perché non aggiungere un elemento filtro rigorosamente al documento creato da Raphael? Se lo provi usando ad es. $("svg").append("<circle ... />"), viene creato come elemento html e nulla è sullo schermo come descritto nelle risposte. Ma se viene aggiunto l'intero documento SVG, il browser gestisce automaticamente la conversione dello spazio dei nomi di tutti gli elementi nel documento SVG.

Un esempio chiarisce la tecnica:

// 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 demo completa di funzionamento di questa tecnica è qui: http://jsbin.com/ilinan/1/edit .

(Non ho (ancora) idea, perché $("#cont").html($("#cont").html());non funziona quando si utilizza Raphael. Sarebbe un hack molto breve.)


Sto usando la mia soluzione (fatta in casa) per gestire SVG e ho lo stesso problema di Raphael, quando uso il "trucco di riprogrammazione" con $ (# immagine) .html ..., ma la mia soluzione era di riavviare un SVG memorizzato nella cache elements (rettangolo di marcatura, trasformazione e così via)
halfbit

Sei un mago. Stavo guardando la risposta accettata ed ero come un uomo, questo sarà un dolore. Poi, ho visto questo e fa tutto ciò di cui ho bisogno in una breve riga. Grazie!
Il compositore

1
QUESTO ERA CAUSANDO ME PER PERDERE I MIEI MARMI ... pensavo di poter usare magicamente gli elmi DOM js nello spazio dei nomi SVG come si potrebbe pensare ... ho ottenuto l'ispettore tag per riconoscere gli inserimenti ... ma non i dadi fino ad ora!
Gus Crawford,

Ha funzionato alla grande per me quando ho aggiunto i miei tag poligono e percorso esistenti a un tag svg genitore appena creato. Grazie!
Kivak Wolf,

1
$("#cont").html($("#cont").html());ha funzionato benissimo in Chrome, ma non ha funzionato in IE 11 per me.
CoderDennis

37

La libreria D3 sempre più popolare gestisce le stranezze di aggiungere / manipolare svg molto bene. Potresti prendere in considerazione l'idea di usarlo al contrario degli hack di jQuery menzionati qui.

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

bella risposta. mi ha aiutato per qualche problema qui.
Ismail Baig,

jQuery soffoca anche sull'impostazione / acquisizione di classi da SVG. Solo a parte.
QueueHammer

24

JQuery non può aggiungere elementi a <svg>(sembra aggiungerli in DOM Explorer, ma non sullo schermo).

Una soluzione alternativa è aggiungere un <svg>con tutti gli elementi necessari alla pagina e quindi modificare gli attributi degli elementi utilizzando .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/


Grazie, mi ha aiutato molto! Questo è un ottimo approccio. Invece di aggiungere <circle>singolarmente o altri elementi usando metodi DOM complessi e lenti (es. createDocumentFragment()O createElementNS()), aggiungi l'intero documento svg al contenitore. Invece di $ ('body') può ovviamente essere anche $ ('div'). E dopo che il documento svg è su DOM e può essere interrogato in modo familiare usando ad es. $ ( 'c'). attr ( 'riempimento', 'rosso').
Timo Kähkönen,

Ne ho fatto un ulteriore esempio: jsbin.com/inevaz/1 . Ottiene il codice sorgente di tiger.svg da internet su iframe e può essere Copia-Incolla su textarea. Il contenuto di textarea viene quindi utilizzato come sorgente per svg inline che è quindi accessibile tramite DOM (per cambiare lo stile del tratto).
Timo Kähkönen,

Questo è fantastico, non so perché tutti votino per quelle altre risposte che semplicemente non funzionano.
Dustin Poissant,

Questa è stata la risposta migliore anche per me. Avevo bisogno di uno svg con <usa xlink: href = "# icon"> e questa era la risposta operativa più breve.
Eydamos,

17

Non ho visto qualcuno menzionare questo metodo, ma document.createElementNS()è utile in questo caso.

È possibile creare gli elementi utilizzando Javascript vanilla come normali nodi DOM con lo spazio dei nomi corretto e quindi jQuery-ify da lì. Così:

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

L'unico lato negativo è che devi creare ogni elemento SVG con il giusto spazio dei nomi individualmente o non funzionerà.


2
La risposta di Timo (votata per primo) ha funzionato in Chrome, ma non in IE. Questa risposta ha risolto il problema per entrambi i browser!
CoderDennis

1
Bello! Buono a sapersi
Chris Dolphin il

14

Ho trovato un modo semplice che funziona con tutti i browser che ho (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
Quella linea di aggiornamento è esattamente quello che stavo cercando. Browser stupidi. Grazie!
Sam Soffes

8

Riesco a vedere il cerchio in Firefox, facendo 2 cose:

1) Rinominare il file da HTML a HTML

2) Cambia lo script in

<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
Otto anni dopo e questo è ancora utile!
Don 01001100,

5

Basato sulla risposta di @ chris-dolphin ma usando la funzione helper:

// 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
Ovviamente puoi anche fare:$svg.append($s('<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'));
Jonas Berlin,

4

Se la stringa che devi aggiungere è SVG e aggiungi lo spazio dei nomi corretto, puoi analizzare la stringa come XML e accodarla 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))

3

La risposta accettata da Bobince è una soluzione breve e portatile. Se hai bisogno non solo di aggiungere SVG ma anche di manipolarlo, potresti provare la libreria JavaScript "Pablo" (l'ho scritto). Sarà familiare agli utenti di jQuery.

Il tuo esempio di codice sarebbe quindi simile a:

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

Puoi anche creare elementi SVG al volo, senza specificare il markup:

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

1

Suggerirei che potrebbe essere meglio usare Ajax e caricare l'elemento svg da un'altra pagina.

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

Dove href è la posizione della pagina con lo svg. In questo modo puoi evitare qualsiasi effetto nervoso che potrebbe verificarsi sostituendo il contenuto html. Inoltre, non dimenticare di scartare lo svg dopo che è stato caricato:

$('.svg_element').unwrap();

0

Questo funziona per me oggi 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");
}

Fa:

questa immagine SVG come si vede in Firefox 57


Non sono gli elementi SVG che stai aggiungendo, sono contenuti di testo.
Robert Longson,

Questo può essere, ma è stato visualizzato dopo il caricamento iniziale della pagina di SVG con allineamento statico.
paul_h

Quindi, non è la domanda che viene posta, che riguarda gli elementi svg e non il contenuto del testo.
Robert Longson,

$(this).clone()sta clonando un elemento SVG (ed è figlio se ne avesse uno). Guarda oltre l'uso di text(). Sto prendendo un unico tspan che conteneva "Il tuo nuovo" e finisco con due tspan, uno con "Il tuo" (nero) e uno con "Nuovo" (blu con line-through). Accidenti, in calo da 0 voti 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.

-1

Un modo molto più semplice è semplicemente generare il tuo SVG in una stringa, creare un elemento HTML wrapper e inserire la stringa svg nell'elemento HTML usando $("#wrapperElement").html(svgString). Funziona bene su Chrome e Firefox.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.