Html2canvas: CSS not applied to SVGs

Created on 7 Dec 2016  路  6Comments  路  Source: niklasvh/html2canvas

See this demo for an illustrated example.

When inline SVGs are styled using CSS the properties are not rendered.

My example above shows how an SVG that includes a defined fill property as:

<path fill="red" ...

But is then overridden in the browser using the CSS:

path {
    fill: green;
}

will render in the browser as a green path but once converted to a canvas using html2canvas the property is lost and is instead rendered as red.

Most helpful comment

I got this working using computed-style-to-inline-style and one workaround https://github.com/lukehorvat/computed-style-to-inline-style/issues/2

computedToInline(element, true);

for (const attribute of ["width", "height"]) {
    for (const rect of element.querySelectorAll("svg ["+attribute+"]")) {
        rect.style[attribute] = "";
    }
}

html2canvas(element).then(function(canvas) {
   // ...
});

All 6 comments

It looks like this can be solved by explicitly applying svg styles to the SVG and each of its nodes using getComputedStyle. See this updated demo.

For my use case, all of my styles are defined in my stylesheets including the SVG dimensions. So iterating over each SVG in my document and applying:

svg.setAttribute('height', svg.clientHeight);
svg.setAttribute('width', svg.clientWidth);

and then over each node in SVG I can apply:

node.setAttribute('fill', style.getPropertyValue('fill'));
// stroke, stroke-width, and any other SVG properties will also be needed

I then remove all of the changes I made after the canvas is rendered so that media queries and hover states will be rendered as expected again.

It would be great if a solution, this one or a better one if it exists, was incorporated into html2canvas.

@G0dwin, I'm interested in trying this workaround, but in the link you provided to the updated demo, I don't see the problem being resolved:

  • In Chrome, I still see red instead of green, plus I see extra jpeg artifacts
  • In Firefox, I see a mostly-white jpeg
  • In Edge, I see black instead of green

The demo seems to link directly to the master branch of this repo, so maybe changes in html2canvas since your last post have affected the demo?

I got this working using computed-style-to-inline-style and one workaround https://github.com/lukehorvat/computed-style-to-inline-style/issues/2

computedToInline(element, true);

for (const attribute of ["width", "height"]) {
    for (const rect of element.querySelectorAll("svg ["+attribute+"]")) {
        rect.style[attribute] = "";
    }
}

html2canvas(element).then(function(canvas) {
   // ...
});

Was able to get this working with the same method as @tlrobinson but running it inside the onclone function of html2canvas so that it only affects the items that are going to be sent to the image.

const { containerId } = this.props;
const element = document.querySelector(`#${containerId}`);
const options = {
  ignoreElements: (element) => hasClass(element, 'mapboxgl-ctrl'),
  onclone: (clonedDoc) => {
    // After we clone the document, inline svg fill
    const container = clonedDoc.body.querySelector(`#${containerId}`);
    computedStyleToInlineStyle(container, {
      recursive: true,
      properties: ["fill"],
    });
  },
}
// For testing
html2canvas(element, options).then(canvas => {
  document.body.appendChild(canvas);
});

Thanks for the tip!

Here's a version of the fix that does not rely on external libraries:

function applyStyleToSvgTrees(container) {
  for (let svg of container.getElementsByTagName('svg')) {
    for (let el of svg.getElementsByTagName('*')) {
      s = getComputedStyle(el);
      for (let key of s) {
        let prop = key.replace(/\-([a-z])/g, v => v[1].toUpperCase());
        el.style[prop] = s[key];
      }
    }
  }
}


html2canvas(document.body, {
  onclone: (clonedDoc) => {
    applyStyleToSvgTrees(clonedDoc);
  },
}).then(canvas => {
  document.body.appendChild(canvas);
});

Try here with jsFiddle

@Jerther you saved my day, thanks a lot!
I confirm this works, here's a Typescript version if anyone needs it:

function applyStyleToSvg(container: Element | Document) {
    for (let svg of Array.from(container.getElementsByTagName('svg'))) {
        for (let element of Array.from(svg.getElementsByTagName('*')) as Array<SVGElement>) {
            const computedStyle = getComputedStyle(element);
            for (let property of Array.from(computedStyle)) {
                element.style.setProperty(property, computedStyle.getPropertyValue(property));
            }
        }
    }
}
Was this page helpful?
0 / 5 - 0 ratings