Why is it necessary to use `document.createElementNS` when adding `svg` tags to an HTML document via JS?
Asked Answered
C

3

6

This won't work:

const svg = document.createElement('svg')
svg.setAttribute('height', '100')
svg.setAttribute('width', '100')
document.body.appendChild(svg)

const rect = document.createElement('rect')
rect.setAttribute('height', '100%')
rect.setAttribute('width', '100%')
rect.setAttribute('fill', 'red')
svg.appendChild(rect)

This will work:

const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
svg.setAttribute('height', '100')
svg.setAttribute('width', '100')
document.body.appendChild(svg)

const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
rect.setAttribute('height', '100%')
rect.setAttribute('width', '100%')
rect.setAttribute('fill', 'red')
svg.appendChild(rect)

Apparently I need to explicitely specify the namespace each time I create an svg tag or any of its descendants via JS.

Q1: Why is this necessary? Quoting MDN docs on the svg tag:

Note: The xmlns attribute is only required on the outermost svg element of SVG documents. It is unnecessary for inner svg elements or inside HTML documents.

Well I'm nesting the svg inside HTML here so the xmlns attribute shouldn't be necessary should it?

Q2: Can this be avoided?

Typing document.createElementNS('http://www.w3.org/2000/svg', 'rect') by hand each time is annoying.

Is there any shorter way?

Coolie answered 11/9, 2019 at 18:0 Comment(0)
O
8

If you call document.createElement("a") which <a> should it create? The HTML one or the SVG one? There lies the problem. Ditto script etc. You're not giving the browser any way to guess till the appendChild step and by then it's too late.

You can do this without namespaces via innerHTML because that takes a string with all the markup so the browser can parse and add in one step and thereby infer the correct namespace.

Or just create a wrapper function

function createSVGElement(tag) {
  return document.createElementNS('http://www.w3.org/2000/svg', tag)
}
Ossuary answered 12/9, 2019 at 6:32 Comment(4)
I'm not sure I can buy into the "why" of this answer: as per the HTML5 spec, the only SVG element allowed is an SVG 2.0 element, the spec for which updated all overlapping elements so that they're functionally identical between HTML and SVG. So, reasonably speaking, you'd expect that to remove the need for explicit namespacing: property and attribute assignment works identical for both specs, the only difference being on-page behaviour once inserted into the DOM, at which point the browser knows which underlying prototype to use (by looking at the ancestors for the insertion point).Brescia
@Mike'Pomax'Kamermans unfortunately that's mostly untrue. There are significant functional differences between an SVG a element and a HTML a element.Ossuary
Hmm, do you have a link to a resource that goes into detail there? Because as far as I can determine those differences are only realized once it's part of the DOM (at which point we've effectively achieved the same result as when innerHTML had been used), but not before. Except for title, all the HTML attributes are supported by the SVG version, but as a DOM element, you can set any attribute key/value you want, and as a JS object, you can set any property value you want.Brescia
(to explain: one absolutely needs the NS functions to make things work, and browsers certainly don't have reshape-on-(re)insertion implemented, so the fact "that" we need the NS functions is true, but the reason for it feels like we're passing the buck: based on the specs, the need for the NS functions seems nonexistent, beyond browser makers and spec editors deciding that it would be too much work to unify the two because having the two separate functions already had universal support, not wanting to force work on engine developers/maintainers)Brescia
B
0

First, the example that doesn't "work": It works as intended. It's hard to say how though because surrounding HTML document code isn't shown. I'd guess that the parser (and the browser) will not interpret the created elements as svg elements because of the issue.

Q1 - why is it necessary to specify the namespace? The xmlns attribute declares a default namespace for the subdocument where the attribute is. Every element without a namespace prefix will belong to default namespace. If you nest your svg inside an HTML document, I'd guess the default namespace would be an HTML namespace (any HTML version might be possible) because no other would make sense in an HTML document. The namespace-aware parser will then read those elements like h:rect where h is the prefix of that HTML namespace.

Q2 - Can it be avoided? Well if you really want, there are other ways around it, but it's much easier to not avoid it. In other words, there's no shorter way to do it.

Let's also look at your quote about the svg "tag":

Note: The xmlns attribute is only required on the outermost svg element of SVG documents. It is unnecessary for inner svg elements or inside HTML documents.

This note is about the serialized document. It doesn't say anything about the document tree or about how it should be constructed. createElement and createElementNS add element nodes to the document tree. The document tree can be serialized into an XML document in different ways, e.g. with a default namespace using the xmlns attribute or each namespace declared separately with xmlns:svg attribute etc. What matters is that you need namespace-qualified names for the svg elements. Otherwise they won't be interpreted as svg elements. If you declare the namespace in your XML document, the XML parser will assign that element the namespace you declared. Or going the other way, if your document tree has the namespace property for that element defined, the document writer will also have the namespace correctly declared in the serialized document, either by using default namespaces or namespace prefixes.

Blakeblakelee answered 11/9, 2019 at 18:49 Comment(0)
C
0

I guess document.createElement, as a method of the HTML document, uses its own namespace when creating an element without telling the namespace. So when it creates a circle element it is not at all a svg:circle element.

At this day, svg.createElement seems not implemented. It would have allowed writting document.getElementsByTagname('svg').createElement('circle') that would have produce a real svg:circle for the createElement method is now of the callee svg element.

If the document is a strict XML document, the syntax <svg:circle /> is possible in it, if the xmlns:svg='http://www.w3.org/2000/svg' is declared in the root node like the following :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html 
     PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html
    xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
    xmlns:svg="http://www.w3.org/2000/svg">
    <h1>My circle</h1>
    <svg:circle cx="20" cy="20" r="10" />
</html>

In such a document, I have not tested, it may be possible to write :

document.createElement('svg:circle');
Collect answered 25/1 at 21:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.