svgcanvgoffscreen-canvas

How can I draw an SVG over what already exists in a canvas?


What I am trying to do is draw the SVG over the blue circle on the canvas. The blue circle should be the background.

However, the result is just the SVG.

enter image description here

The final result should look something like

(note: pretend I used just a circle SVG instead of the one used in the code)

enter image description here

where the background is larger and the SVG is inset and centered in it.

How can I do that?

const svg = `<svg xmlns="http://www.w3.org/2000/svg" fill="#ff0000" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M256 0c17.7 0 32 14.3 32 32l0 10.4c93.7 13.9 167.7 88 181.6 181.6l10.4 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-10.4 0c-13.9 93.7-88 167.7-181.6 181.6l0 10.4c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-10.4C130.3 455.7 56.3 381.7 42.4 288L32 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l10.4 0C56.3 130.3 130.3 56.3 224 42.4L224 32c0-17.7 14.3-32 32-32zM107.4 288c12.5 58.3 58.4 104.1 116.6 116.6l0-20.6c0-17.7 14.3-32 32-32s32 14.3 32 32l0 20.6c58.3-12.5 104.1-58.4 116.6-116.6L384 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l20.6 0C392.1 165.7 346.3 119.9 288 107.4l0 20.6c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-20.6C165.7 119.9 119.9 165.7 107.4 224l20.6 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-20.6 0zM256 224a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>`;

var blob = new Blob(
  Array.prototype.map.call(
    document.querySelectorAll('script[type="text/js-worker"]'),
    function (oScript) {
      return oScript.textContent;
    }
  ),
  { type: "text/javascript" }
);

const worker = new Worker(window.URL.createObjectURL(blob), {
  type: "module"
});

const img = document.querySelector("img");

worker.postMessage({
  width: 20,
  height: 20,
  svg: svg
});

worker.onmessage = (event) => {
  img.src = event.data.pngUrl;
};
<script type="text/js-worker">
  import { DOMParser } from 'https://cdn.skypack.dev/@xmldom/xmldom@^0.7.5'
  import {
    Canvg,
    presets
  } from 'https://cdn.skypack.dev/canvg@^4.0.0'

  const preset = presets.offscreen({
    DOMParser
  })

  self.onmessage = async (event) => {
    const {
      width,
      height,
      svg
    } = event.data
    
    const canvas = new OffscreenCanvas(width, height)
    const ctx = canvas.getContext('2d')
    
    ctx.beginPath();
    ctx.arc(10, 10, 10, 0, 2 * Math.PI);
    ctx.fillStyle = 'blue';
    ctx.fill();
    
    const v = await Canvg.from(ctx, svg, preset)
    await v.render()

    const blob = await canvas.convertToBlob()
    const pngUrl = URL.createObjectURL(blob)

    self.postMessage({
      pngUrl
    })
  }
</script>

<img />


Solution

  • Canvg has an ignoreClear option so that it doesn't clear the canvas when rendering.

    Note that in below snippet I did refactor a bit your code to not output a png image but instead render directly in a placeholder <canvas> element.

    const svg = `<svg xmlns="http://www.w3.org/2000/svg" fill="#ff0000" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M256 0c17.7 0 32 14.3 32 32l0 10.4c93.7 13.9 167.7 88 181.6 181.6l10.4 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-10.4 0c-13.9 93.7-88 167.7-181.6 181.6l0 10.4c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-10.4C130.3 455.7 56.3 381.7 42.4 288L32 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l10.4 0C56.3 130.3 130.3 56.3 224 42.4L224 32c0-17.7 14.3-32 32-32zM107.4 288c12.5 58.3 58.4 104.1 116.6 116.6l0-20.6c0-17.7 14.3-32 32-32s32 14.3 32 32l0 20.6c58.3-12.5 104.1-58.4 116.6-116.6L384 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l20.6 0C392.1 165.7 346.3 119.9 288 107.4l0 20.6c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-20.6C165.7 119.9 119.9 165.7 107.4 224l20.6 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-20.6 0zM256 224a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>`;
    
    var blob = new Blob(
      Array.prototype.map.call(
        document.querySelectorAll('script[type="text/js-worker"]'),
        function (oScript) {
          return oScript.textContent;
        }
      ),
      { type: "text/javascript" }
    );
    
    const worker = new Worker(window.URL.createObjectURL(blob), {
      type: "module"
    });
    // Use a placeholder <canvas> instead of converting to a png
    const canvasEl = document.querySelector("canvas");
    canvasEl.width = canvasEl.height = 20;
    const canvas = canvasEl.transferControlToOffscreen();
    worker.postMessage({
      canvas,
      svg: svg
    }, [canvas]);
    <script type="text/js-worker">
      import { DOMParser } from 'https://cdn.skypack.dev/@xmldom/xmldom@^0.7.5'
      import {
        Canvg,
        presets
      } from 'https://cdn.skypack.dev/canvg@^4.0.0'
    
      const preset = presets.offscreen({
        DOMParser,
      });
      preset.ignoreClear = true;
    
      self.onmessage = async (event) => {
        const {
          canvas,
          svg
        } = event.data
        
        const ctx = canvas.getContext('2d')
        
        ctx.beginPath();
        ctx.arc(10, 10, 10, 0, 2 * Math.PI);
        ctx.fillStyle = 'blue';
        ctx.fill();
        
        const v = await Canvg.from(ctx, svg, preset)
        await v.render()
    
      }
    </script>
    
    <canvas></canvas>