jquery's append funktioniert nicht mit svg element?

199

Angenommen, dies:

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

Warum sehe ich nichts?


quelle

Antworten:

249

Wenn Sie eine Markup-Zeichenfolge übergeben $, wird diese mithilfe der Browsereigenschaft innerHTMLin einem <div>(oder einem anderen geeigneten Container für Sonderfälle wie <tr>) als HTML analysiert . innerHTMLSVG oder andere Nicht-HTML-Inhalte können nicht analysiert werden, und selbst wenn dies möglich wäre, könnte es nicht erkennen, dass <circle>sie sich im SVG-Namespace befinden sollten.

innerHTMList in SVGElement nicht verfügbar - es ist nur eine Eigenschaft von HTMLElement. Weder gibt es derzeit eine innerSVGEigenschaft noch eine andere Möglichkeit (*), Inhalte in ein SVGElement zu analysieren. Aus diesem Grund sollten Sie Methoden im DOM-Stil verwenden. Mit jQuery haben Sie keinen einfachen Zugriff auf die Methoden mit Namespace, die zum Erstellen von SVG-Elementen erforderlich sind. Wirklich, jQuery ist überhaupt nicht für die Verwendung mit SVG ausgelegt und viele Vorgänge können fehlschlagen.

HTML5 verspricht, dass Sie es in Zukunft auch <svg>ohne ein xmlnseinfaches HTML ( text/html) -Dokument verwenden können. Dies ist jedoch nur ein Parser-Hack (**). Der SVG-Inhalt ist weiterhin SVGElements im SVG-Namespace und nicht HTMLElements. Sie können ihn also nicht verwenden innerHTML, obwohl er wie ein Teil eines HTML-Dokuments aussieht .

Für heutige Browser müssen Sie jedoch X- HTML verwenden (ordnungsgemäß application/xhtml+xmlbereitgestellt als : Speichern mit der Dateierweiterung .xhtml für lokale Tests), damit SVG überhaupt funktioniert. (Es macht sowieso Sinn; SVG ist ein ordnungsgemäß XML-basierter Standard.) Dies bedeutet, dass Sie die <Symbole in Ihrem Skriptblock maskieren (oder in einen CDATA-Abschnitt einschließen) und die XHTML- xmlnsDeklaration einschließen müssen . Beispiel:

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

*: Nun , es gibt parseWithContext von DOM Level 3 LS , aber die Browserunterstützung ist sehr schlecht. Bearbeiten, um hinzuzufügen: Obwohl Sie kein Markup in ein SVGElement einfügen können, können Sie ein neues SVGElement in ein HTMLElement einfügen innerHTMLund es dann auf das gewünschte Ziel übertragen. Es wird wahrscheinlich etwas langsamer sein:

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

**: Ich hasse die Art und Weise, wie die Autoren von HTML5 Angst vor XML zu haben scheinen und entschlossen sind, XML-basierte Funktionen in das blöde Chaos von HTML zu stecken. XHTML hat diese Probleme vor Jahren gelöst.

Bobince
quelle
7
Diese Antwort ist immer noch relevant! Ich hatte gerade einen bizarren Fehler, bei dem hinzugefügte Elemente im Chrome-Elementinspektor angezeigt wurden, aber nicht gerendert wurden. Wenn ich RMB> edit as htmlauf dem HTML-Tag drücke und die Eingabetaste drücke, wird alles angezeigt (aber alle Ereignis-Listener verschwinden). Nachdem ich diese Antwort gelesen hatte, änderte ich meine createElement-Aufrufe in createElementNS und jetzt funktioniert alles!
Kitsu.eb
7
Sie können die SVG-Elemente mit jquery bearbeiten, sobald sie mit der DOM-Methode createElementNS erstellt wurden. Sie können die makeSVG-Funktion in 'return $ (el)' ändern und haben jetzt ein svg-Element, das jquery-Methoden verwenden kann.
Hoffmann
6
Ah! Deshalb wird es nicht funktionieren. Ich versuchte , die genau die gleiche Sache mit zwei verschiedenen Bibliotheken, ein in jQuery und einem in D3.js . Ich habe mit beiden genau die gleiche Quellausgabe in HTML erhalten, aber die von jQuery generierten Elemente wurden nicht gerendert, während die von D3 generierten Elemente dies taten! Ich empfehle D3:d3.select('body').append('svg').attr('width','100%');
chharvey
3
@MadsSkjern: DOM Level 3 LS hat es nie in Browser geschafft. Der ursprünglich nur für Mozilla bestimmte DOMParser wird jetzt jedoch stärker unterstützt, und jQuery hat dies bereits getan$.parseXML .
Bobince
1
Es ist immer noch relevant. Bobince hasst das Durcheinander von HTML5, ich hasse das Durcheinander des gesamten Webs, weil man wegen dieses Durcheinanders nirgendwo guten Code ohne Hacks finden kann . WWW sollte in WWM World Wide Mess umbenannt werden.
Olivier Pons
149

Die akzeptierte Antwort zeigt einen zu komplizierten Weg. Wie Forresto in seiner Antwort behauptet , " scheint es, sie im DOM-Explorer hinzuzufügen, aber nicht auf dem Bildschirm ", und der Grund dafür sind unterschiedliche Namespaces für HTML und SVG.

Die einfachste Problemumgehung besteht darin, das gesamte SVG zu "aktualisieren". Verwenden Sie nach dem Anhängen eines Kreises (oder anderer Elemente) Folgendes:

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

Das macht den Trick. Der Kreis wird auf dem Bildschirm angezeigt.

Oder wenn Sie möchten, verwenden Sie einen Container div:

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

Und wickeln Sie Ihre SVG in 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>

Das Funktionsbeispiel:
http://jsbin.com/ejifab/1/edit

Die Vorteile dieser Technik:

  • Sie können vorhandene SVG-Dateien (die sich bereits in DOM befinden) bearbeiten, z. erstellt mit Raphael oder wie in Ihrem Beispiel "fest codiert" ohne Skripterstellung.
  • Sie können komplexe Elementstrukturen als Zeichenfolgen hinzufügen, z. $('svg').prepend('<defs><marker></marker><mask></mask></defs>');wie in jQuery.
  • Nachdem die Elemente angehängt und mithilfe $("#cont").html($("#cont").html());ihrer Attribute auf dem Bildschirm sichtbar gemacht wurden , können sie mit jQuery bearbeitet werden.

BEARBEITEN:

Die obige Technik funktioniert nur mit "hartcodiertem" oder DOM-manipuliertem (= document.createElementNS usw.) SVG. Wenn Raphael zum Erstellen von Elementen verwendet wird, wird (nach meinen Tests) die Verknüpfung zwischen Raphael-Objekten und SVG-DOM unterbrochen, wenn $("#cont").html($("#cont").html());es verwendet wird. Die Problemumgehung besteht darin, überhaupt nicht zu verwenden $("#cont").html($("#cont").html());und stattdessen ein Dummy-SVG-Dokument zu verwenden.

Diese Dummy-SVG ist zunächst eine Textdarstellung des SVG-Dokuments und enthält nur Elemente, die benötigt werden. Wenn wir zB wollen. Um dem Raphael-Dokument ein Filterelement hinzuzufügen, könnte der Dummy so etwas wie sein <svg id="dummy" style="display:none"><defs><filter><!-- Filter definitons --></filter></defs></svg>. Die Textdarstellung wird zuerst mit der Methode $ ("body"). Append () von jQuery in DOM konvertiert. Wenn sich das (Filter-) Element im DOM befindet, kann es mit Standard-jQuery-Methoden abgefragt und an das von Raphael erstellte SVG-Hauptdokument angehängt werden.

Warum wird dieser Dummy benötigt? Warum nicht ein Filterelement ausschließlich dem von Raphael erstellten Dokument hinzufügen? Wenn Sie es mit z. $("svg").append("<circle ... />")wird es als HTML-Element erstellt und es wird nichts auf dem Bildschirm angezeigt, wie in den Antworten beschrieben. Wenn jedoch das gesamte SVG-Dokument angehängt wird, übernimmt der Browser automatisch die Namespace-Konvertierung aller Elemente im SVG-Dokument.

Ein Beispiel zur Aufklärung der Technik:

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

Die vollständige Demo dieser Technik finden Sie hier: http://jsbin.com/ilinan/1/edit .

(Ich habe (noch) keine Ahnung, warum $("#cont").html($("#cont").html());bei Verwendung von Raphael nicht funktioniert. Es wäre ein sehr kurzer Hack.)

Timo Kähkönen
quelle
Ich verwende meine (hausgemachte) Lösung für die Handhabung von SVG und habe das gleiche Problem wie Raphael, wenn ich den "Reparsing-Trick" mit $ (# picture) .html ... verwende, aber meine Lösung bestand darin, eine zwischengespeicherte SVG neu zu starten Elemente (Markierungsrechteck, Transformation usw.)
Halfbit
Du bist ein Zauberer. Ich habe mir die akzeptierte Antwort angesehen und war wie ein Mann, das wird ein Schmerz. Dann habe ich das gesehen und es macht alles, was ich brauche, in einer kurzen Zeile. Danke dir!
Der Komponist
1
Dies hat mich veranlasst, meine Murmeln zu verlieren ... dachte, ich könnte magisch js DOM-Ulmen im SVG-Namespace verwenden, wie man denken könnte ... hat den Tag-Inspektor dazu gebracht, die Einfügungen zu erkennen ... aber bis jetzt keine Würfel!
Gus Crawford
Hat für mich hervorragend funktioniert, wenn ich meine vorhandenen Polygon- und Pfad-Tags an ein neu erstelltes übergeordnetes SVG-Tag angehängt habe. Vielen Dank!
Kivak Wolf
1
$("#cont").html($("#cont").html());hat in Chrome großartig funktioniert, aber in IE 11 für mich nicht funktioniert.
CoderDennis
37

Die immer beliebter werdende D3- Bibliothek behandelt die Kuriositäten des Anhängens / Manipulierens von SVG sehr gut. Möglicherweise möchten Sie es im Gegensatz zu den hier genannten jQuery-Hacks verwenden.

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
quelle
gute Antwort. es hat mir bei einigen Problemen hier geholfen.
ismail baig
jQuery drosselt auch das Setzen / Abrufen von Klassen aus SVG. Nur eine Seite.
QueueHammer
24

JQuery kann keine Elemente anhängen <svg>(es scheint sie im DOM-Explorer hinzuzufügen, aber nicht auf dem Bildschirm).

Eine Problemumgehung besteht darin, ein <svg>mit allen Elementen, die Sie benötigen, an die Seite anzuhängen und dann die Attribute der Elemente mithilfe von zu ändern .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
quelle
Danke, hat mir sehr geholfen! Dies ist ein sehr guter Ansatz. Anstatt <circle>einzelne Elemente mit komplexen und langsamen DOM-Methoden (z. B. createDocumentFragment()oder createElementNS()) einzeln anzuhängen, hängen Sie das gesamte SVG-Dokument an den Container an. Anstelle von $ ('body') kann es natürlich auch $ ('div') geben. Danach befindet sich das SVG-Dokument im DOM und kann auf vertraute Weise mit z. $ ('c'). attr ('fill', 'red').
Timo Kähkönen
Ich habe ein zusätzliches Beispiel dafür gemacht: jsbin.com/inevaz/1 . Es erhält den Quellcode von tiger.svg vom Internet zum Iframe und kann in den Textbereich kopiert und eingefügt werden. Der Inhalt des Textbereichs wird dann als Quelle für Inline-SVG verwendet, auf die dann über DOM zugegriffen werden kann (um den Strichstil zu ändern).
Timo Kähkönen
Das ist großartig, ich weiß nicht, warum jeder für die anderen Antworten stimmt, die einfach nicht funktionieren.
Dustin Poissant
Dies war auch für mich die beste Antwort. Ich brauchte ein SVG mit einem <use xlink: href = "# icon"> und dies war die kürzeste funktionierende Antwort.
Eydamos
17

Ich habe noch niemanden gesehen, der diese Methode erwähnt hat, document.createElementNS()ist aber in diesem Fall hilfreich.

Sie können die Elemente mit Vanilla Javascript als normale DOM-Knoten mit dem richtigen Namespace erstellen und von dort aus jQuery-ify. Wie so:

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

Der einzige Nachteil ist, dass Sie jedes SVG-Element mit dem richtigen Namespace einzeln erstellen müssen, sonst funktioniert es nicht.

Chris Dolphin
quelle
2
Timos Antwort (oben gewählt) funktionierte in Chrome, aber nicht im IE. Diese Antwort löste das Problem für beide Browser!
CoderDennis
1
Nett! Gut zu wissen
Chris Dolphin
14

Es wurde ein einfacher Weg gefunden, der mit allen Browsern funktioniert, die ich habe (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
quelle
5
Diese Aktualisierungszeile ist genau das, wonach ich gesucht habe. Dumme Browser. Vielen Dank!
Sam Soffes
8

Ich kann einen Kreis in Firefox sehen, der zwei Dinge tut:

1) Umbenennen der Datei von HTML in XML

2) Ändern Sie das Skript 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>
Topera
quelle
1
Acht Jahre später und das ist immer noch hilfreich!
Don 01001100
5

Basierend auf der Antwort von @ chris-dolphin, aber mit Hilfe der Hilfsfunktion:

// 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
quelle
1
Sie können natürlich auch tun:$svg.append($s('<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'));
Jonas Berlin
4

Wenn die Zeichenfolge, die Sie anhängen müssen, SVG ist und Sie den richtigen Namespace hinzufügen, können Sie die Zeichenfolge als XML analysieren und an das übergeordnete Element anhängen.

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

Die akzeptierte Antwort von Bobince ist eine kurze, tragbare Lösung. Wenn Sie SVG nicht nur anhängen, sondern auch bearbeiten müssen, können Sie die JavaScript-Bibliothek "Pablo" (ich habe es geschrieben) ausprobieren . JQuery-Benutzern wird es bekannt vorkommen.

Ihr Codebeispiel würde dann folgendermaßen aussehen:

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

Sie können SVG-Elemente auch im laufenden Betrieb erstellen, ohne ein Markup anzugeben:

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

Ich würde vorschlagen, dass es besser ist, Ajax zu verwenden und das SVG-Element von einer anderen Seite zu laden.

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

Wobei href der Speicherort der Seite mit dem SVG ist. Auf diese Weise können Sie nervöse Effekte vermeiden, die beim Ersetzen des HTML-Inhalts auftreten können. Vergessen Sie auch nicht, das SVG nach dem Laden auszupacken:

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

Das funktioniert heute bei mir mit 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");
}

Macht:

Dieses SVG-Bild ist in Firefox 57 zu sehen

paul_h
quelle
Das sind keine SVG-Elemente, die Sie anhängen, das ist Textinhalt.
Robert Longson
Das mag sein, aber es wird nach dem ersten Laden der Seite von statisch inline SVG gerendert.
Paul_h
Es geht also nicht um die Frage, die sich um SVG-Elemente und nicht um Textinhalte handelt.
Robert Longson
$(this).clone()klont ein SVG-Element (und es sind Kinder, wenn es welche hatte). Schauen Sie über die Verwendung von hinaus text(). Ich nehme einen einzelnen Tspan mit "Your New" und erhalte zwei Tspans, einen mit "Your" (schwarz) und einen mit "New in it" (blau mit Line-Through). Herrgott, von 0 Stimmen auf -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
quelle
-1

Eine viel einfachere Möglichkeit besteht darin, Ihre SVG-Datei einfach in eine Zeichenfolge zu generieren, ein Wrapper-HTML-Element zu erstellen und die SVG-Zeichenfolge mithilfe von in das HTML-Element einzufügen $("#wrapperElement").html(svgString). Dies funktioniert gut in Chrome und Firefox.

Jakob Jenkov
quelle