Related
I have an svg image on a page. A user then clicks a link and this is called to get the toDataUrl():
console.log('Processing svg for NOT Internet Explorer');
var svgText = $("#div-surrounding-svg-element").html();
var d3Canvas = document.getElementById("d3-canvas");
d3Canvas.width = 2000;
d3Canvas.height = 300;
var ctxt = d3Canvas.getContext("2d");
// make canvas background
ctxt.fillStyle = "#fff";
ctxt.fillRect(0, 0, d3Canvas.width, d3Canvas.height);
var svg = new Blob([svgText], { type: "image/svg+xml;charset=utf-8" });
var domURL = self.URL || self.webkitURL || self;
var url = domURL.createObjectURL(svg);
var img = new Image();
img.onload = function ()
{
ctxt.drawImage(this, 0, 0);
domURL.revokeObjectURL(url);
var dataUrl = d3Canvas.toDataURL();
// $('#download-chart-dataUrl').val(dataUrl);
// var downloadExcelForm = $('#download-chart-form');
// downloadExcelForm.submit();
// $('#download-chart-form').submit();
DownloadPNG(dataUrl);
};
img.src = url;
}
DownloadPNG does a POST to the server and C# does this:
var base64Data = Regex.Match(dataUrl, #"data:image/(?<type>.+?),(?<data>.+)").Groups["data"].Value;
var binData = Convert.FromBase64String(base64Data);
using (var stream = new MemoryStream(binData))
{
var tempFolder = ConfigurationManager.AppSettings["tempFilesDirectory"];
var bitmap = new Bitmap(stream);
var folder = Server.MapPath(tempFolder);
var imagePath = folder + $"/{Guid.NewGuid().ToString()}.png";
bitmap.Save(imagePath);
//return File(imagePath, "image/png", "Chart.png");
Response.Clear();
Response.AddHeader("Content-Disposition", "attachment;filename=Chart.png");
Response.WriteFile(imagePath);
Response.End();
}
I am getting a plain white png instead of the image I want. What am I missing? Thanks.
You don't need any server-side for download png)))Please see an example
P.S.
Using jquery for a dom manipulation is a "bad taste" in 2k18, please use dom api))
Update: I have added querySelector to the SVG child of SVG, and SVG serializing code.
downloadPng=function(){
var img = new Image();
img.onload = function (){
var canvas = document.createElement("canvas");
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
var ctxt = canvas.getContext("2d");
ctxt.fillStyle = "#fff";
ctxt.fillRect(0, 0, canvas.width, canvas.height);
ctxt.drawImage(img, 0, 0);
var a = document.createElement("a");
a.href = canvas.toDataURL("image/png");
a.download = "image.png"
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
var innerSvg = document.querySelector("#div-surrounding-svg-element svg svg");
var svgText = (new XMLSerializer()).serializeToString(innerSvg);
img.src = "data:image/svg+xml;utf8," + encodeURIComponent(svgText);
}
<div id="div-surrounding-svg-element">
<svg id="inner-svg" height="100" width="500" xmlns="http://www.w3.org/2000/svg">
<svg height="100" width="500" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="240" cy="50" rx="220" ry="30" style="fill:green" />
</svg>
<ellipse cx="240" cy="50" rx="220" ry="30" style="fill:yellow" />
</svg>
</div>
<button onclick="downloadPng()">download</button>
I need to draw a svg which use external resources css in a canvas but cannot (or should not) include the style in defs since the svg will be use a lot of times in the canvas.
When I convert the SVGElement to a HTMLImageElement I loss the reference to the style.
Is it possible to obtain the render of a SVGElement in the browser and then draw it in a canvas ?
If not is there an other method to resolve my issue ?
Here is the case that I am trying to resolve:
var svgToParse = '\
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30">\
<circle cx="15" cy="15" r="14" fill="red" />\
<text class="testCss" x="50%" y="65%" text-anchor="middle"></text>\
</svg>\
';
var domParser = new DOMParser();
var svgDoc = domParser.parseFromString(svgToParse, "image/svg+xml");
var container = document.getElementById('test_container_id');
var svg1 = svgDoc.documentElement.cloneNode(true);
// Result to obtain in canvas
container.appendChild(
svg1
);
// Canvas
var DOMURL = window.URL || window.webkitURL || window;
var img = new Image();
var svg2 = new Blob([svgToParse], {type: 'image/svg+xml'});
var url = DOMURL.createObjectURL(svg2);
img.onload = function () {
document.getElementById('canvas_id')
.getContext('2d').drawImage(img, 0, 0);
DOMURL.revokeObjectURL(url);
}
img.src = url;
body {
font-family: FontAwesome;
}
.testCss {
fill: white;
}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<div id="test_container_id">
<h3>
Needed in canvas:
</h3>
</div>
<h3>
Canvas:
</h3>
<canvas id="canvas_id" width="100" height="100">
</canvas>
I have an inline SVG in my html, and I need to be able to save this as either a JPEG, PNG or SVG.
I have tried a few different methods with converting the SVG to canvas and then converting to JPEG, but I haven't been able to get these working.
Here is an example of my inline SVG.
.font {
color: #ffffff;
font-family: Roboto;
font-weight: bold;
text-transform: uppercase;
}
.name {
font-size: 64pt;
}
.top-bar-text {
font-size: 32pt;
}
.font tspan {
dominant-baseline: middle;
}
<link href='http://fonts.googleapis.com/css?family=Roboto:700' rel='stylesheet' type='text/css'>
<svg width="256" height="256" id="icon">
<rect class="bg1" id="bg_color_1" x="0" y="0" width="256" height="256" fill="#4cbc5a" />
<path class="bg2" id="bg_color_2" d="M 0 96 L0,256 L256,256 L256,96 s -128 96 -256 0" fill="#08a21c" />
<text id="left_corner_text" x="24" y="36" width="48" height="64" class="top_bar lct font top-bar-text" text-anchor="middle" fill="#ffffff"><tspan>1</tspan></text>
<text id="right_corner_text" x="232" y="36" width="48" height="64" class="top_bar rct font top-bar-text" text-anchor="middle" fill="#ffffff"><tspan>2</tspan></text>
<text id="line_1_text" transform="scale(1,2)" x="128" y="90" width="256" height="192" class="l1t font name" text-anchor="middle" fill="#ffffff"><tspan>ABC</tspan></text>
</svg>
Also, not all the elements need to be exported, as some of the options the user has is to remove the top corner numbers.
I would like for when it's been converted to download straight to the browser.
Nowadays this is pretty simple.
The basic idea is:
SVG to canvas
canvas to dataUrl
trigger download from dataUrl
it actually works outside of the Stack Overflow snippet
var btn = document.querySelector('button');
var svg = document.querySelector('svg');
var canvas = document.querySelector('canvas');
function triggerDownload (imgURI) {
var evt = new MouseEvent('click', {
view: window,
bubbles: false,
cancelable: true
});
var a = document.createElement('a');
a.setAttribute('download', 'MY_COOL_IMAGE.png');
a.setAttribute('href', imgURI);
a.setAttribute('target', '_blank');
a.dispatchEvent(evt);
}
btn.addEventListener('click', function () {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var data = (new XMLSerializer()).serializeToString(svg);
var DOMURL = window.URL || window.webkitURL || window;
var img = new Image();
var svgBlob = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
var url = DOMURL.createObjectURL(svgBlob);
img.onload = function () {
ctx.drawImage(img, 0, 0);
DOMURL.revokeObjectURL(url);
var imgURI = canvas
.toDataURL('image/png')
.replace('image/png', 'image/octet-stream');
triggerDownload(imgURI);
};
img.src = url;
});
<button>svg to png</button>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="200" height="200">
<rect x="10" y="10" width="50" height="50" />
<text x="0" y="100">Look, i'm cool</text>
</svg>
<canvas id="canvas"></canvas>
Regarding the downloading part, you can set up a filename and etc etc (although not in this example). Some days ago I answered a question on how to download a specific portion of HTML from the given page. It might be useful regarding the downloading part: https://stackoverflow.com/a/28087280/2178180
update: now letting you specify the filename
Here's a solution that works in IE11 as well.
I just did a bunch of testing of various methods of this and while the above answer by Ciro Costa is fantastic in that it works in Firefox and Chrome it does not work in IE11. IE11 fails due to a security issue with rendering an svg to the canvas which requires a canvas implementation, canvg. Here's a solution using canvg that's pretty terse and works in the latest versions of Chrome, Firefox, Edge, and IE11.
Fiddle: https://jsfiddle.net/StefanValentin/9mudw0ts/
DOM
<svg
id="my-svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
width="200"
height="200"
>
<rect x="10" y="10" width="50" height="50" />
<text x="0" y="100">Look, i'm cool</text>
</svg>
JavaScript
var svg = document.querySelector('#my-svg');
var data = (new XMLSerializer()).serializeToString(svg);
// We can just create a canvas element inline so you don't even need one on the DOM. Cool!
var canvas = document.createElement('canvas');
canvg(canvas, data, {
renderCallback: function() {
canvas.toBlob(function(blob) {
download('MyImageName.png', blob);
});
}
});
The download function above could be whatever you want to do, as there are many ways to trigger a download via JavaScript. Here's the one we use that works in all the browsers I've tested.
// Initiate download of blob
function download(
filename, // string
blob // Blob
) {
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, filename);
} else {
const elem = window.document.createElement('a');
elem.href = window.URL.createObjectURL(blob);
elem.download = filename;
document.body.appendChild(elem);
elem.click();
document.body.removeChild(elem);
}
}
Working off #CiroCosta. 1 option if you are having trouble exporting an element you could just draw the image to the canvas before drawing the svg image
btn.addEventListener('click', function () {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var data = (new XMLSerializer()).serializeToString(svg);
var DOMURL = window.URL || window.webkitURL || window;
// get the raw image from the DOM
var rawImage = document.getElementById('yourimageID');
var img = new Image();
var svgBlob = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
var url = DOMURL.createObjectURL(svgBlob);
img.onload = function () {
ctx.drawImage(rawImage, 0, 0);
ctx.drawImage(img, 0, 0);
DOMURL.revokeObjectURL(url);
var imgURI = canvas
.toDataURL('image/png')
.replace('image/png', 'image/octet-stream');
triggerDownload(imgURI);
};
img.src = url;
});
Worked for me but only for png and jpeg. SVG files still only display inline elements and not tags
EDIT: The way you create an svg like this is actually by converting the image tag into Base64 and the setting that as the xlink:href in the image attributes like this:
<image id="crop" width="725" height="1764" xlink:href="data:image/png;base64,iVBORw0KGgo ... " />
and then triggering the download on the whole svg url like this:
btn.addEventListener('click', function () {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var data = (new XMLSerializer()).serializeToString(svg);
var DOMURL = window.URL || window.webkitURL || window;
var rawImage = document.getElementById('yourimageID');
var img = new Image();
var svgBlob = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
var url = DOMURL.createObjectURL(svgBlob);
img.onload = function () {
ctx.drawImage(img, 0, 0);
triggerDownload(url);
DOMURL.revokeObjectURL(url);
}
};
you can convert pngs like this here:
function getDataUri(url, callback) {
var image = new Image();
image.onload = function () {
var canvas = document.createElement('canvas');
canvas.width = this.naturalWidth; // or 'width' if you want a special/scaled size
canvas.height = this.naturalHeight; // or 'height' if you want a special/scaled size
canvas.getContext('2d').drawImage(this, 0, 0);
// Get raw image data
callback(canvas.toDataURL('image/png').replace(/^data:image\/(png|jpg);base64,/, ''));
// ... or get as Data URI
callback(canvas.toDataURL('image/png'));
};
image.src = url;
}
then setting the attribute
getDataUri('localImagepath', function (dataUri) {
image.setAttribute('xlink:href', dataUri);
});
The solution for saving inline SVG as SVG file
Works in modern browsers
<svg width="100" height="100">
<rect fill="red" x="0" y="0" width="100" height="100" />
</svg>
<button>Save to SVG</button>
let btn = document.querySelector('button')
let svg = document.querySelector('svg')
let triggerDownload = (imgURI, fileName) => {
let a = document.createElement('a')
a.setAttribute('download', 'image.svg')
a.setAttribute('href', imgURI)
a.setAttribute('target', '_blank')
a.click()
}
let save = () => {
let data = (new XMLSerializer()).serializeToString(svg)
let svgBlob = new Blob([data], {type: 'image/svg+xml;charset=utf-8'})
let url = URL.createObjectURL(svgBlob)
triggerDownload(url)
}
let btn = document.querySelector('button')
btn.addEventListener('click', save)
Codepen: https://codepen.io/udovichenko/pen/yLXaWLB
The answer by #ciro costa does help, but the generated png height is not working correctly unless the canvas height and width is set.
function downloadImg() {
const svgElem = document.querySelector('svg')
const serializer = new XMLSerializer();
let svgData = serializer.serializeToString(svgElem);
svgData = '<?xml version="1.0" standalone="no"?>\r\n' + svgData;
const svgBlob = new Blob([svgData], {
type: 'image/svg+xml;charset=utf-8',
});
let DOMURL = window.URL || window.webkitURL || window;
const url = DOMURL.createObjectURL(svgBlob);
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const domRect = svgElem.getBBox();
canvas.width = domRect.width;
canvas.height = domRect.height;
ctx.drawImage(img, 0, 0, domRect.width, domRect.height);
DOMURL.revokeObjectURL(url);
const imgURI = canvas
.toDataURL('image/png')
.replace('image/png', 'image/octet-stream');
download(imgURI);
};
img.onerror = (e) => {
console.error('Image not loaded', e);
};
img.src = url;
}
function download(href) {
let download = document.createElement('a');
download.href = href;
download.download = 'img.png';
download.click();
download.remove();
}
<svg width="256" height="256" id="icon">
<rect class="bg1" id="bg_color_1" x="0" y="0" width="256" height="256" fill="#4cbc5a" />
</svg>
<div>
<button onclick="downloadImg()">Download</button>
</div>
In general, the solutions presented here work, but don't forget to explicitly set the canvas size in pixels, otherwise the image may be cropped.
For example:
// get the size of the svg image
const { width, height } = svg.getBBox();
// create a canvas and set its size
var canvas = document.createElement(`canvas`);
canvas.setAttribute(`width`, width);
canvas.setAttribute(`height`, height);
Use this to example, but JavaScript section is simplify to you.
function SVGPNG(svg, cb) {
let temp = document.createElement("img");
let imageSrc = URL.createObjectURL(
new Blob([svg], { type: "image/svg+xml" })
);
temp.src = imageSrc;
temp.setAttribute("style", "position:fixed;left:-200vw;");
document.body.appendChild(temp);
temp.onload = function onload() {
let canvas = document.createElement("canvas");
canvas.width = temp.clientWidth;
canvas.height = temp.clientHeight;
let ctx = canvas.getContext("2d");
ctx.drawImage(temp, 0, 0);
let src = canvas.toDataURL("image/png");
cb(src, canvas);
temp.remove();
URL.revokeObjectURL(imageSrc);
};
}
function onPaste(e) {
SVGPNG(e.target.value, (src) => {
document.getElementById("output").value = src;
});
}
body {
font-family: system-ui;
background: #f06d06;
color: white;
text-align: center;
}
textarea {
border: solid 1px #ccc;
border-radius: 10px;
resize: none;
outlined: solid 1px #999;
}
<textarea cols="60" rows="20" autofocus onchange="onPaste">Paste your SVG code here...</textarea>
<textarea cols="60" rows="20" readonly id="output">Your output here...</textarea>
Keeping it simple, place an svg, a canvas and an empty img into the HTML. Set all to the same size. The javascript will use the svg to create a binary large object which is then rendered in the canvas as a png image. The function call creates a clone of the canvas and converts it into a jpeg.
function fjpg(){
clone = c.cloneNode(true);
ctx = clone.getContext('2d');
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, clone.width, clone.height);
ctx.drawImage(c, 0, 0);
document.all.jp1.src=clone.toDataURL("image/jpeg");
ctx = c.getContext('2d');
svgBlob = new Blob( [dataPNG], { type: 'image/svg+xml' } );
urlPNG = self.URL.createObjectURL( svgBlob );
img = new Image();
img.onload = function () {ctx.drawImage(img,0,0)}
img.src = urlPNG;
}
c = document.all.canvas0;
ctx = c.getContext('2d');
data = (new XMLSerializer()).serializeToString(document.all.svg0);
dataJPG = data.replace('>SVG<','>JPG<');
dataPNG = data.replace('>SVG<','>PNG<');
svgBlob = new Blob( [dataJPG], { type: 'image/svg+xml' } );
urlJPG = self.URL.createObjectURL( svgBlob );
img = new Image();
img.onload = function () {
ctx.drawImage( img, 0, 0 );
fjpg();
}
img.src = urlJPG;
<svg id='svg0' height=180 width=180><rect width=100% height=100% fill=red /><circle cx=90 cy=90 r=80 fill=green /><text x=90 y=105 font-size=60 text-anchor=middle fill=yellow>SVG</text></svg>
<canvas id="canvas0" height=180 width=180></canvas>
<img src='' id='jp1'>
I've two svg elements on which I've applied a mouseover/mouseout event. The goal is to increase the radius of the mask on mouseover to a size specified by a variable (maxMaskRadius) and decrease it on mouseout back to the initial state (initialMaskRadius).
I works perfectly with just one element. But when I've two elements and hover from one element to another, the animation from the previous elements aborts immediately. But I'd like to have it animate back to its initial state. With my current code that's unfortunately not possible.
Any suggestions on how to do that proper?
DEMO
CSS:
.dday.highlight .overlay {
fill: rgba(247,99,62,0.8);
}
.dday.normal {
width: 288px;
height: 288px;
}
HTML:
<svg class="dday highlight normal" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-image="car.jpg">
<image height="196" width="250" />
<a class="overlay" xlink:href="/svg/index.html" target="_top">
<rect x="0" y="0" width="288" height="288" style="mask: url(#mask1)" onmouseover="initAnimation(evt)" onmouseout="initAnimation(evt)" />
</a>
<mask id="mask1">
<rect x="0" y="0" width="288" height="288" fill="#fff" />
<circle cx="125" cy="125" r="25" />
</mask>
</svg>
<svg class="dday highlight normal" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-image="nokia.jpg">
<image height="196" width="250" />
<a class="overlay" xlink:href="/svg/index.html" target="_top">
<rect x="0" y="0" width="288" height="288" style="mask: url(#mask2)" onmouseover="initAnimation(evt)" onmouseout="initAnimation(evt)" />
</a>
<mask id="mask2">
<rect x="0" y="0" width="288" height="288" fill="#fff" />
<circle cx="125" cy="125" r="25" />
</mask>
</svg>
JS:
var maxImageWidth = 250,
maxImageHeight = 196,
ease = 50,
speed = 12,
maxMaskRadius = 100,
svg = null,
svgWidth = null,
svgHeight = null,
mask = null,
maskRadius = null,
initialMaskRadius = null,
imageObj = [],
imageSrcs = [],
imageWidth = null,
imageHeight = null,
mouseEvent = null;
init();
function init(el, index) {
$('.dday').each(function(index){
defineCurrentElement(this, index);
positionMask();
});
}
function defineCurrentElement(el, index) {
// Redefine the current Element
svg = $(el).closest('.dday'),
svgWidth = svg.width(),
svgHeight = svg.height(),
mask = svg.find('circle')[0];
// On page load there is a index provided to load the images for each element
if(typeof index !== 'undefined'){
loadImage(index);
}
}
function loadImage(index) {
// Load images and scale them to fit the predefined area
imageSrcs[index] = svg.data('image');
imageObj[index] = new Image(),
imageObj[index].image = $('image')[index];
imageObj[index].onload = function(){
scale_width = maxImageWidth / this.width;
scale_height = maxImageHeight / this.height;
scale = Math.min(scale_width, scale_height);
imageWidth = this.width * scale;
imageHeight = this.height * scale;
var xCoordinate = (svgWidth - imageWidth) / 2,
yCoordinate = (svgHeight - imageHeight) / 2;
this.image.setAttributeNS('http://www.w3.org/1999/xlink','href', imageSrcs[index]);
this.image.setAttributeNS(null,'width', imageWidth);
this.image.setAttributeNS(null,'height', imageHeight);
this.image.setAttributeNS(null,'x', xCoordinate);
this.image.setAttributeNS(null,'y', yCoordinate);
};
imageObj[index].src = imageSrcs[index];
}
function initAnimation(ev) {
// Triggered on mouseover/-out
// Change current element and init animation
defineCurrentElement(ev.target);
mouseEvent = ev.type;
requestAnimationFrame(animate);
}
function animate() {
if(mouseEvent == 'mouseover') {
// Increase mask radius on mouseover and repeat until target state is reached
maskRadius += Math.round(Math.max(((maxMaskRadius-maskRadius)/ease) * speed, 0.5));
if(maskRadius >= maxMaskRadius) {
// Target radius has been reached
maskRadius = maxMaskRadius;
} else {
// Target radius hasn't been reached yet -> repeat animation
mask.setAttributeNS(null,'r', maskRadius);
requestAnimationFrame(animate);
}
} else {
// Decrease mask radius on mouseover and repeat until initial state is reached
maskRadius -= Math.max(((maskRadius-initialMaskRadius)/ease) * speed, 0.5);
if(maskRadius <= initialMaskRadius) {
// Target radius has been reached
maskRadius = initialMaskRadius;
} else {
// Target radius hasn't been reached yet -> repeat animation
mask.setAttributeNS(null,'r', maskRadius);
requestAnimationFrame(animate);
}
}
}
function positionMask() {
// Center mask inside element
maskRadius = initialMaskRadius = parseInt(mask.getAttributeNS(null, 'r'), 10);
var maskWidth = maskRadius * 2,
xCoordinate = (svgWidth - maskWidth) / 2 + maskRadius,
yCoordinate = (svgHeight - maskWidth) / 2 + maskRadius;
mask.setAttributeNS(null,'cx', xCoordinate);
mask.setAttributeNS(null,'cy', yCoordinate);
}
Okke, I fixed all your code, and that was not an easy task to work with your code. Please declare all variables which you use, and not use in function global variables as your own private variables, because you can re-write existing global variable.
Now about fixed code:
CSS: no changes.
HTML: removed inline handlers (onmouseover and onmouseout)
Javascript:
When document is ready each svg element with class dday is initializing: downloading image for that svg if variable index exists (loading logic didn't changed almost), then centering mask, declaring function for animation, then adding handler for rect element in a element in initializing svg element.
All settings has been exported to variable settings
All private variables for svg is stored in {svgElement}.svgData object.
Demo: jsFiddle
P.S. By the way, this code is also not good enougt, need more time for clean code, but this code works.
HTML:
<svg class="dday sector-sports normal" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-image="http://img1.wikia.nocookie.net/__cb20130511205806/epicrapbattlesofhistory/images/9/94/Vaderrotj.jpg">
<image height="196" width="250" />
<a class="overlay" xlink:href="/svg/index.html" target="_top">
<rect x="0" y="0" width="288" height="288" style="mask: url(#mask1)" />
</a>
<mask id="mask1">
<rect x="0" y="0" width="288" height="288" fill="#fff" />
<circle cx="125" cy="125" r="25" />
</mask>
</svg>
<svg class="dday sector-sports normal" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-image="http://static.comicvine.com/uploads/original/11111/111116692/3213841-7948839370-yoda..jpg">
<image height="196" width="250" />
<a class="overlay" xlink:href="/svg/index.html" target="_top">
<rect x="0" y="0" width="288" height="288" style="mask: url(#mask2)" />
</a>
<mask id="mask2">
<rect x="0" y="0" width="288" height="288" fill="#fff" />
<circle cx="125" cy="125" r="25" />
</mask>
</svg>
Javascript: (used jQuery 1.11 library)
$(document).ready(function () {
var settings = {
imageWidthMax: 250,
imageHeightMax: 196,
ease: 50,
speed: 12,
maskRadiusMax: 100
};
var maskElements = [];
$('svg.dday').each(function (index) {
if (maskElements.indexOf(this) < 0) {
maskElements.push(this);
var sd = {};
this.svgData = sd;
sd.svg = $(this);
sd.svgWidth = sd.svg.width();
sd.svgHeight = sd.svg.height();
sd.mask = sd.svg.find('circle')[0];
// On page load there is a index provided to load the images for each element
if (typeof index !== 'undefined') {
var img = new Image();
img.image = $('image')[index];
img.onload = function () {
var m_scale_width = settings.imageWidthMax / this.width;
var m_scale_height = settings.imageHeightMax / this.height;
var m_scale = Math.min(m_scale_width, m_scale_height);
sd.imgWidth = this.width * m_scale;
sd.imgHeight = this.height * m_scale;
var m_x = (sd.svgWidth - sd.imgWidth) / 2;
var m_y = (sd.svgHeight - sd.imgHeight) / 2;
this.image.setAttributeNS('http://www.w3.org/1999/xlink', 'href', sd.svg.data('image'));
this.image.setAttributeNS(null, 'width', sd.imgWidth);
this.image.setAttributeNS(null, 'height', sd.imgHeight);
this.image.setAttributeNS(null, 'x', m_x);
this.image.setAttributeNS(null, 'y', m_y);
};
img.src = sd.svg.data('image');
}
//Center mask inside element
sd.maskRadiusInit = parseInt(sd.mask.getAttributeNS(null, 'r'), 10);
sd.maskRadius = sd.maskRadiusInit;
sd.maskWidth = sd.maskRadius * 2;
sd.maskX = (sd.svgWidth - sd.maskWidth) / 2 + sd.maskRadius;
sd.maskY = (sd.svgHeight - sd.maskWidth) / 2 + sd.maskRadius;
sd.mask.setAttributeNS(null, 'cx', sd.maskX);
sd.mask.setAttributeNS(null, 'cy', sd.maskY);
var animate = function () {
var m_addToRadius = Math.round(Math.max(((settings.maskRadiusMax - sd.maskRadius) / settings.ease) * settings.speed, 0.5));
if (sd.eventType === 'mouseover') {
sd.maskRadius += m_addToRadius;
if (sd.maskRadius > settings.maskRadiusMax) {
sd.maskRadius = settings.maskRadiusMax;
sd.mask.setAttributeNS(null, 'r', sd.maskRadius);
} else {
sd.mask.setAttributeNS(null, 'r', sd.maskRadius);
requestAnimationFrame(animate);
}
} else {
sd.maskRadius -= Math.round(Math.max(m_addToRadius, 0.5));
if (sd.maskRadius <= sd.maskRadiusInit) {
sd.maskRadius = sd.maskRadiusInit;
sd.mask.setAttributeNS(null, 'r', sd.maskRadius);
} else {
sd.mask.setAttributeNS(null, 'r', sd.maskRadius);
requestAnimationFrame(animate);
}
}
};
$('a>rect', this).on('mouseover mouseleave', function (evt) {
sd.eventType = evt.type;
requestAnimationFrame(animate);
});
}
});
});
Demo: jsFiddle
Below is an example of a Javascript animate object, AnimateJS It works cross-browser.
The example shows a quadratic hover effect for either an svg element or elements contained ing a <g>. Give it a try with your app.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Example: Hover Over Element - Quadratic</title>
<script type="text/javascript" src="../bowser.js"></script>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body style='padding:10px;font-family:arial'>
<center>
<h4>Example: Hover Over Element - Quadratic</h4>
<div style='width:90%;background-color:gainsboro;text-align:justify;padding:10px;border-radius:6px;'>
When the cursor moves over the element its size is increased. This works on both transfomed and non-transformed elements, contained in a <g>, or as individual elements. Uses <b>getBBox</b> to determine scale reference point.
The previous hover size increase is reduced to its original size.
</div>
<table><tr>
<td>
<table>
<tr><td colspan=2><b>Animation Settings:</b></td></tr>
<tr><td>1. Smoothness</td><td>100 frames per second</td></tr>
<tr><td>2. Duration</td><td>200 - runtime in ms</td></tr>
<tr><td>3. Range</td><td> increase scale .5</td></tr>
<tr><td>4. Output Equation</td><td><span style=color:blue>function</span> quad(p){return Math.pow(p, 2)}</td></tr>
<tr><td>6. Application Output </td><td>element transform</td></tr>
</table><br />
<i>There are 2 hover functions: <b>hoverOverG(evt)</b> for <g> elements,<br />and <b>hoverOverE(evt)</b> for individual elements.</i>
</td>
<td>
<div id="svgDiv" style='background-color:lightgreen;'>
<svg version="1.1" id="mySVG" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400" overflow="hidden" >
<g id="CircleStar1" onmouseover="hoverOverG(evt)" transform="translate(100 100)" ><polygon fill="crimson" stroke="none" points="15,-1.37091e-006 14.2658,-4.63527 12.1353,-8.81679 8.81679,-12.1353 4.63524,-14.2659 -1.37091e-006,-15 -4.63527,-14.2659 -8.81679,-12.1353 -12.1353,-8.81679 -14.2659,-4.63527 -15,-1.37091e-006 -14.2659,4.63524 -12.1353,8.81679 -8.81679,12.1353 -4.63527,14.2658 -1.37091e-006,15 4.63524,14.2658 8.81679,12.1353 12.1353,8.81679 14.2658,4.63524" /><polygon fill="dodgerblue" stroke="none" points="6.2319,3.59799 14.392,-1.37091e-006 6.2319,-3.59799 7.19598,-12.4638 -1.37091e-006,-7.19598 -7.19598,-12.4638 -6.2319,-3.59799 -14.392,-1.37091e-006 -6.2319,3.59799 -7.19598,12.4638 -1.37091e-006,7.19598 7.19598,12.4638" /></g>
<g id="CircleStar2" onmouseover="hoverOverG(evt)" transform="translate(200 200)" ><polygon fill="crimson" stroke="none" points="15,-1.37091e-006 14.2658,-4.63527 12.1353,-8.81679 8.81679,-12.1353 4.63524,-14.2659 -1.37091e-006,-15 -4.63527,-14.2659 -8.81679,-12.1353 -12.1353,-8.81679 -14.2659,-4.63527 -15,-1.37091e-006 -14.2659,4.63524 -12.1353,8.81679 -8.81679,12.1353 -4.63527,14.2658 -1.37091e-006,15 4.63524,14.2658 8.81679,12.1353 12.1353,8.81679 14.2658,4.63524" /><polygon fill="dodgerblue" stroke="none" points="6.2319,3.59799 14.392,-1.37091e-006 6.2319,-3.59799 7.19598,-12.4638 -1.37091e-006,-7.19598 -7.19598,-12.4638 -6.2319,-3.59799 -14.392,-1.37091e-006 -6.2319,3.59799 -7.19598,12.4638 -1.37091e-006,7.19598 7.19598,12.4638" /></g>
<g id="CircleStar3" onmouseover="hoverOverG(evt)" transform="translate(300 300)" ><polygon fill="crimson" stroke="none" points="15,-1.37091e-006 14.2658,-4.63527 12.1353,-8.81679 8.81679,-12.1353 4.63524,-14.2659 -1.37091e-006,-15 -4.63527,-14.2659 -8.81679,-12.1353 -12.1353,-8.81679 -14.2659,-4.63527 -15,-1.37091e-006 -14.2659,4.63524 -12.1353,8.81679 -8.81679,12.1353 -4.63527,14.2658 -1.37091e-006,15 4.63524,14.2658 8.81679,12.1353 12.1353,8.81679 14.2658,4.63524" /><polygon fill="dodgerblue" stroke="none" points="6.2319,3.59799 14.392,-1.37091e-006 6.2319,-3.59799 7.19598,-12.4638 -1.37091e-006,-7.19598 -7.19598,-12.4638 -6.2319,-3.59799 -14.392,-1.37091e-006 -6.2319,3.59799 -7.19598,12.4638 -1.37091e-006,7.19598 7.19598,12.4638" /></g>
<g id=rectEllipse transform="translate(330 20)scale(.5)" onmouseover="hoverOverG(evt)">
<rect x=50 y=200 width=60 height=50 fill=orange />
<ellipse cx=80 cy=227 rx=25 ry=15 fill=blue />
</g>
<g id=rectEllipseTransform transform="translate(130 120)scale(.5)" onmouseover="hoverOverG(evt)">
<rect x=50 y=200 width=60 height=50 fill=orange />
<ellipse cx=80 cy=227 rx=25 ry=15 fill=blue />
</g>
<g id=hoverElements >
<circle onmouseover="hoverOverE(evt)" cx=250 cy=150 r=10 fill=blue />
<circle onmouseover="hoverOverE(evt)" cx=150 cy=150 r=10 fill=blue />
<circle onmouseover="hoverOverE(evt)" cx=350 cy=350 r=10 fill=blue />
<circle transform="translate(110 40)" onmouseover="hoverOverE(evt)" cx=150 cy=150 r=10 fill=maroon />
<circle transform="translate(220 80)" onmouseover="hoverOverE(evt)" cx=150 cy=150 r=10 fill=maroon />
<circle transform="translate(220 80)" onmouseover="hoverOverE(evt)" cx=-10 cy=-10 r=10 fill=red />
<circle transform="translate(80 320)scale(.8)" onmouseover="hoverOverE(evt)" cx=-10 cy=-10 r=10 fill=red />
</g>
</svg>
</div>
</td>
</tr> </table>
<br />SVG Source:<br />
<textarea id=svgSourceValue style='font-size:110%;font-family:lucida console;width:90%;height:200px'></textarea>
<br />Javascript:<br />
<textarea id=jsValue style='border-radius:26px;font-size:110%;font-weight:bold;color:midnightblue;padding:16px;background-color:beige;border-width:0px;font-size:100%;font-family:lucida console;width:90%;height:400px'></textarea>
</center>
<div id='browserDiv' style='padding:5px;position:absolute;top:5px;left:5px;background-color:gainsboro;'>OK in:IE11/CH32/FF23<br /></div>
<script id=myScript>
/*---generalized animate core function
Allows progress/output to follow a specific/customized equation(delta)
Inspired by: Ilya Kantor - http://javascript.info/tutorial/animation
*/
var AnimateJS=function(options){
this.options=options
var start = new Date
var iT = setInterval(
function(){
var timePassed = new Date - start
var progress = timePassed / options.duration
if (progress > 1) progress = 1
this.progress=progress
var delta = options.delta(progress)
options.output(delta)
if (progress == 1)clearInterval(iT);
},options.delay)
}
/*
provide options:
1) range(end value)
2) frames per second(delay = 1000/frames per second)
3) duration in ms
4) delta: equation(linear,etc.)
5) output: This application's output function
*/
var HoverSizeIncrease=.5 //---the element's size increased by 50%--
var FinishedOver=true
var StartTrans=null
var PrevTarget=null
//--onmouseover g symbol---
function hoverOverG(evt)
{
if(FinishedOver==true && (evt.target.parentNode!=PrevTarget)) //--allows initial run---
{
if(PrevTarget)
extractHover(PrevTarget)
var target=evt.target.parentNode
PrevTarget=target
FinishedOver=false
var scaleBegin=1
var range=HoverSizeIncrease //---scale increase
var FPS=100 //---frames per second---
var duration=200 //---ms,.2 seconds---
//---quadratic formula in nth degree---
var delta=function quad(p){return Math.pow(p,2)}
if(target.getAttribute("transform"))
{
StartTrans=target.getAttribute("transform")
var myTrans=StartTrans
}
else
{
StartTrans=null
var myTrans=""
}
var bb=target.getBBox()
var bbx=bb.x
var bby=bb.y
var bbw=bb.width
var bbh=bb.height
var cx=bbx+.5*bbw
var cy=bby+.5*bbh
//----core animation function---
new AnimateJS(
{
delay: 1000/FPS,
duration: duration,
delta: delta, //---quadratic---
output: function(delta)
{
var scale=scaleBegin+delta*range
target.setAttribute("transform",myTrans+"translate("+(cx)+" "+(cy)+")scale("+scale+")translate("+(-cx)+" "+(-cy)+")")
//---finished---
if(progress==1)
onFinish()
}
})
}
}
//--onmouseover element---
function hoverOverE(evt)
{
if(FinishedOver==true && (evt.target!=PrevTarget)) //--allows initial run---
{
if(PrevTarget)
extractHover(PrevTarget)
var target=evt.target
PrevTarget=target
FinishedOver=false
var scaleBegin=1
var range=HoverSizeIncrease //---scale increase
var FPS=100 //---frames per second---
var duration=200 //---ms,.2 seconds---
//---quadratic formula in nth degree---
var delta=function quad(p){return Math.pow(p,2)}
if(target.getAttribute("transform"))
{
StartTrans=target.getAttribute("transform")
var myTrans=StartTrans
}
else
{
StartTrans=null
var myTrans=""
}
var bb=target.getBBox()
var bbx=bb.x
var bby=bb.y
var bbw=bb.width
var bbh=bb.height
var cx=bbx+.5*bbw
var cy=bby+.5*bbh
//----core animation function---
new AnimateJS(
{
delay: 1000/FPS,
duration: duration,
delta: delta, //---quadratic---
output: function(delta)
{
var scale=scaleBegin+delta*range
target.setAttribute("transform",myTrans+"translate("+(cx)+" "+(cy)+")scale("+scale+")translate("+(-cx)+" "+(-cy)+")")
//---finished---
if(progress==1)
onFinish()
}
})
}
}
var FinishedExtract=true
var ExtractTarget
var ExtractTrans
function extractHover(PrevTarget)
{
if(FinishedExtract==true) //--allows initial run---
{
ExtractTarget=PrevTarget
if(StartTrans)
ExtractTrans=StartTrans
else
ExtractTrans=""
FinishedExtract=false
var scaleBegin=1+HoverSizeIncrease
var range=HoverSizeIncrease //---scale decrease
var FPS=100 //---frames per second---
var duration=200 //---ms,.2 seconds---
//---quadratic formula in nth degree---
var delta=function quad(p){return Math.pow(p,2)}
var bb=ExtractTarget.getBBox()
var bbx=bb.x
var bby=bb.y
var bbw=bb.width
var bbh=bb.height
var cx=bbx+.5*bbw
var cy=bby+.5*bbh
//----core animation function---
new AnimateJS(
{
delay: 1000/FPS,
duration: duration,
delta: delta, //---quadratic---
output: function(delta)
{
var scale=scaleBegin-delta*range
ExtractTarget.setAttribute("transform",ExtractTrans+"translate("+(cx)+" "+(cy)+")scale("+scale+")translate("+(-cx)+" "+(-cy)+")")
if (progress == 1) // --- finished---
extractFinish();
}
})
}
}
//---this example animation: loop finished---
function onFinish()
{
FinishedOver=true
}
//---this example animation: loop finished---
function extractFinish()
{
FinishedExtract=true
if(ExtractTrans!="")
ExtractTarget.setAttribute("transform",ExtractTrans)
else
ExtractTarget.removeAttribute("transform")
}
</script>
<script>
document.addEventListener("onload",init(),false)
function init()
{
jsValue.value=myScript.text
svgSourceValue.value=svgDiv.innerHTML
}
</script>
</body>
</html>
Try to do this using very simple way. I've not applied any animation. I think you'll figure out how to do that.
Remove mouse event from inline
JS
$('svg')
.mouseenter(function(ev) {
console.log($(this).find('circle').attr('r',40));
})
.mouseleave(function(ev) {
console.log($(this).find('circle').attr('r',25));
});
CHECK JSFiddle
I want to convert SVG into bitmap images (like JPEG, PNG, etc.) through JavaScript.
Here is how you can do it through JavaScript:
Use the canvg JavaScript library to render the SVG image using Canvas: https://github.com/gabelerner/canvg
Capture a data URI encoded as a JPG (or PNG) from the Canvas, according to these instructions: Capture HTML Canvas as gif/jpg/png/pdf?
jbeard4 solution worked beautifully.
I'm using Raphael SketchPad to create an SVG. Link to the files in step 1.
For a Save button (id of svg is "editor", id of canvas is "canvas"):
$("#editor_save").click(function() {
// the canvg call that takes the svg xml and converts it to a canvas
canvg('canvas', $("#editor").html());
// the canvas calls to output a png
var canvas = document.getElementById("canvas");
var img = canvas.toDataURL("image/png");
// do what you want with the base64, write to screen, post to server, etc...
});
This seems to work in most browsers:
function copyStylesInline(destinationNode, sourceNode) {
var containerElements = ["svg","g"];
for (var cd = 0; cd < destinationNode.childNodes.length; cd++) {
var child = destinationNode.childNodes[cd];
if (containerElements.indexOf(child.tagName) != -1) {
copyStylesInline(child, sourceNode.childNodes[cd]);
continue;
}
var style = sourceNode.childNodes[cd].currentStyle || window.getComputedStyle(sourceNode.childNodes[cd]);
if (style == "undefined" || style == null) continue;
for (var st = 0; st < style.length; st++){
child.style.setProperty(style[st], style.getPropertyValue(style[st]));
}
}
}
function triggerDownload (imgURI, fileName) {
var evt = new MouseEvent("click", {
view: window,
bubbles: false,
cancelable: true
});
var a = document.createElement("a");
a.setAttribute("download", fileName);
a.setAttribute("href", imgURI);
a.setAttribute("target", '_blank');
a.dispatchEvent(evt);
}
function downloadSvg(svg, fileName) {
var copy = svg.cloneNode(true);
copyStylesInline(copy, svg);
var canvas = document.createElement("canvas");
var bbox = svg.getBBox();
canvas.width = bbox.width;
canvas.height = bbox.height;
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, bbox.width, bbox.height);
var data = (new XMLSerializer()).serializeToString(copy);
var DOMURL = window.URL || window.webkitURL || window;
var img = new Image();
var svgBlob = new Blob([data], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(svgBlob);
img.onload = function () {
ctx.drawImage(img, 0, 0);
DOMURL.revokeObjectURL(url);
if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob)
{
var blob = canvas.msToBlob();
navigator.msSaveOrOpenBlob(blob, fileName);
}
else {
var imgURI = canvas
.toDataURL("image/png")
.replace("image/png", "image/octet-stream");
triggerDownload(imgURI, fileName);
}
document.removeChild(canvas);
};
img.src = url;
}
The solution to convert SVG to blob URL and blob URL to png image
const svg=`<svg version="1.1" baseProfile="full" width="300" height="200"
xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="red" />
<circle cx="150" cy="100" r="80" fill="green" />
<text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text></svg>`
svgToPng(svg,(imgData)=>{
const pngImage = document.createElement('img');
document.body.appendChild(pngImage);
pngImage.src=imgData;
});
function svgToPng(svg, callback) {
const url = getSvgUrl(svg);
svgUrlToPng(url, (imgData) => {
callback(imgData);
URL.revokeObjectURL(url);
});
}
function getSvgUrl(svg) {
return URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' }));
}
function svgUrlToPng(svgUrl, callback) {
const svgImage = document.createElement('img');
// imgPreview.style.position = 'absolute';
// imgPreview.style.top = '-9999px';
document.body.appendChild(svgImage);
svgImage.onload = function () {
const canvas = document.createElement('canvas');
canvas.width = svgImage.clientWidth;
canvas.height = svgImage.clientHeight;
const canvasCtx = canvas.getContext('2d');
canvasCtx.drawImage(svgImage, 0, 0);
const imgData = canvas.toDataURL('image/png');
callback(imgData);
// document.body.removeChild(imgPreview);
};
svgImage.src = svgUrl;
}
change svg to match your element
function svg2img(){
var svg = document.querySelector('svg');
var xml = new XMLSerializer().serializeToString(svg);
var svg64 = btoa(xml); //for utf8: btoa(unescape(encodeURIComponent(xml)))
var b64start = 'data:image/svg+xml;base64,';
var image64 = b64start + svg64;
return image64;
};svg2img()
My use case was to have the svg data loaded from a network and this ES6 Class did the Job.
class SvgToPngConverter {
constructor() {
this._init = this._init.bind(this);
this._cleanUp = this._cleanUp.bind(this);
this.convertFromInput = this.convertFromInput.bind(this);
}
_init() {
this.canvas = document.createElement("canvas");
this.imgPreview = document.createElement("img");
this.imgPreview.style = "position: absolute; top: -9999px";
document.body.appendChild(this.imgPreview);
this.canvasCtx = this.canvas.getContext("2d");
}
_cleanUp() {
document.body.removeChild(this.imgPreview);
}
convertFromInput(input, callback) {
this._init();
let _this = this;
this.imgPreview.onload = function() {
const img = new Image();
_this.canvas.width = _this.imgPreview.clientWidth;
_this.canvas.height = _this.imgPreview.clientHeight;
img.crossOrigin = "anonymous";
img.src = _this.imgPreview.src;
img.onload = function() {
_this.canvasCtx.drawImage(img, 0, 0);
let imgData = _this.canvas.toDataURL("image/png");
if(typeof callback == "function"){
callback(imgData)
}
_this._cleanUp();
};
};
this.imgPreview.src = input;
}
}
Here is how you use it
let input = "https://restcountries.eu/data/afg.svg"
new SvgToPngConverter().convertFromInput(input, function(imgData){
// You now have your png data in base64 (imgData).
// Do what ever you wish with it here.
});
If you want a vanilla JavaScript version, you could head over to Babel website and transpile the code there.
Here a function that works without libraries and returns a Promise:
/**
* converts a base64 encoded data url SVG image to a PNG image
* #param originalBase64 data url of svg image
* #param width target width in pixel of PNG image
* #return {Promise<String>} resolves to png data url of the image
*/
function base64SvgToBase64Png (originalBase64, width) {
return new Promise(resolve => {
let img = document.createElement('img');
img.onload = function () {
document.body.appendChild(img);
let canvas = document.createElement("canvas");
let ratio = (img.clientWidth / img.clientHeight) || 1;
document.body.removeChild(img);
canvas.width = width;
canvas.height = width / ratio;
let ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
try {
let data = canvas.toDataURL('image/png');
resolve(data);
} catch (e) {
resolve(null);
}
};
img.onerror = function() {
resolve(null);
};
img.src = originalBase64;
});
}
On Firefox there is an issue for SVGs without set width / height.
See this working example including a fix for the Firefox issue.
This is an old question, in 2022 we have ES6 and we don't need 3rd party libraries.
Here is a very basic way to convert svg images into other formats.
The trick is to load the svg element as an img element, then use a canvas element to convert the image into the desired format. So, four steps are needed:
Extract svg as xml data string.
Load the xml data string into a img element
Convert the img element to a dataURL using a canvas element
Load the converted dataURL into a new img element
Step 1
Extracting a svg as xml data string is simple, we don't need to convert it as a base64 string. We just serialize it as XML then we encode the string as a URI:
// Select the element:
const $svg = document.getElementById('svg-container').querySelector('svg')
// Serialize it as xml string:
const svgAsXML = (new XMLSerializer()).serializeToString($svg)
// Encode it as a data string:
const svgData = `data:image/svg+xml,${encodeURIComponent(svgAsXML)}`
Step 2
Loading the xml data string into a img element:
// This function returns a Promise whenever the $img is loaded
const loadImage = async url => {
const $img = document.createElement('img')
$img.src = url
return new Promise((resolve, reject) => {
$img.onload = () => resolve($img)
$img.onerror = reject
$img.src = url
})
}
Step 3
Converting the img element to a dataURL using a canvas element:
const $canvas = document.createElement('canvas')
$canvas.width = $svg.clientWidth
$canvas.height = $svg.clientHeight
$canvas.getContext('2d').drawImage(img, 0, 0, $svg.clientWidth, $svg.clientHeight)
return $canvas.toDataURL(`image/${format}`, 1.0)
Step 4
Loading the converted dataURL into a new img element:
const $img = document.createElement('img')
$img.src = dataURL
$holder.appendChild($img)
Here you have a working snippet:
const $svg = document.getElementById('svg-container').querySelector('svg')
const $holder = document.getElementById('img-container')
const $label = document.getElementById('img-format')
const destroyChildren = $element => {
while ($element.firstChild) {
const $lastChild = $element.lastChild ?? false
if ($lastChild) $element.removeChild($lastChild)
}
}
const loadImage = async url => {
const $img = document.createElement('img')
$img.src = url
return new Promise((resolve, reject) => {
$img.onload = () => resolve($img)
$img.onerror = reject
})
}
const convertSVGtoImg = async e => {
const $btn = e.target
const format = $btn.dataset.format ?? 'png'
$label.textContent = format
destroyChildren($holder)
const svgAsXML = (new XMLSerializer()).serializeToString($svg)
const svgData = `data:image/svg+xml,${encodeURIComponent(svgAsXML)}`
const img = await loadImage(svgData)
const $canvas = document.createElement('canvas')
$canvas.width = $svg.clientWidth
$canvas.height = $svg.clientHeight
$canvas.getContext('2d').drawImage(img, 0, 0, $svg.clientWidth, $svg.clientHeight)
const dataURL = await $canvas.toDataURL(`image/${format}`, 1.0)
console.log(dataURL)
const $img = document.createElement('img')
$img.src = dataURL
$holder.appendChild($img)
}
const buttons = [...document.querySelectorAll('[data-format]')]
for (const $btn of buttons) {
$btn.onclick = convertSVGtoImg
}
.wrapper {
display: flex;
flex-flow: row nowrap;
width: 100vw;
}
.images {
display: flex;
flex-flow: row nowrap;
width: 70%;
}
.image {
width: 50%;
display: flex;
flex-flow: row wrap;
justify-content: center;
}
.label {
width: 100%;
text-align: center;
}
<div class="wrapper">
<div class="item images">
<div class="image left">
<div class="label">svg</div>
<div id="svg-container">
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="200" height="200" viewBox="0 0 248 204">
<path fill="#1d9bf0" d="M221.95 51.29c.15 2.17.15 4.34.15 6.53 0 66.73-50.8 143.69-143.69 143.69v-.04c-27.44.04-54.31-7.82-77.41-22.64 3.99.48 8 .72 12.02.73 22.74.02 44.83-7.61 62.72-21.66-21.61-.41-40.56-14.5-47.18-35.07 7.57 1.46 15.37 1.16 22.8-.87-23.56-4.76-40.51-25.46-40.51-49.5v-.64c7.02 3.91 14.88 6.08 22.92 6.32C11.58 63.31 4.74 33.79 18.14 10.71c25.64 31.55 63.47 50.73 104.08 52.76-4.07-17.54 1.49-35.92 14.61-48.25 20.34-19.12 52.33-18.14 71.45 2.19 11.31-2.23 22.15-6.38 32.07-12.26-3.77 11.69-11.66 21.62-22.2 27.93 10.01-1.18 19.79-3.86 29-7.95-6.78 10.16-15.32 19.01-25.2 26.16z"/>
</svg>
</div>
</div>
<div class="image right">
<div id="img-format" class="label"></div>
<div id="img-container"></div>
</div>
</div>
<div class="item buttons">
<button id="btn-png" data-format="png">PNG</button>
<button id="btn-jpg" data-format="jpeg">JPG</button>
<button id="btn-webp" data-format="webp">WEBP</button>
</div>
</div>
Svg to png can be converted depending on conditions:
If svg is in format SVG (string) paths:
create canvas
create new Path2D() and set svg as parameter
draw path on canvas
create image and use canvas.toDataURL() as src.
example:
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
let svgText = 'M10 10 h 80 v 80 h -80 Z';
let p = new Path2D('M10 10 h 80 v 80 h -80 Z');
ctx.stroke(p);
let url = canvas.toDataURL();
const img = new Image();
img.src = url;
Note that Path2D not supported in ie and partially supported in edge. Polyfill solves that:
https://github.com/nilzona/path2d-polyfill
Create svg blob and draw on canvas using .drawImage():
make canvas element
make a svgBlob object from the svg xml
make a url object from domUrl.createObjectURL(svgBlob);
create an Image object and assign url to image src
draw image into canvas
get png data string from canvas: canvas.toDataURL();
Nice description:
https://web.archive.org/web/20200125162931/http://ramblings.mcpher.com:80/Home/excelquirks/gassnips/svgtopng
Note that in ie you will get exception on stage of canvas.toDataURL(); It is because IE has too high security restriction and treats canvas as readonly after drawing image there. All other browsers restrict only if image is cross origin.
Use canvg JavaScript library. It is separate library but has useful functions.
Like:
ctx.drawSvg(rawSvg);
var dataURL = canvas.toDataURL();
I recently discovered a couple of image tracing libraries for JavaScript that indeed are able to build an acceptable approximation to the bitmap, both size and quality. I'm developing this JavaScript library and CLI :
https://www.npmjs.com/package/svg-png-converter
Which provides unified API for all of them, supporting browser and node, non depending on DOM, and a Command line tool.
For converting logos/cartoon/like images it does excellent job. For photos / realism some tweaking is needed since the output size can grow a lot.
It has a playground although right now I'm working on a better one, easier to use, since more features has been added:
https://cancerberosgx.github.io/demos/svg-png-converter/playground/#
There are several ways to convert SVG to PNG using the Canvg library.
In my case, I needed to get the PNG blob from inline SVG.
The library documentation provides an example (see OffscreenCanvas example).
But this method does not work at the moment in Firefox. Yes, you can enable the gfx.offscreencanvas.enabled option in the settings. But will every user on the site do this? :)
However, there is another way that will work in Firefox too.
const el = document.getElementById("some-svg"); //this is our inline SVG
var canvas = document.createElement('canvas'); //create a canvas for the SVG render
canvas.width = el.clientWidth; //set canvas sizes
canvas.height = el.clientHeight;
const svg = new XMLSerializer().serializeToString(el); //convert SVG to string
//render SVG inside canvas
const ctx = canvas.getContext('2d');
const v = await Canvg.fromString(ctx, svg);
await v.render();
let canvasBlob = await new Promise(resolve => canvas.toBlob(resolve));
For the last line thanks to this answer
get data URIs from SVG:
data:image/svg+xml;base64,${btoa(new XMLSerializer().serializeToString(svgElem))}
prepare an Image
create a canvas and use toDataURL to export.
Example
<!-- test data-->
<svg width="400" height="400"><g transform="translate(23.915343915343925,-80.03971756398937)" class="glyph" stroke="#000000" fill="#a0a0a0"><path d="M74.97 108.70L74.97 108.70L100.08 110.77Q93.89 147.91 87.35 179.89L87.35 179.89L148.23 179.89L148.23 194.34Q143.76 277.91 113.84 339.81L113.84 339.81Q144.44 363.54 163.70 382.46L163.70 382.46L146.51 402.75Q128.62 384.18 101.80 361.83L101.80 361.83Q75.32 405.85 34.39 436.80L34.39 436.80L17.20 415.82Q57.43 386.93 82.20 345.66L82.20 345.66Q57.78 326.40 27.86 304.39L27.86 304.39Q44.37 257.96 56.75 203.97L56.75 203.97L19.26 203.97L19.26 179.89L61.90 179.89Q69.47 145.16 74.97 108.70ZM93.20 323.99L93.20 323.99Q118.65 272.06 123.12 203.97L123.12 203.97L82.20 203.97Q69.47 260.03 55.71 297.17L55.71 297.17Q76.01 311.61 93.20 323.99ZM160.26 285.13L160.26 260.37L239.71 260.37L239.71 216.01Q268.25 191.24 294.05 155.48L294.05 155.48L170.58 155.48L170.58 130.71L322.94 130.71L322.94 155.48Q297.49 191.93 265.50 223.92L265.50 223.92L265.50 260.37L337.38 260.37L337.38 285.13L265.50 285.13L265.50 397.59Q265.50 431.64 237.65 431.64L237.65 431.64L187.09 431.64L180.21 407.57Q202.22 407.91 227.67 407.91L227.67 407.91Q239.71 407.91 239.71 390.03L239.71 390.03L239.71 285.13L160.26 285.13Z"></path></g></svg>
<button title="download">svg2png</button>
<script>
const output = {"name": "result.png", "width": 64, "height": 64}
document.querySelector("button").onclick = () => {
const svgElem = document.querySelector("svg")
// const uriData = `data:image/svg+xml;base64,${btoa(svgElem.outerHTML)}` // it may fail.
const uriData = `data:image/svg+xml;base64,${btoa(new XMLSerializer().serializeToString(svgElem))}`
const img = new Image()
img.src = uriData
img.onload = () => {
const canvas = document.createElement("canvas");
[canvas.width, canvas.height] = [output.width, output.height]
const ctx = canvas.getContext("2d")
ctx.drawImage(img, 0, 0, output.width, output.height)
// 👇 download
const a = document.createElement("a")
const quality = 1.0 // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingQuality
a.href = canvas.toDataURL("image/png", quality)
a.download = output.name
a.append(canvas)
a.click()
a.remove()
}
}
</script>
Here are my 2 cents. Somehow Download anchor tag is not working as expected in code snippet, however it was working fine in Chrome.
Here is working jsFiddle
const waitForImage = imgElem => new Promise(resolve => imgElem.complete ? resolve() : imgElem.onload = imgElem.onerror = resolve);
const svgToImgDownload = ext => {
if (!['png', 'jpg', 'webp'].includes(ext))
return;
const _svg = document.querySelector("#svg_container").querySelector('svg');
const xmlSerializer = new XMLSerializer();
let _svgStr = xmlSerializer.serializeToString(_svg);
const img = document.createElement('img');
img.src = 'data:image/svg+xml;base64,' + window.btoa(_svgStr);
waitForImage(img)
.then(_ => {
const canvas = document.createElement('canvas');
canvas.width = _svg.clientWidth;
canvas.height = _svg.clientHeight;
canvas.getContext('2d').drawImage(img, 0, 0, _svg.clientWidth, _svg.clientHeight);
return canvas.toDataURL('image/' + (ext == 'jpg' ? 'jpeg' : ext), 1.0);
})
.then(dataURL => {
console.log(dataURL);
document.querySelector("#img_download_btn").innerHTML = `Download`;
})
.catch(console.error);
};
document.querySelector('#map2Png').addEventListener('click', _ => svgToImgDownload('png'));
document.querySelector('#map2Jpg').addEventListener('click', _ => svgToImgDownload('jpg'));
document.querySelector('#map2Webp').addEventListener('click', _ => svgToImgDownload('webp'));
<div id="svg_container" style="float: left; width: 50%">
<svg width="200" height="200" viewBox="-100 -100 200 200">
<circle cx="0" cy="20" r="70" fill="#D1495B" />
<circle cx="0" cy="-75" r="12" fill="none" stroke="#F79257" stroke-width="2" />
<rect x="-17.5" y="-65" width="35" height="20" fill="#F79257" />
</svg>
</div>
<div>
<button id="map2Png">PNG</button>
<button id="map2Jpg">JPG</button>
<button id="map2Webp">WEBP</button>
</div>
<div id="img_download_btn"></div>