172

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

Community
  • 1
  • 1
Randy Voet
  • 3,670
  • 4
  • 29
  • 36
  • 5
    This question has the answer with a live demo http://stackoverflow.com/questions/5495952/draw-svg-on-html5-canvas-with-support-for-font-element – Drew LeSueur Nov 17 '11 at 18:45

8 Answers8

184

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.

Klesun
  • 12,280
  • 5
  • 59
  • 52
Simon Sarris
  • 62,212
  • 13
  • 141
  • 171
  • 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 Feb 21 '11 at 11:52
  • 1
    Simon, what you are saying is not correct. And secondly, it's a confirmed bug in Chrome. – Mathias Lykkegaard Lorenzen Jul 29 '11 at 12:25
  • `ctx.drawImage` seems to work with recent Safaris too. – heisenbug Nov 02 '14 at 22:45
  • 5
    Wikimedia doesn't like you using the SVG, it seems. I swapped in http://snapsvg.io/assets/images/logo.svg as the first available SVG I found. Worked in FF. http://jsfiddle.net/Na6X5/331/ – Thomas Mar 10 '15 at 04:24
  • @SimonSarris, as per your example how a large svg fit on the small screens http://jsfiddle.net/nikhilvkd/Fur2d/365/ – Krish May 10 '16 at 06:51
  • 1
    You can also use [Data URI's](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) in order to do this: http://jsfiddle.net/020k543w/ – Swivel Jul 24 '16 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. – Hatoru Hansou Nov 18 '16 at 07:48
  • 1
    waiting for the load event is so annoying when you need the image synchronously :( – Rivenfall May 20 '19 at 13:38
38

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/>
ashleedawg
  • 20,365
  • 9
  • 72
  • 105
Henrique Campos
  • 491
  • 4
  • 8
  • 2
    Same thing with fonts, they need to be embedded in the SVG: https://jsfiddle.net/ykx7kp8L/121/ – Sphinxxx Apr 25 '18 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. – luckydonald Dec 04 '18 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/>
Crashalot
  • 33,605
  • 61
  • 269
  • 439
Matyas
  • 13,473
  • 3
  • 60
  • 73
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.

sampathsris
  • 21,564
  • 12
  • 71
  • 98
Max West
  • 755
  • 7
  • 11
6

Mozilla has a simple way for drawing SVG on canvas called "Drawing DOM objects into a canvas"

DᴀʀᴛʜVᴀᴅᴇʀ
  • 7,681
  • 17
  • 73
  • 127
Nati Krisi
  • 1,023
  • 1
  • 12
  • 23
  • This has the same drawback as in @Simon's first method: does not work in Firefox, Chrome OK. – amergin Jul 03 '14 at 23:56
  • 7
    Your link doesn't work anymore. I still interested in Mozilla way – Alirezak Jan 09 '19 at 16:16
  • @Alirezak this is probably the link: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes – Lovell D'souza Dec 08 '20 at 08:10
  • 2
    @Alirezak just dug up a capture from archive.org: https://web.archive.org/web/20160529021018/https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Drawing_DOM_objects_into_a_canvas – WickyNilliams Aug 18 '21 at 07:33
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

Alberto Manuel
  • 163
  • 1
  • 5
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>
Infigon
  • 554
  • 5
  • 18
0

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

Syed
  • 15,657
  • 13
  • 120
  • 154