179

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).

1

8 Answers 8

191

EDIT: Dec 2019

The Path2D() constructor is supported by all major browsers now, "allowing path objects to be declared on 2D canvas surfaces".


EDIT: Nov 2014

You can now use ctx.drawImage to draw HTMLImageElements that have a .svg source in some but not all browsers (75% coverage: Chrome, IE11, and Safari work, Firefox works with some bugs, but nightly has fixed them).

var img = new Image();
img.onload = function() {
    ctx.drawImage(img, 0, 0);
}
img.src = "http://upload.wikimedia.org/wikipedia/commons/d/d2/Svg_example_square.svg";

Live example here. You should see a green square in the canvas. The second green square on the page is the same <svg> element inserted into the DOM for reference.

You can also use the new Path2D objects to draw SVG (string) paths. In other words, you can write:

var path = new Path2D('M 100,100 h 50 v 50 h 50');
ctx.stroke(path);

Live example of that here.


Original 2010 answer:

There's nothing native that allows you to natively use SVG paths in canvas. You must convert yourself or use a library to do it for you.

I'd suggest looking in to canvg: (check homepage & demos)

canvg takes the URL to an SVG file, or the text of the SVG file, parses it in JavaScript and renders the result on Canvas.

8
  • 4
    Why is this needed? SVG seem to draw perfectly on a canvase with just drawImage. But I still get that warning. Where does it come from?
    – shoosh
    Commented Feb 21, 2011 at 11:52
  • 1
    Simon, what you are saying is not correct. And secondly, it's a confirmed bug in Chrome. Commented Jul 29, 2011 at 12:25
  • 5
    Wikimedia doesn't like you using the SVG, it seems. I swapped in snapsvg.io/assets/images/logo.svg as the first available SVG I found. Worked in FF. jsfiddle.net/Na6X5/331
    – Thomas
    Commented Mar 10, 2015 at 4:24
  • 1
    You can also use Data URI's in order to do this: jsfiddle.net/020k543w
    – Swivel
    Commented Jul 24, 2016 at 20:52
  • 11
    Note: due to a long standing FireFox Bug, sadly, svgs that lack the width and height tags won't render at all on the canvas. Also, width and height must not be in percentages. Commented Nov 18, 2016 at 7:48
39

Further to @Matyas answer: if the svg's image is also in base64, it will be drawn to the output.

Demo:

var svg = document.querySelector('svg');
var img = document.querySelector('img');
var canvas = document.querySelector('canvas');

// get svg data
var xml = new XMLSerializer().serializeToString(svg);

// make it base64
var svg64 = btoa(xml);
var b64Start = 'data:image/svg+xml;base64,';

// prepend a "header"
var image64 = b64Start + svg64;

// set it as the source of the img element
img.onload = function() {
    // draw the image onto the canvas
    canvas.getContext('2d').drawImage(img, 0, 0);
}
img.src = image64;
svg, img, canvas {
  display: block;
}
SVG
<svg height="40" width="40">
  <rect width="40" height="40" style="fill:rgb(255,0,255);" />
  <image xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAEX0lEQVQ4jUWUyW6cVRCFv7r3/kO3u912nNgZgESAAgGBCJgFgxhW7FkgxAbxMLwBEmIRITbsQAgxCEUiSIBAYIY4g1EmYjuDp457+Lv7n+4tFjbwAHVOnVPnlLz75ht67OhhZg/M0p6d5tD9C8SNBBs5XBJhI4uNLC4SREA0UI9yJr2c4e6QO+v3WF27w+rmNrv9Pm7hxDyHFg5yYGEOYxytuRY2SYiSCIwgRgBQIxgjEAKuZWg6R9S0SCS4qKLZElY3HC5tp7QPtmlMN7HOETUTXBJjrEGsAfgPFECsQbBIbDGJZUYgGE8ugQyPm+o0STtTuGZMnKZEjRjjLIgAirEOEQEBDQFBEFFEBWLFtVJmpENRl6hUuFanTRAlbTeZarcx0R6YNZagAdD/t5N9+QgCYAw2jrAhpjM3zaSY4OJGTDrVwEYOYw2qioigoviq5MqF31m9fg1V5fCx+zn11CLNVnufRhBrsVFE1Ihpthu4KDYYwz5YQIxFBG7duMZnH31IqHL6wwnGCLFd4pez3/DaG2/x4GNPgBhEZG/GGlxkMVFkiNMYay3Inqxed4eP33uf7Y0uu90xWkGolFAru7sZn5w5w921m3u+su8vinEO02hEWLN/ANnL2rkvv2an2yd4SCKLM0JVBsCgAYZZzrnPP0eDRzXgfaCuPHXwuEYjRgmIBlQVVLl8/hKI4fRzz3L6uWe5+PMvnHz6aa4uX+D4yYe5vXaLH86eoyoLjLF476l9oKo9pi5HWONRX8E+YznOef7Vl1h86QWurlwjbc+QpikPPfoIcZLS39pmMikp8pzae6q6oqgriqrGqS+xeLScoMYSVJlfOMTl5RXW1+5w5fJVnFGWf1/mxEMnWPppiclkTLM5RdJoUBYFZVlQ5DnZMMMV167gixKLoXXsKGqnOHnqOJ/+/CfZ+XUiZ0jTmFv5mAvf/YjEliQ2vPD8Ir6qqEcZkzt38cMRo5WruFvfL9FqpyRxQhj0qLOax5I2S08+Tu/lFiGUGOPormxwuyfMnjrGrJa88uIixeYWl776lmrzNjmw8vcG8sU7ixpHMXFsCUVg9tABjEvRgzP82j7AhbyiX5Qcv2+Bvy7dYGZ1k7efeQB/Y4PBqGBtdYvb3SFzLcfqToZc/OB1zYeBSpUwLBlvjZidmWaSB1yaYOfn6LqI/r0hyU6P+cRSlhXjbEI2zvnt7y79oqQ3qeg4g6vKjCIXehtDmi6m0UnxVnCRkPUHVNt9qkLJxgXOCYNOg34v48raPaamU2o89/KKsQ9sTSpc0JK7NwdcX8s43Ek5cnSOLC/Z2R6Rj0ra0w2W1/t0xyWn51uk2Ri1QtSO6OU5d7OSi72cQeWxKG7p/Dp//JXTy6C1Pcbc6DMpPRtjTxChEznWhwVZUCKrjCrPoPDczHLmnLBdBgZlRRWUEBR3ZKrme5TlrTGlV440Y1IrXM9qQGi6mkG5V6uza7tUIeCDElTZ1L26elX+fcH/ACJBPYTJ4X8tAAAAAElFTkSuQmCC" height="20px" width="20px" x="10" y="10"></image></svg><br/>

IMAGE 
<img/><br/>
   
CANVAS 
<canvas></canvas><br/>

2
  • 2
    Same thing with fonts, they need to be embedded in the SVG: jsfiddle.net/ykx7kp8L/121
    – Sphinxxx
    Commented Apr 25, 2018 at 15:52
  • 1
    you might be able to iterate through the img tags in the svg, and just draw images on the canvas separately afterwards. Commented Dec 4, 2018 at 16:33
29

You can easily draw simple svgs onto a canvas by:

  1. Assigning the source of the svg to an image in base64 format
  2. Drawing the image onto a canvas

Note: The only drawback of the method is that it cannot draw images embedded in the svg. (see demo)

Demonstration:

(Note that the embedded image is only visible in the svg)

var svg = document.querySelector('svg');
var img = document.querySelector('img');
var canvas = document.querySelector('canvas');

// get svg data
var xml = new XMLSerializer().serializeToString(svg);

// make it base64
var svg64 = btoa(xml);
var b64Start = 'data:image/svg+xml;base64,';

// prepend a "header"
var image64 = b64Start + svg64;

// set it as the source of the img element
img.src = image64;

// draw the image onto the canvas
canvas.getContext('2d').drawImage(img, 0, 0);
svg, img, canvas {
  display: block;
}
SVG

<svg height="40">
  <rect width="40" height="40" style="fill:rgb(255,0,255);" />
  <image xlink:href="https://en.gravatar.com/userimage/16084558/1a38852cf33713b48da096c8dc72c338.png?size=20" height="20px" width="20px" x="10" y="10"></image>
</svg>
<hr/><br/>

IMAGE
<img/>
<hr/><br/>
   
CANVAS
<canvas></canvas>
<hr/><br/>

3
  • 3
    Is there any way to fix the problem that you mentioned. Image embedded in svg. Commented Nov 25, 2016 at 6:50
  • Sorry, but I haven't found a solution to the embedded image problem.
    – Matyas
    Commented Nov 26, 2016 at 9:09
  • Okay. Thanks Matyas :) Commented Nov 28, 2016 at 7:07
7

Mozilla had a documentation page on drawing DOM to canvas called "Drawing DOM objects into a canvas"

4
6

As Simon says above, using drawImage shouldn't work. But, using the canvg library and:

var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
ctx.drawSvg(SVG_XML_OR_PATH_TO_SVG, dx, dy, dw, dh);

This comes from the link Simon provides above, which has a number of other suggestions and points out that you want to either link to, or download canvg.js and rgbcolor.js. These allow you to manipulate and load an SVG, either via URL or using inline SVG code between svg tags, within JavaScript functions.

1

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

1

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

0

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>

Not the answer you're looking for? Browse other questions tagged or ask your own question.