web-dev-qa-db-fra.com

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

En supposant que:

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

192
1521237

Lorsque vous passez une chaîne de balisage dans $, elle est analysée au format HTML à l'aide de la propriété innerHTML du navigateur sur un <div> (ou tout autre conteneur approprié pour des cas particuliers tels que <tr>). innerHTML ne peut pas analyser de contenu SVG ou autre contenu non HTML. Même s'il le pouvait, il ne serait pas en mesure de dire que <circle> était censé se trouver dans l'espace de noms SVG.

innerHTML n'est pas disponible sur SVGElement. Il s'agit d'une propriété de HTMLElement uniquement. Il n’existe pas non plus de propriété innerSVG ni d’autre moyen (*) d’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 de nommage nécessaires pour créer des éléments SVG. En réalité, 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 un xmlns dans un document HTML simple (text/html) à l'avenir. Mais ceci n’est qu’un hack d’analyseur (**), le contenu SVG sera toujours SVGElements dans l’espace de noms SVG, et non pas HTMLElements, vous ne pourrez donc pas utiliser innerHTML même s’ils ressemble à une partie d'un document HTML.

Cependant, pour les navigateurs actuels, 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 du tout. (Cela semble logique de toute façon; SVG est une norme correctement basée sur XML.) Cela signifie que vous devez échapper aux symboles < à l'intérieur de votre bloc de script (ou les inclure dans une section CDATA) et inclure le XHTML xmlns déclaration. 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>

*: eh bien, il y a les DOM niveau 3 LS parseWithContext , mais le support du navigateur est très médiocre. Modifier pour ajouter: cependant, bien que vous ne puissiez pas injecter de balises dans un SVGElement, vous pouvez injecter un nouveau SVGElement dans un HTMLElement en utilisant 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 à insérer des fonctionnalités basées sur XML dans le désordre cruel qu'est HTML. XHTML a résolu ces problèmes il y a des années.

237
bobince

Le réponse acceptée montre trop compliqué. Comme Forresto le dit dans sa réponse , ", il semble les ajouter dans l'explorateur DOM, mais pas à l'écran " et la raison en est espaces de noms différents 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 voulez, utilisez un conteneur div:

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

Et envelopper 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 éditer svg existant (qui est déjà dans le DOM), par exemple. créé avec 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 peuvent être modifiés à l'aide de jQuery.

MODIFIER:

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

Ce fichier SVG factice est d’abord une représentation textuelle d’un 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 (filter) est dans le DOM, il peut être interrogé à l'aide de 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 essayez par exemple. $("svg").append("<circle ... />"), il est créé en tant qu'élément HTML et rien ne s'affiche à l'écran comme décrit dans les réponses. Mais si tout le 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 avec Raphaël. Ce serait un hack très court.)

143
Timo Kähkönen

La bibliothèque de plus en plus populaire D gère très bien les particularités de l’ajout/la manipulation de svg. Vous voudrez peut-être envisager de l'utiliser, par opposition aux piratages 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");
36
nategood

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 nécessaires à la page, puis à modifier les attributs des éléments à l'aide de .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/

25
forresto

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

Vous pouvez créer les éléments en utilisant Vanilla Javascript en tant que nœuds DOM normaux avec le nom de l'espace correct, puis en 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.

14
Chris Dolphin

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

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

1) renommer un fichier html en xhtml

2) Changer 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>
8
Topera

Basé sur la réponse de @ chris-dolphin mais 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);
5
Jonas Berlin

Si la chaîne à ajouter est SVG et que vous ajoutez l'espace de nom approprié, vous pouvez analyser la chaîne au format 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))
4
jdesjean

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 le bibliothèque JavaScript "Pablo" (je l'ai écrit). Cela semblera familier aux utilisateurs de jQuery.

Votre exemple de code ressemblerait alors à ceci:

$(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');
3
Premasagar

Je suggérerais qu'il serait peut-être préférable d'utiliser ajax et de charger l'élément svg depuis 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 les effets d'instabilité pouvant résulter du remplacement du contenu HTML. De plus, n'oubliez pas de déballer le svg après son chargement:

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

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:

this SVG image as seen in Firefox 57

0
paul_h
 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.
0
4baad4