Dynamically added SVG element does not appear even with createElementNS

Viewed 91

The html code and how it shows up on the inspector

Hello, I am starting to learn some web development. It has been going well, until I tried to dynamically generate some SVG rectangles in a group. But even though the code shows up on the inspector, when I hover over the dynamically generated rectangle code, it says "Rect 0x0", and does not visually appear. I have searched many other posts, but I have not found a solution.

function getMousePosition(svg, event) {
    var CTM = svg.getScreenCTM();
    if (event.touches) { event = event.touches[0]; }
    return {
        x: (event.clientX - CTM.e) / CTM.a,
        y: (event.clientY - CTM.f) / CTM.d
    };
}
var NS = "http://www.w3.org/2000/svg", elemWidth = 80, elemHeight = 40, slotWidth = 10, slotHeight = 10;

function spawnElem(event){
    let svg = event.target;
    if(event.which == 3){
        let pos = getMousePosition(svg, event)
        spawnElemHelper(svg, pos.x, pos.y);
    }
}

function spawnElemHelper(svg, x, y){
    topLeft = {x: x - elemWidth / 2,
               y: y - elemHeight / 2
            };

    // group = document.createElementNS(NS, 'g');
    // group.classList.add('draggable-group');

    main = document.createElementNS("http://www.w3.org/2000/svg", 'rect');
    main.setAttributeNS(NS, 'x', String(topLeft.x));
    main.setAttributeNS(NS, 'y', String(topLeft.y));
    main.setAttributeNS(NS, 'width', String(elemWidth));
    main.setAttributeNS(NS, 'height', String(elemHeight));
    main.setAttributeNS(NS, 'fill', 'blue');

    // slotInput = document.createElementNS(NS, 'rect');
    // slotInput.classList.add("slot");
    // slotInput.setAttributeNS(NS, 'x', String(topLeft.x));
    // slotInput.setAttributeNS(NS, 'y', String(topLeft.y + (elemHeight - slotHeight) / 2));
    // slotInput.setAttributeNS(NS, 'width', String(slotWidth));
    // slotInput.setAttributeNS(NS, 'height', String(slotHeight));
    // slotInput.setAttributeNS(NS, 'fill', 'yellow');

    // slotOutput = document.createElementNS(NS, 'rect');
    // slotOutput.classList.add("slot");
    // slotOutput.setAttributeNS(NS, 'x', String(topLeft.x + elemWidth - slotWidth));
    // slotOutput.setAttributeNS(NS, 'y', String(topLeft.y + (elemHeight - slotHeight) / 2));
    // slotOutput.setAttributeNS(NS, 'width', String(slotWidth));
    // slotOutput.setAttributeNS(NS, 'height', String(slotHeight));
    // slotOutput.setAttributeNS(NS, 'fill', 'yellow');

    // group.appendChild(main);
    // group.appendChild(slotInput);
    // group.appendChild(slotOutput);
    
    //svg.appendChild(group);
    svg.appendChild(main);
}
//var svg is the svg element in the HTML which has the id "canvas"
document.getElementById('canvas').addEventListener('mousedown', spawnElem);
2 Answers

SVG uses the namespace only for elements and not for attributes. The attributes are in the "empty" namespace. This is typical for XML (several formats do this) and namespaced attributes are required to use a prefix.

You can use setAttribute() or setAttributeNS('', ...):

const xmlns_svg = 'http://www.w3.org/2000/svg';

const svg = document.implementation.createDocument(xmlns_svg, 'svg', null);
svg.documentElement.setAttribute('width', '100');
svg.documentElement.setAttribute('height', '100');

const rect = svg.documentElement.appendChild(
  svg.createElementNS(xmlns_svg, 'rect')
);
rect.setAttribute('x', '25');
rect.setAttribute('y', '25');
rect.setAttribute('width', '50');
rect.setAttribute('height', '50');
rect.setAttribute('fill', 'blue');

document.querySelector('div.demo').appendChild(
  document.importNode(svg.documentElement, true)
);

const svgData = (new XMLSerializer()).serializeToString(svg);
document.querySelector('img.demo').setAttribute(
  'src',
  'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgData)
);
<div class="demo"></div>

<img class="demo"/>

Use setAttribute instead of setAttributeNS.

Related