l'append de jquery ne fonctionne pas avec l'élément svg?

199

En supposant ceci:

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

Pourquoi je ne vois rien?


la source

Réponses:

249

Lorsque vous passez une chaîne de balisage dans $, elle est analysée en HTML en utilisant la innerHTMLpropriété du navigateur sur un <div>(ou un autre conteneur approprié pour des cas spéciaux comme <tr>). innerHTMLne peut pas analyser SVG ou tout autre contenu non HTML, et même s'il le pouvait, il ne pourrait pas dire qu'il <circle>était censé se trouver dans l'espace de noms SVG.

innerHTMLn'est pas disponible sur SVGElement - c'est une propriété de HTMLElement uniquement. Il n’existe pas non plus deinnerSVG propriété ou d'autre moyen (*) pour analyser le contenu dans un SVGElement. Pour cette raison, vous devez utiliser des méthodes de style DOM. jQuery ne vous donne pas un accès facile aux méthodes d'espaces de noms nécessaires pour créer des éléments SVG. Vraiment, jQuery n'est pas du tout conçu pour être utilisé avec SVG et de nombreuses opérations peuvent échouer.

HTML5 promet de vous permettre d'utiliser <svg>sans l' xmlnsintérieur d'un text/htmldocument HTML ( ) simple à l'avenir. Mais il ne s'agit que d'un piratage de l'analyseur (**), le contenu SVG sera toujours des SVGElements dans l'espace de noms SVG, et non des HTMLElements, vous ne pourrez donc pas l'utiliser innerHTMLmême s'ils ressemblent à une partie d'un document HTML.

Cependant, pour les navigateurs d'aujourd'hui, vous devez utiliser X HTML (correctement servi en tant que application/xhtml+xml; enregistrer avec l'extension de fichier .xhtml pour les tests locaux) pour que SVG fonctionne. (Cela a du sens de toute façon; SVG est une norme correctement basée sur XML.) Cela signifie que vous devez échapper les <symboles à l'intérieur de votre bloc de script (ou les enfermer dans une section CDATA) et inclure la xmlnsdéclaration XHTML . exemple:

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

*: bien, il y a parseWithContext de DOM Level 3 LS , mais le support du navigateur est très pauvre. Modifier pour ajouter: cependant, même si vous ne pouvez pas injecter de balisage dans un SVGElement, vous pouvez injecter un nouveau SVGElement dans un HTMLElement à l'aide innerHTML, puis le transférer vers la cible souhaitée. Ce sera probablement un peu plus lent cependant:

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

**: Je déteste la façon dont les auteurs de HTML5 semblent avoir peur du XML et sont déterminés à déchaîner les fonctionnalités basées sur XML dans le désordre cruel qu'est le HTML. XHTML a résolu ces problèmes il y a des années.

bobince
la source
7
Cette réponse est toujours d'actualité! J'ai juste eu un bug bizarre où les éléments ajoutés s'affichent dans l'inspecteur d'éléments Chrome, mais ne s'affichaient pas. Si je RMB> edit as htmlsur la balise html et appuyez sur Entrée, tout s'affiche (mais tous les écouteurs d'événement disparaissent). Après avoir lu cette réponse, j'ai changé mes appels createElement en createElementNS et maintenant tout fonctionne!
kitsu.eb
7
Vous pouvez manipuler les éléments SVG avec jquery une fois qu'ils sont créés à l'aide de la méthode DOM createElementNS. Vous pouvez changer la fonction makeSVG en 'return $ (el)' et maintenant vous avez un élément svg qui peut utiliser des méthodes jquery.
Hoffmann
6
Ah! Voilà pourquoi cela ne fonctionnera pas. J'ai essayé exactement la même chose avec deux bibliothèques différentes, l' une dans jQuery et un en d3.js . J'ai obtenu exactement la même sortie source en HTML en utilisant les deux, mais les éléments générés par jQuery ne se rendraient pas alors que ceux générés par D3 le feraient! Je recommande d'utiliser D3:d3.select('body').append('svg').attr('width','100%');
chharvey
3
@MadsSkjern: DOM Level 3 LS n'a jamais atteint les navigateurs. Le DOMParser uniquement à l'origine de Mozilla est désormais plus largement pris en charge, et jQuery l'a fait $.parseXML.
bobince
1
C'est toujours d'actualité. bobince déteste le désordre de HTML5, je déteste le désordre de l'ensemble du Web, car nulle part vous ne pouvez trouver un bon code sans piratage à cause de ce désordre. WWW doit être renommé WWM World Wide Mess.
Olivier Pons le
149

La réponse acceptée montre de manière trop compliquée. Comme Forresto le prétend dans sa réponse , " il semble les ajouter dans l'explorateur DOM, mais pas à l'écran " et la raison en est les différents espaces de noms pour html et svg.

La solution de contournement la plus simple consiste à "actualiser" l'ensemble du svg. Après avoir ajouté un cercle (ou d'autres éléments), utilisez ceci:

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

Cela fait l'affaire. Le cercle est à l'écran.

Ou si vous le souhaitez, utilisez un conteneur div:

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

Et enveloppez votre svg à l'intérieur du conteneur 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'exemple fonctionnel:
http://jsbin.com/ejifab/1/edit

Les avantages de cette technique:

  • vous pouvez modifier le svg existant (qui est déjà dans DOM), par exemple. créé en utilisant Raphael ou comme dans votre exemple "codé en dur" sans script.
  • vous pouvez ajouter des structures d'éléments complexes sous forme de chaînes, par exemple. $('svg').prepend('<defs><marker></marker><mask></mask></defs>');comme vous le faites dans jQuery.
  • une fois les éléments ajoutés et rendus visibles à l'écran à l'aide de $("#cont").html($("#cont").html());leurs attributs, vous pouvez les modifier à l'aide de jQuery.

ÉDITER:

La technique ci-dessus fonctionne uniquement avec SVG "codé en dur" ou manipulé par DOM (= document.createElementNS etc.). Si Raphael est utilisé pour créer des éléments, (selon mes tests) la liaison entre les objets Raphael et SVG DOM est rompue si elle $("#cont").html($("#cont").html());est utilisée. La solution de contournement est de ne pas utiliser $("#cont").html($("#cont").html());du tout et d'utiliser un document SVG factice.

Ce SVG factice est d'abord une représentation textuelle du document SVG et ne contient que les éléments nécessaires. Si nous voulons par exemple. pour ajouter un élément de filtre au document Raphael, le mannequin pourrait être quelque chose comme<svg id="dummy" style="display:none"><defs><filter><!-- Filter definitons --></filter></defs></svg> . La représentation textuelle est d'abord convertie en DOM à l'aide de la méthode $ ("body"). Append () de jQuery. Et lorsque l'élément (filtre) est dans DOM, il peut être interrogé à l'aide des méthodes jQuery standard et ajouté au document SVG principal créé par Raphael.

Pourquoi ce mannequin est nécessaire? Pourquoi ne pas ajouter un élément de filtre strictement au document créé par Raphael? Si vous l'essayez en utilisant par exemple. $("svg").append("<circle ... />"), il est créé en tant qu'élément html et rien n'est à l'écran comme décrit dans les réponses. Mais si l'ensemble du document SVG est ajouté, le navigateur gère automatiquement la conversion de l'espace de noms de tous les éléments du document SVG.

Un exemple éclaire la technique:

// 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 démonstration complète de cette technique est disponible ici: http://jsbin.com/ilinan/1/edit .

(Je n'ai (encore) aucune idée, pourquoi $("#cont").html($("#cont").html());ne fonctionne pas lors de l'utilisation de Raphaël. Ce serait un hack très court.)

Timo Kähkönen
la source
J'utilise ma solution (faite maison) pour gérer SVG et j'ai le même problème que Raphael, lorsque j'utilise le "truc de ré-analyse" avec $ (# image) .html ..., mais ma solution était de réinitialiser un SVG mis en cache éléments (marquage du rectangle, transformation et ainsi de suite)
halfbit
Tu es un sorcier. Je regardais la réponse acceptée et j'étais comme un homme, ça va être pénible. Ensuite, je l'ai vu et il fait tout ce dont j'ai besoin en une seule ligne courte. Je vous remercie!
The Composer
1
Cela me faisait perdre mes billes ... pensais que je pouvais magiquement utiliser les ormes DOM js sur l'espace de noms SVG comme on pourrait le penser ... a demandé à l'inspecteur de balises de reconnaître les insertions ... mais pas de dés jusqu'à maintenant!
Gus Crawford
A très bien fonctionné pour moi lors de l'ajout de mes balises de polygone et de chemin d'accès existantes à une balise svg parent nouvellement créée. Merci!
Kivak Wolf
1
$("#cont").html($("#cont").html());fonctionnait très bien dans Chrome, mais ne fonctionnait pas dans IE 11 pour moi.
CoderDennis
37

Le D3 de plus en plus populaire bibliothèque gère très bien les bizarreries de l'ajout / manipulation de svg. Vous voudrez peut-être envisager de l'utiliser contrairement aux hacks jQuery mentionnés ici.

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
la source
Bonne réponse. cela m'a aidé pour un problème ici.
ismail baig
jQuery s'étouffe également lors de la définition / obtention de classes à partir de SVG. Juste un aparté.
QueueHammer
24

JQuery ne peut pas ajouter d'éléments <svg>(il semble les ajouter dans l'explorateur DOM, mais pas à l'écran).

Une solution consiste à ajouter un <svg>avec tous les éléments dont vous avez besoin à la page, puis à modifier les attributs des éléments à l'aide .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
la source
Merci, ça m'a beaucoup aidé! C'est une très bonne approche. Au lieu d'ajouter <circle>ou d'autres éléments individuellement à l'aide de méthodes DOM complexes et lentes (par exemple, createDocumentFragment()ou createElementNS()), ajoutez tout le document svg dans le conteneur. Au lieu de $ ('body'), il peut aussi s'agir de $ ('div'). Et après cela, le document svg est sur DOM et peut être interrogé de manière familière en utilisant par exemple. $ ('c'). attr ('fill', 'red').
Timo Kähkönen du
J'en ai fait un exemple supplémentaire: jsbin.com/inevaz/1 . Il obtient le code source de tiger.svg d'Internet vers iframe et il peut être copié-collé dans textarea. Le contenu de textarea est ensuite utilisé comme source de svg en ligne qui est ensuite accessible via DOM (pour changer le style de trait).
Timo Kähkönen
C'est super, je ne sais pas pourquoi tout le monde vote pour ces autres réponses qui ne fonctionnent tout simplement pas.
Dustin Poissant
C'était aussi la meilleure réponse pour moi. J'avais besoin d'un svg avec un <use xlink: href = "# icon"> et c'était la réponse la plus courte.
Eydamos
17

Je n'ai vu personne mentionner cette méthode mais document.createElementNS()est utile dans ce cas.

Vous pouvez créer les éléments en utilisant vanilla Javascript comme nœuds DOM normaux avec l'espace de nom correct, puis les jQuery-ify à partir de là. Ainsi:

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

Le seul inconvénient est que vous devez créer chaque élément SVG avec le bon espace de noms individuellement, sinon cela ne fonctionnera pas.

Chris Dolphin
la source
2
La réponse de Timo (le mieux voté) fonctionnait dans Chrome, mais pas dans IE. Cette réponse a résolu le problème pour les deux navigateurs!
CoderDennis
1
Agréable! Bon à savoir
Chris Dolphin
14

J'ai trouvé un moyen simple qui fonctionne avec tous les navigateurs que j'ai (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
la source
5
Cette ligne de rafraîchissement est exactement ce que je cherchais. Navigateurs stupides. Merci!
Sam Soffes
8

Je peux voir un cercle dans Firefox, faisant 2 choses:

1) Renommer un fichier de html en xhtml

2) Changez le script en

<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
la source
1
Huit ans plus tard, c'est toujours utile!
Don 01001100
5

Basé sur la réponse de @ chris-dolphin mais en utilisant la fonction d'assistance:

// 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
la source
1
Vous pouvez bien sûr aussi faire:$svg.append($s('<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'));
Jonas Berlin
4

Si la chaîne que vous devez ajouter est SVG et que vous ajoutez l'espace de noms approprié, vous pouvez analyser la chaîne en XML et l'ajouter au parent.

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
la source
3

La réponse acceptée par Bobince est une solution courte et portable. Si vous devez non seulement ajouter SVG mais aussi le manipuler, vous pouvez essayer la bibliothèque JavaScript "Pablo" (je l'ai écrite). Cela semblera familier aux utilisateurs de jQuery.

Votre exemple de code ressemblerait alors à:

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

Vous pouvez également créer des éléments SVG à la volée, sans spécifier de balisage:

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

Je dirais qu'il serait préférable d'utiliser ajax et de charger l'élément svg à partir d'une autre page.

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

Où href est l'emplacement de la page avec le svg. De cette façon, vous pouvez éviter tout effet nerveux qui pourrait se produire en remplaçant le contenu html. N'oubliez pas non plus de déballer le svg après son chargement:

$('.svg_element').unwrap();
Dan185
la source
0

Cela fonctionne pour moi aujourd'hui avec 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");
}

Fait du:

cette image SVG comme vu dans Firefox 57

paul_h
la source
Ce ne sont pas des éléments SVG que vous ajoutez, c'est du contenu textuel.
Robert Longson
C'est possible, mais il est restitué après le chargement initial de la page du SVG statiquement intégré.
paul_h
Alors quoi, ce n'est pas la question posée, qui concerne les éléments svg et non le contenu texte.
Robert Longson
$(this).clone()clone un élément SVG (et ce sont des enfants s'il en avait). Regardez au-delà de l'utilisation de text(). Je prends une seule cuillère à café qui contenait "Votre nouveau" et je me retrouve avec deux cuillères à café, une avec "Votre" dedans (noir) et une avec "Nouveau dedans" (bleu avec ligne). Jeez, en baisse de 0 voix à -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
la source
-1

Une manière beaucoup plus simple consiste à simplement générer votre SVG dans une chaîne, à créer un élément HTML wrapper et à insérer la chaîne svg dans l'élément HTML à l'aide de $("#wrapperElement").html(svgString). Cela fonctionne très bien dans Chrome et Firefox.

Jakob Jenkov
la source