mithril.js: svg tag frequently breaks on redraw in Chrome

Back already with another head-scratcher!

When using Chrome and dynamically rendering svgs with a <use> tag that references an ID in an external svg spritesheet document, some svgs elements that remain between redraws disappear.

Expected Behavior

Each svg should display as normal. The behavior is correct in Firefox but not in Chrome.

Current Behavior

Seemingly at random, svg elements that remain between redraws suddenly disappear. Upon page refresh (with state saved in history.state), the elements reappear, but subsequent redraws trigger the bug again.

Possible Solution

You might need to familiarize yourself with the provided code below in order to understand the following.

https://github.com/MithrilJS/mithril.js/issues/1297 likely provides a workaround, however because the svgs are not held in a separate document, they are not able to be cached.

Furthermore, I’m not certain that referencing a javascript object, say, 50 times in a page (ex. icons), is comparable in performance to referencing the same element by ID with use 50 times.

If these two problems could be addressed, that would make encoding the svgs as mithril components viable, however it is still more complicated to set up a build pipeline. Currently I have a script that watches a folder for changes and automatically creates a minified document containing each svg icon as a symbol. Reworking this to output a valid mithril component that displays the correct svg based on a provided argument, or a page with several individual components, is very convoluted and could also hurt my build times.

I have narrowed down the source of the problem a good bit. Providing a static string, instead of dynamically inserting the string, in the href attribute of the use tag still produces this bug. Some of the svgs render and some don’t, even though they reference the same ID.

Curiously, when I remove the map function and just render every item instead of filtering based on their value, each svg displays just fine even when clicking on a different result.

So it is not an issue with resolving IDs, and must be an issue with how dynamic rendering is handled when svg and/or use elements are involved. I’m not sure how this relates back to Chrome but I’m not surprised it’s browser-specific because the Chrome dev team loves to break shit. For all I know, this is purely a Chrome bug and mithril’s implementation is correct.

Steps to Reproduce (for bugs)

For the sake of clarity, I have greatly simplified the following components.

The relevant components include a container, a component containing the svg element, and the model Items that contains information about each item.

vnode.attrs.items is formatted as

{
  item1: true / false,
  item2: true / false,
  item3: true / false, 
  ...
}

and Items is formatted as

[
  item1: {
    name: <name>,
    icon: '/spritesheet.svg#sprite-<name>'
  },
  ...
]

The indexes of both vnode.attrs.items and Items are the same for each item, and thusly the container converts vnode.attrs.items to an array of keys and values with Object.entries and loops through each entry and if the value is true, retrieves the relevant information from Items.

Container:

function MyContainer() {
  return {
    view: ( vnode ) => (
      <div>
        {
          Object.entries( vnode.attrs.items )
          // Render the component if the value is true
            .map(( x, i ) => (
              x[1] ?
                <MyComponent
                  key={ i }
                  name={ Items.list[ i ].name }
                  icon={ Items.list[ i ].icon }
                />
                : undefined
            ))
        }
      </div>
    )
  }
}

Component:

function MyComponent() {
  return {
    view: ( vnode ) => (
      <div>
          <svg class={ 'icon' }>
            <use href={ vnode.attrs.icon }/>
          </svg>

          <span>
            { String( vnode.attrs.name )}
          </span>
      </div>
    )
  }
}

Context

I have a list of search results. I am pulling a list of attributes from a document that changes whenever a result is clicked. This document is pulled in a parent component and only the attributes are passed through (as items). These attributes have associated full names (item1 -> “Item 1”) and icons. The icons are stored as strings referencing the appropriate path and id of the svg symbol, and the appropriate svg is loaded from the external spritesheet on render.

Your Environment

  • Version used: 1.1.5
  • Browser Name and version: Chrome 61.0.3163.100 (64-bit)
  • Operating System and version (desktop or mobile): Fedora 26
  • Webpack + Babel + JSX

I know this bug is a bit complicated so I am happy to provide further context upon request.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 1
  • Comments: 22 (14 by maintainers)

Most upvoted comments

I have discovered a number of svg-related bugs with Chrome since I filed this issue. I’ve figured out workarounds for the most part but it’s definitely a Chrome bug.

@StephanHoyer many namespace-related bugs have been fixed in Mithril since v1.0 was released, so this may no longer be needed…

@soulofmischief if you want to keep on using a sprite, you could perhaps try to interact with the DOM element from the oncreate and onupdate hooks, like you’ve done manually.