append jquery nie działa z elementem svg?

199

Zakładając to:

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

Dlaczego nic nie widzę?


źródło

Odpowiedzi:

249

Kiedy przekazujesz ciąg znaczników $, jest on analizowany jako HTML przy użyciu właściwości przeglądarki innerHTMLna <div>(lub innym odpowiednim kontenerze w szczególnych przypadkach, takich jak <tr>). innerHTMLnie może przeanalizować SVG ani innej treści innej niż HTML, a nawet gdyby mógł, nie byłby w stanie stwierdzić, że <circle>powinien znajdować się w przestrzeni nazw SVG.

innerHTMLnie jest dostępny w SVGElement - jest tylko własnością HTMLElement. Nie ma obecnie innerSVGwłaściwości ani innego sposobu (*) do parsowania treści w SVGElement. Z tego powodu powinieneś używać metod w stylu DOM. jQuery nie zapewnia łatwego dostępu do metod przestrzeni nazw potrzebnych do tworzenia elementów SVG. Naprawdę jQuery nie jest w ogóle przeznaczony do użytku z SVG i wiele operacji może się nie powieść.

HTML5 obiecuje, że w przyszłości będziesz mógł używać go <svg>bez xmlnszwykłego text/htmldokumentu HTML ( ). Ale to tylko hack parsera (**), zawartość SVG nadal będzie SVGElements w przestrzeni nazw SVG, a nie HTMLElements, więc nie będziesz mógł używać, innerHTMLnawet jeśli wyglądają jak część dokumentu HTML.

Jednak w dzisiejszych przeglądarkach musisz używać X HTML (poprawnie wyświetlanego jako application/xhtml+xml; zapisz z rozszerzeniem .xhtml do testowania lokalnego), aby SVG w ogóle działało. (W każdym razie ma to sens; SVG jest właściwie standardem opartym na XML.) Oznacza to, że będziesz musiał uciec od <symboli wewnątrz bloku skryptu (lub ująć w sekcji CDATA) i dołączyć xmlnsdeklarację XHTML . przykład:

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

*: cóż, istnieje parseWithContext DOM Level 3 LS , ale obsługa przeglądarki jest bardzo słaba. Edytuj, aby dodać: chociaż nie możesz wstrzykiwać znaczników do SVGElement, możesz wstrzyknąć nowy SVGElement do HTMLElement za pomocą innerHTML, a następnie przenieść go do pożądanego celu. Prawdopodobnie będzie trochę wolniej:

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

**: Nienawidzę sposobu, w jaki autorzy HTML5 boją się XML-a i postanowili wprowadzić funkcje oparte na XML w kruchym bałaganie jakim jest HTML. XHTML rozwiązał te problemy lata temu.

Bobin
źródło
7
Ta odpowiedź jest nadal aktualna! Właśnie miałem dziwny błąd, w którym dodane elementy wyświetlają się w Inspektorze elementów Chrome, ale nie renderują się. Jeśli I RMB> edit as htmlna znaczniku html i naciśniesz, wpisz wszystko, co się wyświetla (ale wszystkie detektory zdarzeń znikają). Po przeczytaniu tej odpowiedzi zmieniłem wywołania createElement na createElementNS i teraz wszystko działa!
kitsu.eb
7
Można manipulować elementami SVG za pomocą jquery po ich utworzeniu za pomocą metody DOM createElementNS. Możesz zmienić funkcję makeSVG na „return $ (el)”, a teraz masz element svg, który może korzystać z metod jquery.
Hoffmann
6
Ach! Dlatego to nie zadziała. Próbowałem dokładnie to samo z dwóch różnych bibliotek, jeden w jQuery i jeden w D3.js . Otrzymałem dokładnie takie same dane wyjściowe w HTML przy użyciu obu, ale elementy wygenerowane przez jQuery nie renderowałyby się tak, jak te wygenerowane przez D3! Polecam użycie D3:d3.select('body').append('svg').attr('width','100%');
chharvey
3
@MadsSkjern: DOM Level 3 LS nigdy nie trafił do przeglądarek. DOMParser, pierwotnie dostępny tylko dla Mozilli, jest teraz bardziej obsługiwany, a jQuery ma $.parseXML.
Bobin
1
To wciąż aktualne. bobince nienawidzi bałaganu HTML5, nienawidzę bałaganu całej sieci, ponieważ nigdzie nie można znaleźć dobrego kodu bez włamań z powodu tego bałaganu. Nazwa WWW powinna zostać zmieniona na WWM World Wide Mess.
Olivier Pons,
149

W Zaakceptowanych odpowiedź pokazuje zbyt skomplikowany sposób. Jak twierdzi Forresto w swojej odpowiedziwydaje się, że dodaje je w eksploratorze DOM, ale nie na ekranie ”, a przyczyną tego są różne przestrzenie nazw dla html i svg.

Najłatwiejszym obejściem jest „odświeżenie” całego pliku svg. Po dołączeniu kręgu (lub innych elementów) użyj tego:

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

To załatwia sprawę. Krąg jest na ekranie.

Lub jeśli chcesz, użyj div kontenera:

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

I owinąć swój plik SVG wewnątrz div pojemnika:

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

Przykład funkcjonalny:
http://jsbin.com/ejifab/1/edit

Zalety tej techniki:

  • możesz edytować istniejący plik svg (który jest już w DOM), np. utworzony przy użyciu Raphaela lub jak w twoim przykładzie „na stałe” bez skryptów.
  • możesz dodawać złożone struktury elementów jako ciągi znaków, np. $('svg').prepend('<defs><marker></marker><mask></mask></defs>');jak ty w jQuery.
  • po dodaniu elementów i wyświetleniu $("#cont").html($("#cont").html());ich na ekranie za pomocą ich atrybutów można je edytować za pomocą jQuery.

EDYTOWAĆ:

Powyższa technika działa tylko z SVG „zakodowanym na stałe” lub DOM (= document.createElementNS itp.). Jeśli Raphael jest używany do tworzenia elementów, (zgodnie z moimi testami) połączenie między obiektami Raphaela i SVG DOM zostanie przerwane, jeśli $("#cont").html($("#cont").html());zostanie użyte. Obejściem tego problemu jest w ogóle nieużywanie, $("#cont").html($("#cont").html());a zamiast tego używanie fałszywego dokumentu SVG.

Ten manekin SVG jest najpierw tekstową reprezentacją dokumentu SVG i zawiera tylko potrzebne elementy. Jeśli chcemy np. aby dodać element filtrujący do dokumentu Raphaela, manekin może być podobny <svg id="dummy" style="display:none"><defs><filter><!-- Filter definitons --></filter></defs></svg>. Reprezentacja tekstowa jest najpierw konwertowana na DOM za pomocą metody $ („body”). Append () jQuery'ego. A gdy element (filter) znajduje się w DOM, można go odpytywać przy użyciu standardowych metod jQuery i dołączać do głównego dokumentu SVG, który jest tworzony przez Raphaela.

Dlaczego ten manekin jest potrzebny? Dlaczego nie dodać elementu filtrującego ściśle do dokumentu utworzonego przez Raphaela? Jeśli spróbujesz użyć np. $("svg").append("<circle ... />"), jest tworzony jako element HTML i nic nie jest wyświetlane na ekranie, jak opisano w odpowiedziach. Ale jeśli dołączony jest cały dokument SVG, przeglądarka automatycznie obsługuje konwersję przestrzeni nazw wszystkich elementów w dokumencie SVG.

Przykład oświeca 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)");

Pełna działająca wersja demonstracyjna tej techniki znajduje się tutaj: http://jsbin.com/ilinan/1/edit .

(Nie mam (jeszcze) pojęcia, dlaczego $("#cont").html($("#cont").html());nie działa przy użyciu Rafaela. Byłby to bardzo krótki hack.)

Timo Kähkönen
źródło
Korzystam z mojego (stworzonego przez siebie) rozwiązania do obsługi SVG i mam ten sam problem co Raphael, kiedy używam „sztuczki polegającej na ponownej analizie” za pomocą $ (# picture) .html ..., ale moim rozwiązaniem było ponowne zainicjowanie buforowanej SVG elementy (zaznaczanie prostokąta, transformacja itp.)
półbitek
Jesteś czarodziejem. Patrzyłem na przyjętą odpowiedź i byłem jak człowiek, to będzie ból. Potem to zobaczyłem i robi wszystko, czego potrzebuję, w jednym krótkim wierszu. Dziękuję Ci!
Kompozytor
1
To spowodowało, że straciłem moje MARBLESY ... pomyślałem, że mogę magicznie użyć wiązów DOM DOMU w przestrzeni nazw SVG, jak mogłoby się wydawać ... sprawiło, że inspektor tagów rozpoznał wstawienia ... ale do tej pory nie kości!
Gus Crawford
Świetnie sprawdził się podczas dodawania mojego istniejącego znacznika wielokąta i ścieżki do nowo utworzonego nadrzędnego znacznika svg. Dzięki!
Kivak Wolf,
1
$("#cont").html($("#cont").html());działał świetnie w Chrome, ale nie działał w IE 11 dla mnie.
CoderDennis,
37

Coraz popularniejsza biblioteka D3 bardzo ładnie radzi sobie z dziwactwami związanymi z dodawaniem / manipulowaniem SVG. Możesz rozważyć użycie go w przeciwieństwie do wspomnianych tutaj hacków jQuery.

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
źródło
niezła odpowiedź. pomógł mi tutaj jakiś problem.
ismail baig
jQuery także dusi się przy ustawianiu / pobieraniu klas z SVG. Tylko na bok.
QueueHammer
24

JQuery nie może dołączać elementów <svg>(wydaje się, że dodaje je w eksploratorze DOM, ale nie na ekranie).

Jednym z obejść tego problemu jest dołączenie <svg>wszystkich elementów potrzebnych do strony, a następnie zmodyfikowanie atrybutów elementów za pomocą .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
źródło
Dzięki, bardzo mi pomogłeś! To bardzo dobre podejście. Zamiast dołączania <circle>lub innych elementów indywidualnie przy użyciu złożonych i powolnych metod DOM (np. createDocumentFragment()Lub createElementNS()), dołącz cały dokument svg do kontenera. Zamiast $ („body”) może to być oczywiście również $ („div”). A potem dokument svg znajduje się w DOM i można go przeszukiwać w znany sposób, używając np. $ („c”). attr („fill”, „red”).
Timo Kähkönen,
Zrobiłem dodatkowy przykład: jsbin.com/inevaz/1 . Pobiera kod źródłowy tiger.svg z Internetu do iframe i można go skopiować i wkleić do obszaru tekstowego. Zawartość pola tekstowego jest następnie wykorzystywana jako źródło wbudowanego pliku svg, który jest następnie dostępny za pośrednictwem DOM (w celu zmiany stylu obrysu).
Timo Kähkönen
To wspaniale, nie wiem, dlaczego wszyscy głosują na te inne odpowiedzi, które po prostu nie działają.
Dustin Poissant
To też była dla mnie najlepsza odpowiedź. Potrzebowałem svg z <use xlink: href = "# icon"> i to była najkrótsza działająca odpowiedź.
Eydamos
17

Nie widziałem, żeby ktoś wspominał o tej metodzie, ale document.createElementNS()jest pomocny w tym przypadku.

Możesz tworzyć elementy za pomocą waniliowego Javascript jako normalnych węzłów DOM z poprawną przestrzenią nazw, a następnie stamtąd je pobrać. Tak jak:

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

Jedynym minusem jest to, że musisz utworzyć każdy element SVG z odpowiednią przestrzenią nazw osobno, inaczej nie zadziała.

Chris Dolphin
źródło
2
Odpowiedź Timo (najczęściej głosowana) działała w Chrome, ale nie w IE. Ta odpowiedź rozwiązała problem dla obu przeglądarek!
CoderDennis
1
Miły! Dobrze wiedzieć
Chris Dolphin
14

Znalazłem prosty sposób, który działa ze wszystkimi przeglądarkami, które mam (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
źródło
5
Ta linia odświeżania była dokładnie tym, czego szukałem. Głupie przeglądarki. Dzięki!
Sam Soffes,
8

Widzę okrąg w firefoxie, robiąc 2 rzeczy:

1) Zmiana nazwy pliku z html na xhtml

2) Zmień skrypt na

<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
źródło
1
Osiem lat później i to wciąż jest pomocne!
Don 01001100 27.01.19
5

Na podstawie odpowiedzi @ chris-dolphin, ale używając funkcji pomocnika:

// 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
źródło
1
Możesz oczywiście również:$svg.append($s('<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'));
Jonas Berlin
4

Jeśli ciąg, który musisz dołączyć, to SVG i dodasz odpowiednią przestrzeń nazw, możesz przeanalizować ciąg jako XML i dołączyć do elementu nadrzędnego.

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
źródło
3

Odpowiedź zaakceptowana przez Bobince to krótkie, przenośne rozwiązanie. Jeśli potrzebujesz nie tylko dołączyć SVG, ale także nim manipulować, możesz wypróbować bibliotekę JavaScript „Pablo” (napisałem to). Będzie to dobrze znane użytkownikom jQuery.

Twój przykład kodu wyglądałby wtedy następująco:

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

Możesz także tworzyć elementy SVG w locie, bez określania znaczników:

var circle = Pablo.circle({
    cx:100,
    cy:50,
    r:40
}).appendTo('svg');
Premasagar
źródło
1

Sugerowałbym, że lepiej byłoby użyć ajax i załadować element svg z innej strony.

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

Gdzie href to lokalizacja strony z svg. W ten sposób można uniknąć efektów roztrzęsienia, które mogą wystąpić podczas zastępowania zawartości HTML. Nie zapomnij też rozpakować pliku svg po załadowaniu:

$('.svg_element').unwrap();
Dan185
źródło
0

Działa to dla mnie dzisiaj z 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");
}

Sprawia:

ten obraz SVG widziany w przeglądarce Firefox 57

paul_h
źródło
To nie są elementy SVG, które dodajesz, to treść tekstowa.
Robert Longson
Być może, ale renderowane po początkowym załadowaniu strony statycznie wstawionego pliku SVG.
paul_h
Więc co, to nie jest pytanie, które dotyczy elementów svg, a nie treści tekstowej.
Robert Longson,
$(this).clone()klonuje element SVG (i to dzieci, jeśli takie miały). Rozejrzyj się za użyciem text(). Biorę pojedynczy tspan, który zawierał „Your New”, i kończę na dwóch tspan, jednym z „Your” w nim (czarny) i jednym z „New in it” (niebieski w / line-through). Jezu, z 0 głosów na -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
źródło
-1

Znacznie prostszym sposobem jest po prostu wygenerowanie pliku SVG w ciąg, utworzenie opakowania HTML i wstawienie ciągu svg do elementu HTML za pomocą $("#wrapperElement").html(svgString). Działa to dobrze w Chrome i Firefox.

Jakob Jenkov
źródło