Home > OS >  Modify Canvas SVG Image with Javascript
Modify Canvas SVG Image with Javascript

Time:01-30

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
    or replaceWith 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>

  • Related