Shadow DOM is still somewhat shady

I was recently building a web app using Polymer 2.0 and need to include some SVG assets for wider resolution support. As you may know, Polymer fully supports native shadow DOM with the 2.0 version. When using a shadow DOM context, the style tag will apply the given CSS rules inside the shadow context. This introduces some problems with an inline SVG image. Consider this example;

<svg width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1080 360">
    <defs>
        <style>.a{fill:none;}.b{opacity:0.3;}.c{fill:#ddb464;}.d{clip-path:url(#a);}
        </style>
        <clipPath id="a"><rect class="a" x="129" y="112" width="822" height="216" rx="40" ry="40"/>
        </clipPath>
    </defs>
    <rect class="b" x="143" y="129" width="820" height="216" rx="40" ry="40"/>
    ...
</svg>

The culprit here is the “style” tag inside the “defs”. It defines some classes for the SVG image but they will be interpreted as shadow DOM style and get applied to every a, b, b, d classes in the current shadow DOM. When working with autogenerated SVGs these simple class names are almost a convention (this is the output of Adobe Illustrator for example). The problem is arised when I have multiple SVGs like this. The last style overrides previous ones and colors get mangled not to mention the clipPath confusion.

At first I started to write a svg-wrapper Polymer element to isolate every SVG image inside separate shadow contexts, but then decided to use a quick approach. Just replace the class names with an id prefix. I have used this simple regex to find class names and replace them with a specific id;

\.[a-z](?!.*<style>)

Then fixed clipPath ids manually and added the appropriate class name to the SVG itself. The result is something like;

<svg id="specific-svg">
    <defs>
        <style>#specific-svg .a{fill:none;}#specific-svg .b{opacity:0.3;}#specific-svg .c{fill:#ddb464;}#specific-svg .d{clip-path:url(#specific-svg-a);}
        </style>
        <clipPath id="specific-svg-a"><rect class="a" x="129" y="112" width="822" height="216" rx="40" ry="40"/>
        </clipPath>
    </defs>
    <rect class="b" x="143" y="129" width="820" height="216" rx="40" ry="40"/>
    ...
</svg>

This temporarily solved my problem but it is rather cumbersome to apply this to each and every SVG I have. The proper solution indeed may be implementing the svg-wrapper component and use it to wrap each and every image you use. This would be still somewhat cumbersome but would be fool-proof and easier to use.

To sum up, when you decide to use the shadow DOM (or in my case Polymer) together with SVG assets, be prepared for such a problem. The cause of the problem is one of the very things that the shadow DOM is trying to resolve but apparently the standard is still in early development and this may be an edge case. Amusingly, the solution still seem to be shadow DOM itself. Let me know in the comments or send me an e-mail if you can find a better solution to this problem, maybe something that I’m unaware of. I’ll publish the custom element when I have the time to properly implement it.

Leave a Reply

Your email address will not be published. Required fields are marked *