Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Slotting a SVG inside another SVG in a Web Component

Here's a CodePen: https://codepen.io/neezer/pen/VwbZNYB

I want to slot a SVG inside another SVG in a custom web component, but each time I get a blank screen. In the example above, you can comment out all of the JS and see the red square that should appear in my custom web component. I know my browser supports <slot> because the MDN examples work.

What am I doing wrong?

like image 900
neezer Avatar asked Oct 27 '25 01:10

neezer


1 Answers

foreignObject only works when the user of the <svg-slot>Web Component (JSWC)
includes a valid SVG:

<svg-slot>
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" width="500" height="500">
    <circle cx="50%" cy="50%" r="15%" fill="green"></circle>
  </svg>
</svg-slot>

for the <template>:

<template>
  <style>svg{width:40vw}</style>
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" width="500" height="500">
    <circle cx="50%" cy="50%" r="25%" fill="red"></circle>
    <foreignObject x="0" y="0" width="100%" height="100%">
      <slot></slot>
    </foreignObject>
  </svg>
</template>

The <svg-slot> Web Component code required:

  customElements.define('svg-slot', class extends HTMLElement {
    connectedCallback() {
      this.attachShadow({mode:'open'})
          .append(document.querySelector('template').content.cloneNode(true));
    }
  });

But...

you want your Web Component users to write minimal semantic HTML:

<svg-slots>
  <circle slot="foo" cx="50%" cy="50%" r="15%" fill="green"></circle>
  <circle slot="bar" cx="50%" cy="50%" r= "5%" fill="gold"></circle>
</svg-slots>

That needs some extra work in the connectedCallback of the Web Component

Because <circle> in lightDOM now are Unknown HTML Elements.

So you need an extra step to turn everything in lightDOM into SVG (correct SVG NameSpace)

Which can then be injected into the (template) SVG in shadowDOM

<svg-slots>
  <circle slot="foo" cx="50%" cy="50%" r="30%" fill="green"></circle>
  <circle slot="bar" cx="50%" cy="50%" r="10%" fill="gold"></circle>
</svg-slots>

<template>
  <style>svg{ height:180px }</style>
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" width="500" height="500">
    <circle cx="50%" cy="50%" r="50%" fill="red"></circle>
    <slot name="foo"></slot>
    <slot name="bar"></slot>
  </svg>
</template>

<script>
  customElements.define('svg-slots', class extends HTMLElement {
    connectedCallback() {
      this.attachShadow({mode:'open'})
          .append(document.querySelector('template').content.cloneNode(true));
      setTimeout(()=>{ // make sure innerHTML is parsed
        let svg = document.createElementNS("http://www.w3.org/2000/svg","svg");
        svg.innerHTML = this.innerHTML;
        svg.querySelectorAll("*")
           .forEach(el =>
                    this
                      .shadowRoot
                      .querySelector(`slot[name="${el.getAttribute("slot")}"]`)
                      ?.replaceWith(el)
            )
      })
    }
  });
</script>

notes:

  • using the <slot> element; but not its functionality; it can be any (unknown) element.

  • the connectedCallback also fires when moving DOM nodes (eg. Drag Drop); you need extra code to prevent errors. attachShadow can be moved to the constructor

  • Can be re-factored to not use shadowDOM; then all SVGs can be styled with global CSS
    but you will get a FOUC because <svg-slots> 'innerHTML' now is shown as regular DOM,
    not lightDOM (optionally reflected/slotted into shadowDOM)

  • You can do a lot more with Unknown HTML Elements

    <pie-chart>
      <slice size="90" stroke="green">HTML</slice>
      <slice size="1"  stroke="red">JavaScript</slice>
      <slice size="9"  stroke="blue">CSS</slice>
    </pie-chart>
    

    creates a full SVG Pie Chart;

    See:

    • https://dev.to/dannyengelman/what-web-technologies-are-required-to-draw-a-pie-chart-in-2021-spoiler-alert-a-standard-web-component-will-do-1j56

    • https://dev.to/dannyengelman/web-components-using-unknownhtmlelements-for-better-semantic-html-5d8c

like image 115
Danny '365CSI' Engelman Avatar answered Oct 29 '25 15:10

Danny '365CSI' Engelman