Issue
Chrome: v96 Firefox: v95
I'm trying to download an SVG image as a PNG image from the browser. This seems to be working fine for Chrome, but I'm downloading a blank image with firefox. Any idea why?
export function downloadSvgImage(svgElement: HTMLElement, name: string) {
const xml = new XMLSerializer().serializeToString(svgElement);
const svg64 = window.btoa(xml);
const b64Start = 'data:image/svg+xml;base64,';
const viewBox = svgElement.getAttribute('viewBox');
const dimensionArr = viewBox.split(' ');
const width = parseInt(dimensionArr[2]);
const height = parseInt(dimensionArr[3]);
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
const image = new Image();
image.onload = () => {
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
canvas.toBlob((blob: any) => {
const anchor = document.createElement('a');
anchor.download = `${name}.png`;
anchor.href = URL.createObjectURL(blob);
anchor.click();
URL.revokeObjectURL(blob);
}, 'image/png');
};
image.src = b64Start + svg64;
}
Solution
I updated the code that captures the dimension to the following, and it downloads the SVG image through Firefox as intended:
...
let dimensionX = svgElement.viewBox.baseVal.width;
let dimensionY = svgElement.viewBox.baseVal.height;
if (dimensionX == 0 || dimensionY == 0) {
dimensionX = svgElement.getBBox().width;
dimensionY = svgElement.getBBox().height;
}
const width = dimensionX;
const height = dimensionY;
...
function downloadSvgImage(svgElement, name) {
const xml = new XMLSerializer().serializeToString(svgElement);
const svg64 = window.btoa(xml);
const b64Start = "data:image/svg+xml;base64,";
let dimensionX = svgElement.viewBox.baseVal.width;
let dimensionY = svgElement.viewBox.baseVal.height;
if (dimensionX == 0 || dimensionY == 0) {
dimensionX = svgElement.getBBox().width;
dimensionY = svgElement.getBBox().height;
}
const width = svgElement.clientWidth * 0.5; // dimensionX;
const height = svgElement.clientHeight * 0.5; // dimensionY;
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const image = new Image();
image.onload = () => {
canvas.getContext("2d").drawImage(image, 0, 0, width, height);
const url = canvas.toDataURL("image/png", 1);
const anchor = document.createElement("a");
anchor.download = `${name}.png`;
anchor.href = url;
anchor.click();
setTimeout(() => URL.revokeObjectURL(url), 0);
};
image.src = b64Start + svg64;
}
window.onload = function(){
const svg = document.querySelector("#cartman");
svg.setAttribute("width", svg.clientWidth);
svg.setAttribute("height", svg.clientHeight);
console.log("Download starting in 3 seconds...");
setTimeout(() => downloadSvgImage(svg, "cartman-sp"), 3000);
}
<svg xmlns="http://www.w3.org/2000/svg" id="cartman" viewBox="0 0 104 97">
<path d="M14,85l3,9h72c0,0,5-9,4-10c-2-2-79,0-79,1" fill="#7C4E32"/>
<path d="M19,47c0,0-9,7-13,14c-5,6,3,7,3,7l1,14c0,0,10,8,23,8c14,0,26,1,28,0c2-1,9-2,9-4c1-1,27,1,27-9c0-10,7-20-11-29c-17-9-67-1-67-1" fill="#E30000"/>
<path d="M17,32c-3,48,80,43,71-3 l-35-15" fill="#FFE1C4"/>
<path d="M17,32c9-36,61-32,71-3c-20-9-40-9-71,3" fill="#8ED8F8"/>
<path d="M54,35a10 8 60 1 1 0,0.1zM37,38a10 8 -60 1 1 0,0.1z" fill="#FFF"/>
<path d="M41,6c1-1,4-3,8-3c3-0,9-1,14,3l-1,2h-2h-2c0,0-3,1-5,0c-2-1-1-1-1-1l-3,1l-2-1h-1c0,0-1,2-3,2c0,0-2-1-2-3M17,34l0-2c0,0,35-20,71-3v2c0,0-35-17-71,3M5,62c3-2,5-2,8,0c3,2,13,6,8,11c-2,2-6,0-8,0c-1,1-4,2-6,1c-4-3-6-8-2-12M99,59c0,0-9-2-11,4l-3,5c0,1-2,3,3,3c5,0,5,2,7,2c3,0,7-1,7-4c0-4-1-11-3-10" fill="#FFF200"/>
<path d="M56,78v1M55,69v1M55,87v1" stroke="#000" stroke-linecap="round"/>
<path d="M60,36a1 1 0 1 1 0-0.1M49,36a1 1 0 1 1 0-0.1M57,55a2 3 0 1 1 0-0.1M12,94c0,0,20-4,42,0c0,0,27-4,39,0z"/>
<path d="M50,59c0,0,4,3,10,0M56,66l2,12l-2,12M25,50c0,0,10,12,23,12c13,0,24,0,35-15" fill="none" stroke="#000" stroke-width="0.5"/>
</svg>
I hope it solves your issue.
NOTE: You will have to test this code inside your local environment as downloads from frames are intentionally blocked by the browser.
Update
So I realized that my initial "fix" wasn't actually working as it was still downloading a small empty canvas. I eventually discovered that Firefox has a long-standing bug with regards to rendering SVGs inside the canvas element unless the width
and height
attributes are specified on the <SVG>
root element with non-percentage based values. So I fixed this by manually setting them to their <SVG>
's client dimensions:
svg.setAttribute("width", svg.clientWidth);
svg.setAttribute("height", svg.clientHeight);
You can define the dimensions of the image/png
as a percentage of the <SVG>
's width & height
.
const width = svgElement.clientWidth * 0.5; // half the width of the original svg
const height = svgElement.clientHeight * 0.5; // half the height of the original svg
...
canvas.width = width;
canvas.height = height;
I also updated the code for the data URL generation to use the .toDataURL
method of the canvas element, and delayed the revokeObjectURL
until moments after the download initializes:
canvas.getContext("2d").drawImage(image, 0, 0, width, height);
const url = canvas.toDataURL("image/png", 1);
const anchor = document.createElement("a");
anchor.download = `${name}.png`;
anchor.href = url;
anchor.click();
setTimeout(() => URL.revokeObjectURL(url), 0);
I hope this update solves the issue officially this time around.
Answered By - Vektor
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.