Saturday, December 25, 2021

Dynamically rendering HTML to image with javascript and SVG without using any external library

In this article, we render CSS styled HTML to image. With this method, HTML can be exported to png, jpeg or bmp without using any external js library. SVG 2 is a new recommendation that is exposed by w3 consortium. SVG 2 has a new feature, foreign object, that enables us to use different rendering engines in a SVG representation. So, we use HTML rendering engine to render image representation of HTML. Example codes are at below:

<div id="export_html_to_svg_container" style="display: flex;flex-direction: row;width: 300px; height: 100px;flex-grow: 1">
    <div style="width: 100%; background-color: #ff7070;"></div>
    <div style="display: flex;justify-content: center;text-align: center;align-items: center;width: 100%;background: #a624a8;font-weight: 500;color: white;">OUTPUT</div>
    <div style="width: 100%; background-color: #7070ff;"></div>
</div>

<div style="margin-top:10px">
    <button onclick="exportToImage('png')">PNG</button>
    <button onclick="exportToImage('jpeg')">JPEG</button>
    <button onclick="exportToImage('bmp')">BMP</button>
</div>

<script>

    function exportToImage(imageType) {

        let content = document.getElementById("export_html_to_svg_container");
        let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        let foreignObject = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");      

        svg.appendChild(foreignObject);
        foreignObject.innerHTML = content.outerHTML;
        svg.setAttribute("width", "300px");
        svg.setAttribute("height", "100px");
        foreignObject.setAttribute("width", "100%");
        foreignObject.setAttribute("height", "100%");

        let serializer = new XMLSerializer();
        let serializedContent = serializer.serializeToString(svg);
        let base64Content = btoa(serializedContent);
        let imageUrl = "data:image/svg+xml;base64," + base64Content;
        let img = document.createElement("img");
        
        img.onerror = function (a, b) {
            alert("error");
        }

        img.onload = function () {

            let canvas = document.createElement("canvas");
            let context = canvas.getContext("2d");
            context.drawImage(img, 0, 0);
            let dataUrl = canvas.toDataURL("image/" + imageType);
            simulateDownload("output." + imageType, dataUrl);
        }

        img.src = imageUrl;
    }

    function simulateDownload(download, href) {
        const a = document.createElement('a');
        a.download = download;
        a.href = href;
        a.click();
        a.remove();
    }

</script>
In example, we render div (export_html_to_svg_container) object with its content to image.  First of all, we create an SVG object. We need a foreign object to place the HTML in, so we create one. We put the HTML in the foreign object and the foreign object in the SVG object. Before rendering, we must set the SVG object's width and height attributes. Finally, we serialize the SVG object to string with XMLSerializer and generate an image url using that string. With this url, we render the output image with the help of the image(img) object and canvas.

This method is compatible with many modern browsers like Chrome, Firefox and Edge.

Working Example:

OUTPUT