Drawing an SVG file on a HTML5 canvas

Viewed 285401

Is there a default way of drawing an SVG file onto a HTML5 canvas? Google Chrome supports loading the SVG as an image (and simply using drawImage), but the developer console does warn that resource interpreted as image but transferred with MIME type image/svg+xml.

I know that a possibility would be to convert the SVG to canvas commands (like in this question), but I'm hoping that's not needed. I don't care about older browsers (so if FireFox 4 and IE 9 will support something, that's good enough).

8 Answers

Something to add, to show the svg correctly in canvas element add the attributes height and width to svg root element, Eg:

<svg height="256" width="421">...</svg>

Or

// Use this if to add the attributes programmatically
const svg = document.querySelector("#your-svg");

svg.setAttribute("width", `${width}`);
svg.setAttribute("height", `${height}`);

For more details see this

As vector graphics are meant to be potentially scaled, I will offer a method I have made that is as similar to SVG as possible. This method supports:

  • A resizable canvas

  • Transparency

  • Hi-resolution graphics (automatically, but no pinch support yet)

  • Scaling of the SVG in both directions!

    (To do this with pixels, you will have to divide the new length by the old one)

This is done by converting the SVG to canvas functions here, then adding that to svgRed() (after changing the name of ctx to ctx2. The svgRed() function is used on startup and during pixel ratio changes (for example, increasing the zoom), but not before the canvas is scaled (in order to increase the size of the image). It converts the result into an Image, and can be called any time by ctx.drawImage(redBalloon, Math.round(Math.random() * w), Math.round(Math.random() * h)). To clear the screen, use ctx.clearRect(0, 0, w, h) to do so.

Testing this with the SVG, I found that this is many times faster, as long as the zoom is not set to large values (I discovered that a window.devicePixelRatio of 5 gives just over twice the speed as an SVG, and a window.devicePixelRatio of 1 is approximately 60 times faster).

This also has the bonus benefit of allowing many "fake SVG" items to exist simultaneously, without messing with the HTML (this is shown in the code below). If the screen is resized or scaled, you will need to render it again (completely ignored in my example).

The canvas showing the result is scaled down (in pixels) by the devicePixelRatio, so be careful when drawing items! Scaling (with ctx.scale() this canvas will result in a potentially blurry image, so be sure to account for the pixel difference!

NOTE: It seems that the browser takes a while to optimize the image after the devicePixelRatio has changed (around a second sometimes), so it may not be a good idea to spam the canvas with images immediately, as the example shows.

<!DOCTYPE html>
<html>

<head lang="en">
    <title>Balloons</title>

    <style>
        * {
            user-select: none;
            -webkit-user-select: none;
        }

        body {
            background-color: #303030;
        }
    </style>
</head>

<body>
    <canvas id="canvas2" style="display: none" width="0" height="0"></canvas>
    <canvas id="canvas"
        style="position: absolute; top: 20px; left: 20px; background-color: #606060; border-radius: 25px;" width="0"
        height="0"></canvas>
    <script>
        // disable pinches: hard to implement resizing
        document.addEventListener("touchstart", function (e) {
            if (e.touches.length > 1) {
                e.preventDefault()
            }
        }, { passive: false })
        document.addEventListener("touchmove", function (e) {
            if (e.touches.length > 1) {
                e.preventDefault()
            }
        }, { passive: false })
        // disable trackpad zooming
        document.addEventListener("wheel", e => {
            if (e.ctrlKey) {
                e.preventDefault()
            }
        }, {
            passive: false
        })

        // This is the canvas that shows the result
        const canvas = document.getElementById("canvas")
        // This canvas is hidden and renders the balloon in the background
        const canvas2 = document.getElementById("canvas2")

        // Get contexts
        const ctx = canvas.getContext("2d")
        const ctx2 = canvas2.getContext("2d")
        // Scale the graphic, if you want
        const scaleX = 1
        const scaleY = 1

        // Set up parameters
        var prevRatio, w, h, trueW, trueH, ratio, redBalloon

        function draw() {
            for (var i = 0; i < 1000; i++) {
                ctx.drawImage(redBalloon, Math.round(Math.random() * w), Math.round(Math.random() * h))
            }
            requestAnimationFrame(draw)
        }

        // Updates graphics and canvas.
        function updateSvg() {
            var pW = trueW
            var pH = trueH
            trueW = window.innerWidth - 40
            trueH = Math.max(window.innerHeight - 40, 0)
            ratio = window.devicePixelRatio
            w = trueW * ratio
            h = trueH * ratio
            if (trueW === 0 || trueH === 0) {
                canvas.width = 0
                canvas.height = 0
                canvas.style.width = "0px"
                canvas.style.height = "0px"
                return
            }
            if (trueW !== pW || trueH !== pH || ratio !== prevRatio) {
                canvas.width = w
                canvas.height = h
                canvas.style.width = trueW + "px"
                canvas.style.height = trueH + "px"
                if (prevRatio !== ratio) {
                    // Update graphic
                    redBalloon = svgRed()
                    // Set new ratio
                    prevRatio = ratio
                }
            }
        }
        window.onresize = updateSvg
        updateSvg()
        draw()

        // The vector graphic (you may want to manually tweak the coordinates if they are slightly off (such as changing 25.240999999999997 to 25.241)
        function svgRed() {
            // Scale the hidden canvas
            canvas2.width = Math.round(44 * ratio * scaleX)
            canvas2.height = Math.round(65 * ratio * scaleY)
            ctx2.scale(ratio * scaleX, ratio * scaleY)

            // Draw the graphic
            ctx2.save()
            ctx2.beginPath()
            ctx2.moveTo(0, 0)
            ctx2.lineTo(44, 0)
            ctx2.lineTo(44, 65)
            ctx2.lineTo(0, 65)
            ctx2.closePath()
            ctx2.clip()
            ctx2.strokeStyle = '#0000'
            ctx2.lineCap = 'butt'
            ctx2.lineJoin = 'miter'
            ctx2.miterLimit = 4
            ctx2.save()
            ctx2.beginPath()
            ctx2.moveTo(0, 0)
            ctx2.lineTo(44, 0)
            ctx2.lineTo(44, 65)
            ctx2.lineTo(0, 65)
            ctx2.closePath()
            ctx2.clip()
            ctx2.save()
            ctx2.fillStyle = "#e02f2f"
            ctx2.beginPath()
            ctx2.moveTo(27, 65)
            ctx2.lineTo(22.9, 61.9)
            ctx2.lineTo(21.9, 61)
            ctx2.lineTo(21.1, 61.6)
            ctx2.lineTo(17, 65)
            ctx2.lineTo(27, 65)
            ctx2.closePath()
            ctx2.moveTo(21.8, 61)
            ctx2.lineTo(21.1, 60.5)
            ctx2.bezierCurveTo(13.4, 54.2, 0, 41.5, 0, 28)
            ctx2.bezierCurveTo(0, 9.3, 12.1, 0.4, 21.9, 0)
            ctx2.bezierCurveTo(33.8, -0.5, 45.1, 10.6, 43.9, 28)
            ctx2.bezierCurveTo(43, 40.8, 30.3, 53.6, 22.8, 60.2)
            ctx2.lineTo(21.8, 61)
            ctx2.fill()
            ctx2.stroke()
            ctx2.restore()
            ctx2.save()
            ctx2.fillStyle = "#f59595"
            ctx2.beginPath()
            ctx2.moveTo(18.5, 7)
            ctx2.bezierCurveTo(15.3, 7, 5, 11.5, 5, 26.3)
            ctx2.bezierCurveTo(5, 38, 16.9, 50.4, 19, 54)
            ctx2.bezierCurveTo(19, 54, 9, 38, 9, 28)
            ctx2.bezierCurveTo(9, 17.3, 15.3, 9.2, 18.5, 7)
            ctx2.fill()
            ctx2.stroke()
            ctx2.restore()
            ctx2.restore()
            ctx2.restore()

            // Save the results
            var image = new Image()
            image.src = canvas2.toDataURL()
            return image
        }
    </script>
</body>

</html>

Try this:

let svg = `<svg xmlns="http://www.w3.org/2000/svg" ...`;
let blob = new Blob([svg], {type: 'image/svg+xml'});
let url = URL.createObjectURL(blob);

const ctx = canvas.getContext('2d');
canvas.width = 900;
canvas.height = 1400;

const appLogo = new Image();
appLogo.onload = () => ctx.drawImage(appLogo, 54, 387, 792, 960);
appLogo.src = url;

// let image = document.createElement('img');
// image.src = url;
// image.addEventListener('load', () => URL.revokeObjectURL(url), {once: true});

Note: Blob is not defined in Node.js file, This is code designed to run in the browser, not in Node.

More info here

Related