在浏览器中将SVG转换为图像(JPEG,PNG等)

388

我想通过 JavaScript 将 SVG 转换为位图图像(如 JPEG、PNG 等)。


你实际想要完成什么任务?尽管echo-flows的答案告诉我们(在某些浏览器中)这是可能的,但对于几乎所有实际情况,都有更好、更简单的转换方法。 - aaaaaaaaaaaa
2
这里有一个使用d3的例子:https://dev59.com/zmgu5IYBdhLWcg3wHjh7#23667012 - ace
http://svgopen.org/2010/papers/62-From_SVG_to_Canvas_and_Back/ - 完美地运行![在链接页面上,sourceSVG = $(“your_svg_elem_name”).get(0)] - Vijay Singh
相关:https://dev59.com/WXA75IYBdhLWcg3wqK__ - mathheadinclouds
https://mybyways.com/blog/convert-svg-to-png-using-your-browser - natenho
这个回答解决了你的问题吗?如何将HTML Canvas保存为gif/jpg/png/pdf格式? - Carson
14个回答

282

31
这不仅仅涉及JavaScript,还包括HTML5。这将无法在IE8或任何不支持HTML5 Canvas的浏览器上工作。 - James
192
谢谢您不支持IE8。人们应该明白,是时候向前迈进了。 - Sanket Sahu
11
你现在可以使用 JavaScript SVG 库Pablo来实现这一点(是我开发的)。查看 toImage()download() 以获取自动下载图像的功能。 - Prem
2
canvg是不完整的,例如它不支持蒙版。 - daniel
1
请注意,在2022年底(及以后),第一步完全是不必要的。只需将SVG绘制到画布上,无需使用第三方库,然后使用toDataURL即可。 - Mike 'Pomax' Kamermans
显示剩余3条评论

48

jbeard4的解决方案效果非常好。

我正在使用Raphael SketchPad创建一个SVG。第1步中有文件链接。

要创建一个保存按钮(svg的id为“editor”,canvas的id为“canvas”):

$("#editor_save").click(function() {

// the canvg call that takes the svg xml and converts it to a canvas
canvg('canvas', $("#editor").html());

// the canvas calls to output a png
var canvas = document.getElementById("canvas");
var img = canvas.toDataURL("image/png");
// do what you want with the base64, write to screen, post to server, etc...
});

1
canvg需要第二个参数为<svg>...</svg>,但是jQuery的html()函数不会添加svg标签,所以这段代码对我有用,但我需要编辑canvg实时代码为canvg('canvas', '<svg>'+$("#editor").html()+'</svg>'); - Luckyn
1
@Luckyn 如果你在 svg 元素的父级元素上调用 $(selector).html(),它将起作用。 - jonathanGB
1
@Luckyn 和 @jonathanGB,您不应该在包装器上使用 html() 或手动构建父 svg 标记——这甚至可能会使这种 hack 遗漏一些属性。只需使用 $(svg_elem)[0].outerHTML 即可获得 svg 的完整源代码及其内容。仅供参考... - JWL

36

这似乎在大多数浏览器中都有效:

function copyStylesInline(destinationNode, sourceNode) {
   var containerElements = ["svg","g"];
   for (var cd = 0; cd < destinationNode.childNodes.length; cd++) {
       var child = destinationNode.childNodes[cd];
       if (containerElements.indexOf(child.tagName) != -1) {
            copyStylesInline(child, sourceNode.childNodes[cd]);
            continue;
       }
       var style = sourceNode.childNodes[cd].currentStyle || window.getComputedStyle(sourceNode.childNodes[cd]);
       if (style == "undefined" || style == null) continue;
       for (var st = 0; st < style.length; st++){
            child.style.setProperty(style[st], style.getPropertyValue(style[st]));
       }
   }
}

function triggerDownload (imgURI, fileName) {
  var evt = new MouseEvent("click", {
    view: window,
    bubbles: false,
    cancelable: true
  });
  var a = document.createElement("a");
  a.setAttribute("download", fileName);
  a.setAttribute("href", imgURI);
  a.setAttribute("target", '_blank');
  a.dispatchEvent(evt);
}

function downloadSvg(svg, fileName) {
  var copy = svg.cloneNode(true);
  copyStylesInline(copy, svg);
  var canvas = document.createElement("canvas");
  var bbox = svg.getBBox();
  canvas.width = bbox.width;
  canvas.height = bbox.height;
  var ctx = canvas.getContext("2d");
  ctx.clearRect(0, 0, bbox.width, bbox.height);
  var data = (new XMLSerializer()).serializeToString(copy);
  var DOMURL = window.URL || window.webkitURL || window;
  var img = new Image();
  var svgBlob = new Blob([data], {type: "image/svg+xml;charset=utf-8"});
  var url = DOMURL.createObjectURL(svgBlob);
  img.onload = function () {
    ctx.drawImage(img, 0, 0);
    DOMURL.revokeObjectURL(url);
    if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob)
    {
        var blob = canvas.msToBlob();         
        navigator.msSaveOrOpenBlob(blob, fileName);
    } 
    else {
        var imgURI = canvas
            .toDataURL("image/png")
            .replace("image/png", "image/octet-stream");
        triggerDownload(imgURI, fileName);
    }
    document.removeChild(canvas);
  };
  img.src = url;
}

4
由于.msToBlob()的安全问题,此功能在IE11中无法正常工作。 - Florian Leitgeb
1
谢谢!我喜欢它既适用于“本地”SVG HTML节点,也适用于远程SVG URL。此外,它不需要完整的外部库。 - Rudolf Real
5
可以给个使用示例吗? - Neil Morgan
2
这个copyStylesInline函数帮了我很大的忙!如果没有它,我的导出png文件看起来会非常奇怪。谢谢! - Augusto Peres
我在使用SVGAnimateElement时遇到了问题(由于动画不会影响SVG的样式),是否有其他人也遇到了这个问题? - Kle0s

31

将SVG转换为Blob URL和Blob URL转换为PNG图像的解决方案

const svg=`<svg version="1.1" baseProfile="full" width="300" height="200"
xmlns="http://www.w3.org/2000/svg">
   <rect width="100%" height="100%" fill="red" />
   <circle cx="150" cy="100" r="80" fill="green" />
   <text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text></svg>`
svgToPng(svg,(imgData)=>{
    const pngImage = document.createElement('img');
    document.body.appendChild(pngImage);
    pngImage.src=imgData;
});
 function svgToPng(svg, callback) {
    const url = getSvgUrl(svg);
    svgUrlToPng(url, (imgData) => {
        callback(imgData);
        URL.revokeObjectURL(url);
    });
}
function getSvgUrl(svg) {
    return  URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' }));
}
function svgUrlToPng(svgUrl, callback) {
    const svgImage = document.createElement('img');
    // imgPreview.style.position = 'absolute';
    // imgPreview.style.top = '-9999px';
    document.body.appendChild(svgImage);
    svgImage.onload = function () {
        const canvas = document.createElement('canvas');
        canvas.width = svgImage.clientWidth;
        canvas.height = svgImage.clientHeight;
        const canvasCtx = canvas.getContext('2d');
        canvasCtx.drawImage(svgImage, 0, 0);
        const imgData = canvas.toDataURL('image/png');
        callback(imgData);
        // document.body.removeChild(imgPreview);
    };
    svgImage.src = svgUrl;
 }


此解决方案不适用于复杂的SVG(例如带有图像的图案)。 - Djov
1
这是您的代码,但进行了优化,感谢您... https://codepen.io/arcaelas-the-looper/pen/wvrqzvm - Alejandro Reyes
1
感谢您,@AlejandroReyes。 - Haseeb Saeed
欢迎,您可以在Github上关注我:arcaelas。 - Alejandro Reyes
由于某些原因,当我使用 URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' })) 时,一些SVG被污染了(我怀疑是因为它们在内部定义了 <style>),但是后来我尝试从头开始创建数据URL,像这样:'data:image/svg+xml;utf-8,${svg.replace(/#/g, '%23').replace(/\n/g, '%0A')}';,这个方法完美地解决了问题!你也可以像这样使用base64:'data:image/svg+xml;base64,${btoa(drawing)}';。我正在使用electron。 - puaaaal

29

这是一个老问题,在2022年我们有ES6,不需要第三方库。

这里有一种将svg图像转换为其他格式的基本方法。

关键是将svg元素作为img元素加载,然后使用画布元素将图像转换为所需格式。因此,需要四个步骤:

  1. svg提取为xml数据字符串。
  2. xml数据字符串加载到img元素中
  3. 使用canvas元素将img元素转换为dataURL
  4. 将转换后的dataURL加载到新的img元素中

第1步

提取svg作为xml数据字符串很简单,我们不需要将其转换为base64字符串。我们只需将其序列化为XML,然后将字符串编码为URI:

// Data header for a svg image: 
const dataHeader = 'data:image/svg+xml;charset=utf-8'

// Serialize it as xml string:
const serializeAsXML = $e => (new XMLSerializer()).serializeToString($e)

// Encode URI data as UTF8 data:
const encodeAsUTF8 = s => `${dataHeader},${encodeURIComponent(s)}`

// Select the element:
const $svg = document.getElementById('svg-container').querySelector('svg')

// Encode it as a data string:
const svgData = encodeAsUTF8(serializeAsXML($svg))

注意:
如果您需要一个base64数据,您可以使用这个选项:
...

// Encode URI data as base64 data:
const encodeAsB64 = s => `${dataHeader};base64,${btoa(s)}`
...

// Encode it as a data string:
const svgData = encodeAsB64(serializeAsXML($svg))

第二步
将XML数据字符串加载到img元素中:
// This function returns a Promise whenever the $img is loaded
const loadImage = async url => {
    const $img = document.createElement('img')
    $img.src = url
    return new Promise((resolve, reject) => {
        $img.onload = () => resolve($img)
        $img.onerror = reject
        $img.src = url
    })
}

第三步
元素转换为使用元素的dataURL:
const $canvas = document.createElement('canvas')
$canvas.width = $svg.clientWidth
$canvas.height = $svg.clientHeight
$canvas.getContext('2d').drawImage(img, 0, 0, $svg.clientWidth, $svg.clientHeight)
return $canvas.toDataURL(`image/${format}`, 1.0)

第四步

将转换后的dataURL加载到新的img元素中:

const $img = document.createElement('img')
$img.src = dataURL
$holder.appendChild($img)

这里有一个可用的代码片段:

const dataHeader = 'data:image/svg+xml;charset=utf-8'
const $svg = document.getElementById('svg-container').querySelector('svg')
const $holder = document.getElementById('img-container')
const $label = document.getElementById('img-format')

const destroyChildren = $element => {
  while ($element.firstChild) {
    const $lastChild = $element.lastChild ?? false
    if ($lastChild) $element.removeChild($lastChild)
  }
}

const loadImage = async url => {
  const $img = document.createElement('img')
  $img.src = url
  return new Promise((resolve, reject) => {
    $img.onload = () => resolve($img)
    $img.onerror = reject
  })
}

const serializeAsXML = $e => (new XMLSerializer()).serializeToString($e)

const encodeAsUTF8 = s => `${dataHeader},${encodeURIComponent(s)}`
const encodeAsB64 = s => `${dataHeader};base64,${btoa(s)}`

const convertSVGtoImg = async e => {
  const $btn = e.target
  const format = $btn.dataset.format ?? 'png'
  $label.textContent = format

  destroyChildren($holder)

  const svgData = encodeAsUTF8(serializeAsXML($svg))

  const img = await loadImage(svgData)
  
  const $canvas = document.createElement('canvas')
  $canvas.width = $svg.clientWidth
  $canvas.height = $svg.clientHeight
  $canvas.getContext('2d').drawImage(img, 0, 0, $svg.clientWidth, $svg.clientHeight)
  
  const dataURL = await $canvas.toDataURL(`image/${format}`, 1.0)
  console.log(dataURL)
  
  const $img = document.createElement('img')
  $img.src = dataURL
  $holder.appendChild($img)
}

const buttons = [...document.querySelectorAll('[data-format]')]
for (const $btn of buttons) {
  $btn.onclick = convertSVGtoImg
}
.wrapper {
  display: flex;
  flex-flow: row nowrap;
  width: 100vw;
}

.images {
  display: flex;
  flex-flow: row nowrap;
  width: 70%;
}

.image {
  width: 50%;
  display: flex;
  flex-flow: row wrap;
  justify-content: center;
}

.label {
  width: 100%;
  text-align: center;
}
<div class="wrapper">
  <div class="item images">
    <div class="image left">
      <div class="label">svg</div>
      <div id="svg-container">
        <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="200" height="200" viewBox="0 0 248 204">
  <path fill="#1d9bf0" d="M221.95 51.29c.15 2.17.15 4.34.15 6.53 0 66.73-50.8 143.69-143.69 143.69v-.04c-27.44.04-54.31-7.82-77.41-22.64 3.99.48 8 .72 12.02.73 22.74.02 44.83-7.61 62.72-21.66-21.61-.41-40.56-14.5-47.18-35.07 7.57 1.46 15.37 1.16 22.8-.87-23.56-4.76-40.51-25.46-40.51-49.5v-.64c7.02 3.91 14.88 6.08 22.92 6.32C11.58 63.31 4.74 33.79 18.14 10.71c25.64 31.55 63.47 50.73 104.08 52.76-4.07-17.54 1.49-35.92 14.61-48.25 20.34-19.12 52.33-18.14 71.45 2.19 11.31-2.23 22.15-6.38 32.07-12.26-3.77 11.69-11.66 21.62-22.2 27.93 10.01-1.18 19.79-3.86 29-7.95-6.78 10.16-15.32 19.01-25.2 26.16z"/>
</svg>
      </div>
    </div>
    <div class="image right">
      <div id="img-format" class="label"></div>
      <div id="img-container"></div>
    </div>
  </div>
  <div class="item buttons">
    <button id="btn-png" data-format="png">PNG</button>
    <button id="btn-jpg" data-format="jpeg">JPG</button>
    <button id="btn-webp" data-format="webp">WEBP</button>
  </div>
</div>


在第二步中,您设置了 img.src 两次,我认为第二个就足够了。 - kartsims
在第二步中的图像仍然包含我们使用画布将其转换为另一种格式(即png,jpeg等)的data:image/svg+xml图像。如果在您的情况下step 2足够好,请使用我提供的完整解决方案。 - Teocci

11

更改svg以匹配您的元素

function svg2img(){
    var svg = document.querySelector('svg');
    var xml = new XMLSerializer().serializeToString(svg);
    var svg64 = btoa(xml); //for utf8: btoa(unescape(encodeURIComponent(xml)))
    var b64start = 'data:image/svg+xml;base64,';
    var image64 = b64start + svg64;
    return image64;
};svg2img()

2
对我来说不起作用,我收到了这个错误:Uncaught TypeError: Failed to execute 'serializeToString' on 'XMLSerializer': parameter 1 is not of type 'Node'. - Xsmael
1
@Xsmael 尝试切换 DOMParser 接口 http://developer.mozilla.org/zh-CN/docs/Web/API/DOMParser - Mahdi Khalili
1
@RyanArqueza 什么怎么样? - Mahdi Khalili
我应该在什么情况下关注UTF-8? - Mateut Alin
1
@MTZ 你应该考虑使用 utf8 部分来处理 utf8 字符,例如你的 SVG 包含非 ASCII 字符。 - Mahdi Khalili
显示剩余2条评论

11

我的使用场景是从网络加载svg数据,而这个ES6类就能胜任这项工作。

class SvgToPngConverter {
  constructor() {
    this._init = this._init.bind(this);
    this._cleanUp = this._cleanUp.bind(this);
    this.convertFromInput = this.convertFromInput.bind(this);
  }

  _init() {
    this.canvas = document.createElement("canvas");
    this.imgPreview = document.createElement("img");
    this.imgPreview.style = "position: absolute; top: -9999px";

    document.body.appendChild(this.imgPreview);
    this.canvasCtx = this.canvas.getContext("2d");
  }

  _cleanUp() {
    document.body.removeChild(this.imgPreview);
  }

  convertFromInput(input, callback) {
    this._init();
    let _this = this;
    this.imgPreview.onload = function() {
      const img = new Image();
      _this.canvas.width = _this.imgPreview.clientWidth;
      _this.canvas.height = _this.imgPreview.clientHeight;
      img.crossOrigin = "anonymous";
      img.src = _this.imgPreview.src;
      img.onload = function() {
        _this.canvasCtx.drawImage(img, 0, 0);
        let imgData = _this.canvas.toDataURL("image/png");
        if(typeof callback == "function"){
            callback(imgData)
        }
        _this._cleanUp();
      };
    };

    this.imgPreview.src = input;
  }
}

这是如何使用它的方法

let input = "https://restcountries.eu/data/afg.svg"
new SvgToPngConverter().convertFromInput(input, function(imgData){
    // You now have your png data in base64 (imgData). 
    // Do what ever you wish with it here.
});

如果您想要一个纯JavaScript版本,您可以前往Babel网站并在那里转译代码。

10
这里有一个不需要使用库且返回 Promise 的函数:

不需要任何库即可运行

/**
 * converts a base64 encoded data url SVG image to a PNG image
 * @param originalBase64 data url of svg image
 * @param width target width in pixel of PNG image
 * @return {Promise<String>} resolves to png data url of the image
 */
function base64SvgToBase64Png (originalBase64, width) {
    return new Promise(resolve => {
        let img = document.createElement('img');
        img.onload = function () {
            document.body.appendChild(img);
            let canvas = document.createElement("canvas");
            let ratio = (img.clientWidth / img.clientHeight) || 1;
            document.body.removeChild(img);
            canvas.width = width;
            canvas.height = width / ratio;
            let ctx = canvas.getContext("2d");
            ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
            try {
                let data = canvas.toDataURL('image/png');
                resolve(data);
            } catch (e) {
                resolve(null);
            }
        };
        img.onerror = function() {
            resolve(null);
        };
        img.src = originalBase64;
    });
}

在Firefox浏览器中,存在一种没有设置宽度/高度的SVG问题
请查看包括Firefox问题修复的工作示例

在Safari中对我没有用,只是挂起,没有错误。 - chrisheseltine
感谢您进行测试,我以前没有在Safari上进行过测试。所以Javascript控制台中也没有错误,这很奇怪? - klues
是的,在我的经验中非常奇怪,但不幸的是我没有更多时间浪费在上面。 - chrisheseltine
  1. 我建议在img.onerror上附加代码以避免出现Promise卡住的情况。
  2. 您不必将图像附加到document.body上。
- Fredrik Blomqvist
@klues 嗯,但为什么你需要使用img.clientWidth呢?如果你使用img.naturalWidth或img.width,它可以在不连接到DOM的情况下工作。 - Fredrik Blomqvist
显示剩余2条评论

2
  1. 从SVG获取data URIs

    data:image/svg+xml;base64,${btoa(new XMLSerializer().serializeToString(svgElem))}

  2. 准备Image
  3. 创建画布并使用toDataURL导出。

示例

<!-- test data-->
<svg width="400" height="400"><g transform="translate(23.915343915343925,-80.03971756398937)" class="glyph" stroke="#000000" fill="#a0a0a0"><path d="M74.97 108.70L74.97 108.70L100.08 110.77Q93.89 147.91 87.35 179.89L87.35 179.89L148.23 179.89L148.23 194.34Q143.76 277.91 113.84 339.81L113.84 339.81Q144.44 363.54 163.70 382.46L163.70 382.46L146.51 402.75Q128.62 384.18 101.80 361.83L101.80 361.83Q75.32 405.85 34.39 436.80L34.39 436.80L17.20 415.82Q57.43 386.93 82.20 345.66L82.20 345.66Q57.78 326.40 27.86 304.39L27.86 304.39Q44.37 257.96 56.75 203.97L56.75 203.97L19.26 203.97L19.26 179.89L61.90 179.89Q69.47 145.16 74.97 108.70ZM93.20 323.99L93.20 323.99Q118.65 272.06 123.12 203.97L123.12 203.97L82.20 203.97Q69.47 260.03 55.71 297.17L55.71 297.17Q76.01 311.61 93.20 323.99ZM160.26 285.13L160.26 260.37L239.71 260.37L239.71 216.01Q268.25 191.24 294.05 155.48L294.05 155.48L170.58 155.48L170.58 130.71L322.94 130.71L322.94 155.48Q297.49 191.93 265.50 223.92L265.50 223.92L265.50 260.37L337.38 260.37L337.38 285.13L265.50 285.13L265.50 397.59Q265.50 431.64 237.65 431.64L237.65 431.64L187.09 431.64L180.21 407.57Q202.22 407.91 227.67 407.91L227.67 407.91Q239.71 407.91 239.71 390.03L239.71 390.03L239.71 285.13L160.26 285.13Z"></path></g></svg>

<button title="download">svg2png</button>
<script>
    const output = {"name": "result.png", "width": 64, "height": 64}
    document.querySelector("button").onclick = () => {
        const svgElem = document.querySelector("svg")
        // const uriData = `data:image/svg+xml;base64,${btoa(svgElem.outerHTML)}` // it may fail.
        const uriData = `data:image/svg+xml;base64,${btoa(new XMLSerializer().serializeToString(svgElem))}`
        const img = new Image()
        img.src = uriData
        img.onload = () => {
            const canvas = document.createElement("canvas");
            [canvas.width, canvas.height] = [output.width, output.height]
            const ctx = canvas.getContext("2d")
            ctx.drawImage(img, 0, 0, output.width, output.height)

            //  download
            const a = document.createElement("a")
            const quality = 1.0 // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingQuality
            a.href = canvas.toDataURL("image/png", quality)
            a.download = output.name
            a.append(canvas)
            a.click()
            a.remove()
        }
    }
</script>


2

SVG 可以根据条件转换为 PNG

  1. 如果 SVGSVG(字符串)路径格式
  • 创建画布
  • 创建 new Path2D() 并将 SVG 设置为参数
  • 在画布上绘制路径
  • 创建图像并使用 canvas.toDataURL() 作为 src

例如:

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
let svgText = 'M10 10 h 80 v 80 h -80 Z';
let p = new Path2D('M10 10 h 80 v 80 h -80 Z');
ctx.stroke(p);
let url = canvas.toDataURL();
const img = new Image();
img.src = url;

请注意,Path2D不支持ie,在edge中部分支持。可以使用polyfill解决此问题: https://github.com/nilzona/path2d-polyfill
  1. 创建svg blob对象并使用.drawImage()绘制到画布上:
  • 创建canvas元素
  • 从svg xml创建svgBlob对象
  • 从domUrl.createObjectURL(svgBlob)创建url对象
  • 创建Image对象并将url赋值给image src
  • 将图像绘制到画布上
  • 从画布获取png数据字符串:canvas.toDataURL();

详细描述请参见: https://web.archive.org/web/20200125162931/http://ramblings.mcpher.com:80/Home/excelquirks/gassnips/svgtopng

请注意,在ie中,您将在canvas.toDataURL()阶段收到异常。这是因为IE具有过高的安全限制,并在绘制图像后将canvas视为只读。所有其他浏览器仅在图像跨域时限制。

  1. 使用JavaScript库canvg。它是一个单独的库,但具有有用的功能。

例如:

ctx.drawSvg(rawSvg);
var dataURL = canvas.toDataURL();

1
第三个链接已经失效。 - Serdar Sayın
1
是的,确实。我现在不知道如何到达那里。但上面的描述可能足以理解一些内容。将来复制一些参考文献的上下文是个好主意。 - Alex Vovchuk
修复链接失效。回溯机器是修复链接失效的好工具。 - albert

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