I have a svg image that I can use to draw to canvas:
image = new Image()
image.src = "image.svg"
Is it possible to modify image
to, for example, change the colour of an element?
CodePudding user response:
No, directly changing the color of an SVG image in an HTML canvas is not possible. To modify the color of an element in an SVG, you need to modify the SVG code and then render the updated image on the canvas. This can be achieved by loading the SVG code as a string, parsing it using an XML parser, modifying the relevant elements, and then rendering the modified code on the canvas using methods such as createSVG() or innerHTML.
CodePudding user response:
Load the SVG
Instead of loading the SVG directly into an image element you can use the fetch function. Here a data URL, just for the example -- it can just be replaced by a URL. At this point you could store the SVG in a variable.
Parsing the SVG
To change the SVG one approach could be to use XPath. This is what happens in the function changeAttribute() that takes the SVG, an XPath expression and an object containing the data about what to change.
Load the SVG into an image element and render in canvas
As I read your question you already figured this out. In the function insertIntoCanvas() turn the SVG (XML document) into a data URL by using a FileReader. After that the image is created and drawn on the canvas.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
fetch('data:image/svg xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAgMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI CiAgPHBhdGggZD0iTSA1IDAgTCAwIDUgTCA1IDEwIEwgMTAgNSBaIiAvPgo8L3N2Zz4=')
.then(response => response.text())
.then(text => {
let xmlDoc = new DOMParser().parseFromString(text,'text/xml');
changeAttribute(xmlDoc, '//svg:path', {fill:'red'});
changeAttribute(xmlDoc, '//svg:svg', {width:100, height:100});
insertIntoCanvas(xmlDoc);
});
function insertIntoCanvas(xmlDoc){
let file = new File([xmlDoc.rootElement.outerHTML], 'svg.svg', {
type: "image/svg xml"
});
// and a reader
let reader = new FileReader();
reader.addEventListener('load', e => {
/* create a new image assign the result of the filereader
to the image src */
let img = new Image();
// wait for it to got load
img.addEventListener('load', e => {
// update canvas with new image
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(e.target, 0, 0);
});
img.src = e.target.result;
});
// read the file as a data URL
reader.readAsDataURL(file);
}
function changeAttribute(doc, xpath, obj) {
let r = doc.evaluate(xpath, doc, nsResolver, XPathResult.ANY_TYPE, null);
let nodes = [];
let next = r.iterateNext();
while (next) {
nodes.push(next);
next = r.iterateNext();
}
nodes.forEach(node => {
Object.keys(obj).forEach(key => {
node.setAttribute(key, obj[key]);
});
});
}
function nsResolver(prefix) {
const ns = {
'xhtml': 'http://www.w3.org/1999/xhtml',
'svg': 'http://www.w3.org/2000/svg'
};
return ns[prefix] || null;
}
canvas {
border: thin solid black;
}
<canvas id="canvas" width="300" height="200"></canvas>
CodePudding user response:
You would have to:
- load the external SVG file as text
- inject it in a shadowDOM (the browser will do the parsing)
- add your
<style>
elements - extract the SVG
outerHTML
- replace invalid URI characters " and #
- create a
<canvas>
- write SVG to
canvas.img
as XML DataURI - add your
<canvas>
to the shadowDOM
orreplaceWith
the Web Component that did the work
A native JavaScript Web Component <svg-to-canvas>
, supported in all modern browsers,
can it do all for you, so you only write:
<svg-to-canvas src="//load-file.github.io/heart.svg">
<style>
svg { background:green }
path { fill:red }
</style>
</svg-to-canvas>
JSFiddle: https://jsfiddle.net/WebComponents/cosfrqyv/
StackOverflow snippet:
<style>
svg-to-canvas { background: lightgreen }
canvas { background:pink }
</style>
<svg-to-canvas src="//load-file.github.io/heart.svg">
<style>
svg { background:green }
path { fill:red }
</style>
</svg-to-canvas>
<svg-to-canvas replaceWith src="//load-file.github.io/heart.svg"></svg-to-canvas>
<script>
customElements.define("svg-to-canvas", class extends HTMLElement {
async connectedCallback(svg,canvas,img) {
this.style.display = "inline-block";
this.attachShadow({mode:"open"})
.innerHTML = await (await fetch(this.getAttribute("src"))).text();
svg = this.shadowRoot.querySelector("svg");
svg.append(...this.querySelectorAll("*"));
// stop here for an SVG inside shadowDOM
canvas = document.createElement('canvas');
img = Object.assign(new Image(), {
onl oad : () => {
canvas.width = img.width; canvas.height = img.height;
canvas.getContext('2d').drawImage(img, 0, 0);
},
src : "data:image/svg xml;utf8,"
svg.outerHTML.replace(/"/g, "'").replace(/#/g, "#")
});
if (this.hasAttribute("replaceWith")) this.replaceWith(canvas);
else { svg.remove(); this.shadowRoot.append(canvas) }
}
})
</script>