Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Augmenting SVG tag with Shadow DOM

I've been experimenting with HTML5 and SVG; and I'm very new to JavaScript and web development, so there's probably something I'm missing. I am trying to create reusable web components, leveraging some of the new features, such as HTML Imports, Shadow DOM, and extending existing web elements. I have two html files:

test.html

<html>
<head>
  <title>Test HTML5</title>
</head>
<body>
  <link rel="import" href="Elements/tank.html">

  <svg width="100%"
       viewBox="0 0 500 500"
       style="border: solid; border-width: thin; stroke-width: 2;">

    <polyline points="0,300 100,300 100,100 250,100 250,150 500,150"
              style="fill:none;stroke:black;stroke-width:3"
              onclick="window.alert('you clicked line')" />
    <svg is="my-tank" x="200" y="350" width="50" height="50"></svg>

  </svg>
</body>
</html>

tank.html

<html>
<body>
  <template id="tank">
    <rect fill="red" width="50" height="50"></rect>
  </template>

  <script type="text/javascript">
    var tankElement = document.currentScript.ownerDocument.querySelector('#tank');

    document.registerElement('my-tank', {
      prototype: Object.create(SVGSVGElement.prototype, {
        createdCallback: {
          value: function () {
            var root = this.createShadowRoot();
                        
            //Works
            root.innerHTML = tankElement.innerHTML;

            //Doesn't work
            //var tankTemplate = document.importNode(tankElement.content, true);
            //root.appendChild(tankTemplate);
          }
        }
      }),
      extends: 'svg'
    });
  </script>
</body>
</html>

The code under the //Works comment draws a red rectangle where I'd expect in the SVG drawable space. The code under //Doesn't work doesn't draw anything or yield any error.

Screenshot of //Works: Works

Screenshot of //Doesn't work: Doesn't work

I've been testing with Opera 41.0.

like image 937
Justin Huskic Avatar asked Nov 25 '16 18:11

Justin Huskic


2 Answers

You cannot do this, since svg cannot be a shadow host. Only a limited number of elements can be a shadow host:

article, aside, blockquote, body, div, footer, h1, h2, h3, h4, h5, h6, header, main, nav, p, section, span.

So not only svg, but also others like input or textarea cannot have shadow-doms (I'm talking about Shadow-DOM v1).

See the definition of the attachShadow for the details: https://dom.spec.whatwg.org/#dom-element-attachshadow

Note also that:

  • SVGSVGElement does not implement HTMLElement.
  • Also heard something about SVG's already containing a shadow-dom in their implementation, and with Custom Elements v1 you may have only one shadow-dom (see here: http://hayato.io/2016/shadowdomv1/).

Why don't you create instead a regular custom-element, which may contain <svg> elements inside its shadow-dom?

like image 80
MarcG Avatar answered Nov 02 '22 13:11

MarcG


It's because in your <template>, the <rect> element is not inside a <svg> tag, so it is not recognized as a SVGRectElement but as an HTMLUnknownElement instead.

To make it work, just add the <svg> tag around:

<template id="tank">
    <svg>
        <rect fill="red" width="50" height="50"></rect>
    </svg>
</template>

Now you can import the element using importNode(), or cloneNode():

//Works
var rect = tankElement.content.querySelector('rect').cloneNode();
root.appendChild(rect);

Update Importing mutliple SVG Elements

Put elements in <g>:

<template id="tank">
    <svg>
        <g>
            <rect fill="red" width="50" height="50"></rect>
            <rect fill="blue" width="25" height="50"></rect>
        </g>
    </svg>
</template>

Clone <g> with its content:

var g = tankElement.content.querySelector('g').cloneNode(true);
root.appendChild(g);
like image 27
Supersharp Avatar answered Nov 02 '22 13:11

Supersharp