在HTML5画布上绘制SVG文件

173

有没有一种默认的方法可以将SVG文件绘制到HTML5画布上?Google Chrome支持将SVG作为图像加载(并简单地使用drawImage),但开发者控制台会警告resource interpreted as image but transferred with MIME type image/svg+xml

我知道一个可能的解决方案是将SVG转换为画布命令(如这个问题中所述),但我希望不需要这样做。我不关心旧版本的浏览器(因此,如果FireFox 4和IE 9支持某些功能,那就足够好了)。


5
这个问题的答案有一个现场演示。https://dev59.com/IW035IYBdhLWcg3wT-RC - Drew LeSueur
8个回答

184

编辑:2019年12月

Path2D() 构造函数现在被所有主要浏览器支持,"允许在2D画布表面声明路径对象"。


编辑:2014年11月

现在,您可以使用ctx.drawImage来绘制具有.svg源的HTMLImageElements某些但不是所有浏览器中(覆盖率为75%:Chrome,IE11和Safari可用,Firefox存在一些错误,但夜间版本已经修复了这些错误)。

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";

点击此处查看实例。您应该在画布中看到一个绿色正方形。页面上的第二个绿色正方形是相同的 <svg> 元素,用作参考插入到DOM中。

您还可以使用新的Path2D对象来绘制SVG(字符串)路径。换句话说,您可以编写:

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

这里有一个实时的例子。


2010年原始回答:

没有本地支持让你在画布中原生使用SVG路径的功能。你必须自己转换或使用库来完成。

我建议看一下canvg: (请查看homepagedemos)

canvg接受SVG文件的URL或文本,在JavaScript中解析它,并将结果呈现在画布上。


4
为什么需要这个?SVG看起来在画布上只需使用drawImage就可以完美绘制。但是我仍然收到了警告。它是从哪里来的? - shoosh
1
Simon,你说的不正确。其次,在Chrome中这是一个已确认的bug。 - Mathias Lykkegaard Lorenzen
5
维基媒体似乎不喜欢您使用SVG格式。我替换为http://snapsvg.io/assets/images/logo.svg,这是我找到的第一个可用的SVG文件。在Firefox上可以使用。http://jsfiddle.net/Na6X5/331/ - Thomas
1
您还可以使用数据URI来实现此操作:http://jsfiddle.net/020k543w/ - Swivel
11
注意:由于长期存在的FireFox Bug,缺乏宽度和高度标签的SVG文件无法在画布上渲染。此外,宽度和高度不能用百分比表示。 - Hatoru Hansou
显示剩余3条评论

39

参考@Matyas的回答:如果SVG图像也是base64编码的,则将其绘制到输出中。

示例:

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
同样的,字体也需要被嵌入到SVG中:https://jsfiddle.net/ykx7kp8L/121/ - Sphinxxx
1
你可以通过迭代svg中的img标签,然后在画布上单独绘制图像。 - luckydonald

29

您可以通过以下方法轻松地将简单的svg绘制到画布上:

  1. svg源分配给以base64格式编码的图片
  2. 将图片绘制到画布上

注意:该方法唯一的缺点是它无法绘制嵌入在svg中的图片。(请见演示)

演示:

(注意,嵌入的图片只能在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
有没有办法解决你提到的问题。图像嵌入在SVG中。 - Vijay Baskaran
抱歉,我还没有找到嵌入式图片问题的解决方案。 - Matyas
好的。谢谢Matyas :) - Vijay Baskaran

6

这与@Simon的第一种方法具有相同的缺点:在Firefox中无法工作,但在Chrome中可以。 - amergin
7
你的链接已经失效了,我仍然对Mozilla的方式感兴趣。 - Alirezak
@Alirezak 这可能是链接:https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes - Lovell D'souza
3
@Alirezak 刚从 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

6

正如Simon上面所说,使用drawImage不应该起作用。但是,使用canvg库并:

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

这来自于Simon提供的链接,里面有很多其他建议并指出你要么链接到canvg.js和rgbcolor.js,要么下载它们。这些允许你通过URL或使用svg标签之间的内联SVG代码,在JavaScript函数中操纵和加载SVG。


1

试试这个:

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});

注意:在Node.js文件中未定义Blob。此代码是设计用于在浏览器中运行,而不是在Node中运行。
更多信息在这里

1

有一些需要补充的内容,为了在画布元素中正确显示svg,请在svg根元素中添加heightwidth属性,例如:

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

或者

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

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

更多细节请参见 this

0

由于矢量图形可以被无限缩放,因此我提供了一种尽可能接近SVG的方法。这种方法支持以下功能:

  • 可调整大小的画布

  • 透明度

  • 高分辨率图形(自动处理,但目前不支持捏合手势)

  • 在两个方向上缩放SVG!

    (如果要使用像素进行缩放,则需要将新长度除以旧长度)

这是通过将SVG转换为画布函数here来完成的,然后将其添加到svgRed()中(在更改ctx的名称为ctx2之后)。svgRed()函数用于启动和像素比例更改期间(例如增加缩放),但不会在缩放画布之前使用(以增加图像的大小)。它将结果转换为Image,并且可以随时通过ctx.drawImage(redBalloon, Math.round(Math.random() * w), Math.round(Math.random() * h))调用。要清除屏幕,请使用ctx.clearRect(0, 0, w, h)

通过对SVG进行测试,我发现只要缩放没有设置为大值,这样做的速度会快很多(我发现window.devicePixelRatio为5时,速度比SVG快两倍多,而window.devicePixelRatio为1时,速度大约快60倍)。

这还有一个额外的好处,允许许多“假SVG”项目同时存在,而不会影响HTML(如下面的代码所示)。如果屏幕被调整大小或缩放,您将需要重新渲染它(在我的示例中完全被忽略)。

显示结果的画布通过devicePixelRatio缩小(以像素为单位),因此在绘制项目时要小心!缩放(使用ctx.scale())此画布将导致潜在的模糊图像,因此一定要考虑像素差异!

注意:似乎浏览器在devicePixelRatio更改后需要一段时间来优化图像(有时约一秒钟),因此立即用图像垃圾邮件填充画布可能不是一个好主意,就像示例一样。

<!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>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接