svelte icon indicating copy to clipboard operation
svelte copied to clipboard

<svelte:element this="svg"> generates proper html but doesn't get rendered properly.

Open sandeep-gh opened this issue 3 years ago • 7 comments

Describe the bug

I am using a python library that builds a json describing the html components. This is then fed to a svelte code that uses svelte:element to instantiate the html components. All components (button/iinput) works fine except for svg elements. The html is generated properly as shown below

<button value="9" class="...." style=""> <svg viewbox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" class="h-6 w-6" style=""> <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" style=""> </path></svg></button>```

however the svg doesn't gets rendered on the screen (the button does though)

### Reproduction

Kind of tricky for this case to build a reproduction. If really needed, I can try. Let know. 

### Logs

_No response_

### System Info

```shell
System:
    OS: Linux 5.15 Debian GNU/Linux 11 (bullseye) 11 (bullseye)
    CPU: (4) arm64 Cortex-A72
    Memory: 2.39 GB / 3.68 GB
    Container: Yes
    Shell: 5.1.4 - /bin/bash
  Binaries:
    Node: 18.3.0 - /usr/bin/node
    npm: 8.11.0 - /usr/bin/npm
  Browsers:
    Firefox: 91.10.0esr
  npmPackages:
    svelte: ^3.48.0 => 3.48.0

Severity

annoyance

sandeep-gh avatar Jun 17 '22 06:06 sandeep-gh

I believe you have to define the width or height of the SVG using css / inline style. e.g:

button svg {
    height: 16px;
}

This is default behaviour. Your usage of svelte:element is fine and not causing issues.

yimme avatar Jun 17 '22 10:06 yimme

This didn't help. I have two svg elements : one in plain html, another using svelte. They both are identcal in definition. HTML renders fine. Svelte one doesn't. Attaching the html and the output:

<div id="components">
      <div class="container mx-auto flex justify-center">
      <button value="9" class="bg-pink-100 text-gray-600 mr-1 mb-1 px-4 py-2 font-bold outline-none shadow shadow-sm rounded-md font-bold uppercase ease-linear transition-all duration-150 outline-none focus:outline-none hover:shadow-md hover:bg-gray-200 mx-1" style=""> <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" class="h-6 w-6" style=""> <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" style=""> </path></svg></button>
    
      
      </div>

    <div class="mx-auto container h-screen bg-gray-100/20" style=""> <div class="flex justify-center" style=""> <button value="9" class="bg-gray-100 text-gray-600 mr-1 mb-1 px-4 py-2 font-bold outline-none shadow shadow-sm rounded-md font-bold uppercase ease-linear transition-all duration-150 outline-none focus:outline-none hover:shadow-md hover:bg-gray-200 mx-1" style=""> <svg viewbox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" width="16px" class="h-6 w-6" style=""> <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" style=""> </path></svg></button></div></div></div>

svelte_bug

sandeep-gh avatar Jun 18 '22 00:06 sandeep-gh

@sandeep-gh In the code that you have shared, the first svg element does not contain a height or width values. The second one does contain width, that is the reason why the second one is rendering and the first one isn't. As Yimmie suggested above, you need to add a height or width to it.

sami-baadarani avatar Jun 27 '22 07:06 sami-baadarani

As @sami-baadarani said, the first svg element does not contain a height or width values. After adding width or height, they both work well. @sandeep-gh Screen Shot 2022-07-08 at 9 40 28 AM Screen Shot 2022-07-08 at 9 41 45 AM

magentaqin avatar Jul 08 '22 01:07 magentaqin

I came across something I believe is similar to this issue.

I'm doing something like this:

App.svelte

<script>
	import DynamicTag from "./DynamicTag.svelte";
</script>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:map="http:rsvg.org/dtd/map" viewBox="0 0 1024 1024">
	<DynamicTag element={"g"}><line stroke-width="3" stroke="#000" fill="#000" x1="100" x2="400" y1="100" y2="400" /></DynamicTag>
</svg>

DynamicTag.svelte

<script>
	export let element = "div";
</script>
<svelte:element this={element}><slot /></svelte:element>

The html generated and retrieved through the browser developer tools is flawless and will work if used statically, but renders nothing within my svelte app. The DynamicTag component will work for any non-svg tag, or if svelte:element isn't used (but that'd defeat the purpose obviously).

TLDR Not really having looked into this much, my guess would be that Svelte underneath creates the element using document.createElement and g, along with all other svg tags, only work when created with document.createElementNS.

nicksulkers avatar Jul 15 '22 09:07 nicksulkers

I got curious and looked into the hint that @nicksulkers left. It looks like there are two relevant functions in src/runtime/internal/dom.ts:

  • element -> calls document.createElement
  • svg_element calls document.createElementNS with the SVG namespace

If you use a static name, the compiler is able to figure out that you want to use svg_element & imports it. Or you can add <svelte:options namespace="svg" /> to force the import.

The generated code for DynamicTag.svelte imports element & uses it:

import {
  // ...
  element as element_1,
  // ...
} from "svelte/internal";



function create_dynamic_element(ctx) {
  // ...
  return {
    c() {
      svelte_element = element_1(/*element*/ ctx[0]);

I was thinking it could be hard for the compiler to figure out that an arbitrary name is supposed to use the svg namespace, but that maybe adding the namespace would help. When you do, the correct function is brought in, but the compiler takes a different path & in the output, the element name ends up being svelte:element (which you can even see in the inspector)

import {
  //...
  svg_element,
} from "svelte/internal";


function create_dynamic_element(ctx) {
  // ...
  return {
    c() {
      svelte_element = svg_element("svelte:element");

it seems like we'd want it to output this:

import {
  // ...
  svg_element,
  // ...
} from "svelte/internal";

function create_dynamic_element(ctx) {
  // ...
  return {
    c() {
      svelte_element = svg_element(/*svg_element*/ ctx[0]);

(but I don't know how to do that 🤗)

postscript: I don't think it's simply about the presence or absence of a height or width attribute, because adding either to the sag element in App.svelte doesn't fix the problem

dmcclory avatar Jul 16 '22 18:07 dmcclory

Seeing the same problem here using svelte:element and svelte:self to generate an SVG structure. Once I figured out it was a namespace problem, I was able to add <svelte:options namespace="svg /> and get it working, but I think the intent (based on some older issues I looked up) is that Svelte should be auto-detecting and applying the namespace as needed for these?

seantimm avatar Jul 18 '22 17:07 seantimm

This should be supported now as of 3.51.0.

Conduitry avatar Oct 10 '22 17:10 Conduitry

Closed by #7695.

geoffrich avatar Oct 10 '22 21:10 geoffrich

I am not able to get this to work. Below is setup:

package.json

{
  ...
  ...
  },
  "devDependencies": {
    "@sveltejs/vite-plugin-svelte": "^1.0.0-next.11",
    ...
    "tailwindcss": "^3.1.8",
    "vite": "^2.3.7"
  },
  "dependencies": {
    "svelte": "^3.51.0"
  }
}

The html begin generate:

<div class="flex justify-center" style=""> <div class="flex flex-col space-y-4" style=""> 
<button value="myval" class="" style="">Click me  
<svg viewbox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" class="h-6 w-6" style=""> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" style=""> </path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" style=""> </path></svg>
</button>
</div>
</div>

However, no svg is getting rendered. Not sure if I am missing something.

sandeep-gh avatar Oct 11 '22 13:10 sandeep-gh

I can see the SVG. https://svelte.dev/repl/c63e6c14acfa4b06a5af56b350261330?version=3.51.0

Maybe I can easily understand the situation if you create REPL.

baseballyama avatar Oct 11 '22 13:10 baseballyama

Here is a repl, which shows that the problem persists in v.3.55.0. https://svelte.dev/repl/5e1e989bbccb4d688b571006bc3c0d12?version=3.55.0

I would expect to see both the red and the green circle, since both look identical in html, but the green circle isn't rendered. Refreshing the parent element of the svg makes it show up. (https://stackoverflow.com/questions/36339444)

micha-lmxt avatar Jan 03 '23 08:01 micha-lmxt

What is the use case of this? If we support this, we need to add an additional runtime check and it has a performance overhead.

baseballyama avatar Jan 03 '23 11:01 baseballyama

I have an one use case for this. I am developing a web development framework in Python based on justpy (https://github.com/justpy-org/justpy/). I am using svelte as the frontend library. All the components of the webpage is described as a json and shipped over websocket to frontend where it is rendered using svelte. The use case for rendering svg comes in this regard.

sandeep-gh avatar Jan 05 '23 09:01 sandeep-gh

Why just using {#if tag === 'svg'} is not enough?

baseballyama avatar Jan 05 '23 10:01 baseballyama

I will try the tag=== 'svg' and get back.

sandeep-gh avatar Jan 09 '23 05:01 sandeep-gh

Let me summarize: the problem is that svg elements need to be created with a different namespace. The namespace can't be changed afterwards.

In general, the problem can occur on all svg tags (svg, path, circle, etc ). I say 'can', since svelte is smart enough to change the namespace, if the svelte:element is a visual child of an svg tag. Example:

<svg>
    <svelte:element this={toggle?"circle":"ellipse"} {...props}/>
</svg>

This works. If you extract just the svelte:element into a different component, it would fail.

<svelte:options namespace='svg'/> can be used to work around this problem.

micha-lmxt avatar Jan 20 '23 09:01 micha-lmxt

We have easy workaround that is using <svelte:options namespace='svg'/>. So I close this issue for now. If still someone has a issue, we can reopen this.

baseballyama avatar Feb 26 '23 07:02 baseballyama

@baseballyama workaround works for me now. Thank you.

sandeep-gh avatar Dec 11 '23 13:12 sandeep-gh