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?
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));
}
});
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>
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: