Search code examples
javascripthtmlsvgbase64png

Converting SVG images to base64 PNG with browser


I'm trying to convert a bunch of siblings SVG images to base64 PNG strings, using just the browser and plain Javascript, but for some reason beyond my knowledge I get the base64 PNG string of only the last SVG. Here is the HTML snippet:

<html>
<head><title>Browser SVG to PNG Converter</title></head>
<body bgcolor="#DDDDEE">
    <h1>Browser SVG to PNG Converter</h1>
    <div id="div_svg">
        <svg xmlns="http://www.w3.org/2000/svg" width="150" height="150">
            <rect width="100%" height="100%" style="stroke-width:0;fill:rgb(35,235,235);" />
            <rect x="30" y="15" width="90" height="120" style="stroke:#000000;stroke-width:1;fill:none;" /> </svg>
        <svg xmlns="http://www.w3.org/2000/svg" width="130" height="150">
            <rect width="100%" height="100%" style="stroke-width:0;fill:rgb(35,200,35);" />
            <rect x="30" y="30" width="70" height="90" style="stroke:#000000;stroke-width:2;fill:none;" /> </svg>
        <svg xmlns="http://www.w3.org/2000/svg" width="150" height="150">
            <rect width="100%" height="100%" style="stroke-width:0;fill:rgb(35,235,235);" />
            <rect x="30" y="15" width="90" height="120" style="stroke:#000000;stroke-width:3;fill:none;" /> </svg>
    </div>
    <br />
    <button id="btn_convert">Convert SVG to PNG and display it below</button>
    <br />
    <br />
    <canvas id="aux_canvas" style="display:none;"></canvas>
    <textarea id="output_png" style="width:90vw;height:40vh;display:block;">base64 PNG source will be displayed here:</textarea>
    <script>
    document.getElementById('btn_convert').addEventListener('click', function() {
        var svg_all = document.getElementById('div_svg').querySelectorAll('svg');
        var canvas = document.getElementById('aux_canvas');
        var win = window.URL || window.webkitURL || window;
        var img = new Image();
        img.addEventListener("load", function() {
            canvas.getContext('2d').drawImage(img, 0, 0);
            win.revokeObjectURL(url);
            document.getElementById('output_png').value += "\n\n" + canvas.toDataURL("image/png");
        });
        for(let i = 0; i < svg_all.length; i++) {
            let svg = svg_all[i];
            canvas.width = svg.getBoundingClientRect().width;
            canvas.height = svg.getBoundingClientRect().height;
            var data = new XMLSerializer().serializeToString(svg);
            var blob = new Blob([data], {
                type: 'image/svg+xml'
            });
            var url = win.createObjectURL(blob);
            document.getElementById('output_png').value += "\nGoing to load image..."
            img.src = url;
            document.getElementById('output_png').value += "\nEnded loading image..."
        }
    });
    </script>
</body>
</html>

Input as hardcoded SVG and output to textarea are only for testing purposes, my ultimate goal is a Javascript function getting an array of SVG sources and returning the corresponding PNG images as an array of base64 strings.


Solution

  • Image loading is asynchronous, you have to wait for each image to load before moving to the next, use Promise with async and await before each loading.‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

    Here is a working example

    const svgString = `
            <svg xmlns="http://www.w3.org/2000/svg" width="150" height="150">
                <rect width="100%" height="100%" style="stroke-width:0;fill:rgb(35,235,235);" />
                <rect x="30" y="15" width="90" height="120" style="stroke:#000000;stroke-width:1;fill:none;" /> </svg>
            <svg xmlns="http://www.w3.org/2000/svg" width="130" height="150">
                <rect width="100%" height="100%" style="stroke-width:0;fill:rgb(35,200,35);" />
                <rect x="30" y="30" width="70" height="90" style="stroke:#000000;stroke-width:2;fill:none;" /> </svg>
            <svg xmlns="http://www.w3.org/2000/svg" width="150" height="150">
                <rect width="100%" height="100%" style="stroke-width:0;fill:rgb(35,235,235);" />
                <rect x="30" y="15" width="90" height="120" style="stroke:#000000;stroke-width:3;fill:none;" /> </svg>`;
                
    const div = document.createElement("div");
    div.innerHTML = svgString;
    const svgs = div.querySelectorAll("svg");
    
    
    const convertButton = document.querySelector("#btn_convert");
    const output = document.querySelector("#output_png");
    const img = document.createElement("img");
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext('2d');
    const URL = window.URL || window.webkitURL || window;
    
    convertButton.onclick = async () => {        
      for(let i = 0; i < svgs.length; i++) {
        output.value += "\n---- Loading image... \n\n";
        output.value += await svgToBase64(svgs[i]);
        output.value += "\n---- Image Loaded ... \n\n";
      }
    };
        
    async function svgToBase64(svg) {
      const width = svg.getAttribute("width");
      const height = svg.getAttribute("height");
      
      canvas.width = width;
      canvas.height = height;
    
      const data = new XMLSerializer().serializeToString(svg);
      console.log(data)
      const blob = new Blob([data], {
          type: 'image/svg+xml'
      });
      console.log(blob)
      var url = URL.createObjectURL(blob);
    
      return new Promise((resolve) => {
        img.onload = () => {
          ctx.drawImage(img, 0, 0);
          URL.revokeObjectURL(url);
          resolve(canvas.toDataURL("image/png"));
        }
        img.src = url;
      });
    }
    body {
      background: #DDDDEE;
    }
    <h1>Browser SVG to PNG Converter</h1>
    <br />
    <button id="btn_convert">Convert SVG to PNG and display it below</button>
    <br />
    <br />
    <textarea id="output_png" style="width:90vw;height:40vh;display:block;">base64 PNG source will be displayed here:</textarea>