You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用@napi-rs/canvas叠加SVG到PNG时样式丢失问题求助

问题

使用@napi-rs/canvas将SVG叠加在PNG图片上方后,SVG的所有样式与颜色均丢失。output["map"]和output["map2"]均为网页URL,相关代码如下:

const Canvas = require("@napi-rs/canvas");
const canvas = Canvas.createCanvas(700, 700);
const context = canvas.getContext('2d');
const background = await Canvas.loadImage(output["map"]);
context.drawImage(background, 0, 0, canvas.width, canvas.height);
const forground = await Canvas.loadImage(output["map2"])
context.drawImage(forground,0,0,canvas.width,canvas.height)

该URL指向的SVG内容:

<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300" class="creatureMap" style="position:absolute;">
<defs>
<filter id="blur" x="-30%" y="-30%" width="160%" height="160%"><feGaussianBlur stdDeviation="3" />
</filter>
<pattern id="pattern-untameable" width="10" height="10" patternTransform="rotate(135)" patternUnits="userSpaceOnUse"><rect width="4" height="10" fill="black"></rect>
</pattern>
<filter id="groupStroke"><feFlood result="outsideColor" flood-color="black"/><feMorphology in="SourceAlpha" operator="dilate" radius="2"/><feComposite result="strokeoutline1" in="outsideColor" operator="in"/><feComposite result="strokeoutline2" in="strokeoutline1" in2="SourceAlpha" operator="out"/><feGaussianBlur in="strokeoutline2" result="strokeblur" stdDeviation="1"/>
</filter>
<style>
.spawningMap-very-common { fill: #0F0;}
.spawningMap-common { fill: #B2FF00;}
.spawningMap-uncommon { fill: #FF0;}
.spawningMap-very-uncommon { fill: #FC0;}
.spawningMap-rare { fill: #F60;}
.spawningMap-very-rare { fill: #F00; }.spawning-map-point {stroke:black; stroke-width:1;}
</style>
</defs>
<g filter="url(#blur)" opacity="0.7">
<g class="spawningMap-rare"><rect x="183" y="155" width="12" height="11" /><rect x="79" y="168" width="12" height="11" /><rect x="79" y="180" width="12" height="12" /><rect x="79" y="193" width="12" height="11" /><rect x="105" y="168" width="12" height="11" /><rect x="92" y="168" width="12" height="11" /><rect x="92" y="180" width="12" height="12" /><rect x="92" y="193" width="12" height="11" /><rect x="131" y="142" width="12" height="12" /><rect x="118" y="155" width="12" height="11" /><rect x="118" y="180" width="12" height="12" /><rect x="131" y="193" width="12" height="11" /><rect x="118" y="193" width="12" height="11" /><rect x="157" y="142" width="12" height="12" /><rect x="144" y="142" width="12" height="12" /><rect x="157" y="180" width="12" height="12" /><rect x="144" y="193" width="12" height="11" /><rect x="183" y="142" width="12" height="12" /><rect x="170" y="142" width="12" height="12" /><rect x="170" y="155" width="12" height="11" /><rect x="183" y="168" width="12" height="11" /><rect x="170" y="168" width="12" height="11" /><rect x="196" y="142" width="11" height="12" /><rect x="196" y="155" width="11" height="11" />
</g>
<g class="spawningMap-very-uncommon"><rect x="260" y="142" width="12" height="12" /><rect x="260" y="155" width="12" height="11" /><rect x="260" y="168" width="12" height="11" /><rect x="247" y="168" width="12" height="11" /><rect x="273" y="142" width="12" height="12" /><rect x="53" y="193" width="12" height="11" /><rect x="66" y="193" width="12" height="11" /><rect x="66" y="206" width="12" height="11" /><rect x="79" y="206" width="12" height="11" /><rect x="79" y="218" width="12" height="12" /><rect x="66" y="218" width="12" height="12" /><rect x="92" y="206" width="12" height="11" /><rect x="105" y="206" width="12" height="11" /><rect x="118" y="206" width="12" height="11" /><rect x="131" y="206" width="12" height="11" /><rect x="118" y="218" width="12" height="12" /><rect x="157" y="193" width="12" height="11" /><rect x="144" y="206" width="12" height="11" /><rect x="157" y="206" width="12" height="11" /><rect x="157" y="218" width="12" height="12" /><rect x="170" y="180" width="12" height="12" /><rect x="183" y="218" width="12" height="12" /><rect x="170" y="218" width="12" height="12" /><rect x="196" y="180" width="11" height="12" /><rect x="196" y="193" width="11" height="11" /><rect x="196" y="206" width="11" height="11" /><rect x="196" y="218" width="11" height="12" /><rect x="221" y="142" width="12" height="12" /><rect x="221" y="155" width="12" height="11" /><rect x="234" y="168" width="12" height="11" />
</g>
<g class="spawningMap-very-common"><rect x="115" y="193" width="2" height="2" /><rect x="112" y="193" width="3" height="2" /><rect x="117" y="191" width="2" height="4" /><rect x="112" y="188" width="4" height="4" /><rect x="108" y="193" width="2" height="2" /><rect x="255" y="137" width="8" height="10" /><rect x="247" y="138" width="8" height="11" /><rect x="116" y="188" width="3" height="4" />
</g>
</g>
<g class="spawning-map-point" opacity="0.8">
</g>
<g fill="url(#pattern-untameable)" opacity="0.3"><rect x="115" y="193" width="2" height="2"/><rect x="112" y="193" width="3" height="2"/><rect x="117" y="191" width="2" height="4"/><rect x="112" y="188" width="4" height="4"/><rect x="108" y="193" width="2" height="2"/><rect x="255" y="137" width="8" height="10"/><rect x="247" y="138" width="8" height="11"/><rect x="116" y="188" width="3" height="4"/>
</g>
<g filter="url(#groupStroke)" opacity="0.8"><rect x="115" y="193" width="2" height="2"/><rect x="112" y="193" width="3" height="2"/><rect x="117" y="191" width="2" height="4"/><rect x="112" y="188" width="4" height="4"/><rect x="108" y="193" width="2" height="2"/><rect x="255" y="137" width="8" height="10"/><rect x="247" y="138" width="8" height="11"/><rect x="116" y="188" width="3" height="4"/>
</g>
</svg>

解决方案

问题根源

@napi-rs/canvas的loadImage方法对SVG的支持存在局限性,内部<style>标签定义的类样式、部分自定义滤镜和图案引用无法被正确解析渲染,导致样式丢失。

可行解决办法

  1. 内联SVG样式
    将<style>中的类样式直接写入对应SVG元素的style属性,比如把<g class="spawningMap-rare">改为<g style="fill: #F60;">,让每个元素的样式直接内嵌,避免依赖类选择器。

  2. 预渲染SVG为PNG
    先用浏览器或工具(如sharp、svg-to-png)将SVG转换成PNG格式,再用loadImage加载转换后的PNG,这样样式和滤镜会被完整保留。

  3. 直接加载SVG文本绘制
    先获取SVG的文本内容,使用@napi-rs/canvas的loadSvg方法(需版本支持)直接绘制到画布,示例代码:

    const axios = require('axios');
    // 获取SVG文本内容
    const svgText = await axios.get(output["map2"]).then(res => res.data);
    // 加载SVG并绘制
    const svgImage = await Canvas.loadSvg(svgText);
    context.drawImage(svgImage, 0, 0, canvas.width, canvas.height);
    

    若当前版本不支持loadSvg,可升级@napi-rs/canvas或使用svg2img等第三方库辅助转换。

  4. 简化SVG特性
    复杂自定义滤镜(如groupStroke)可能不被支持,尝试替换为基础SVG滤镜,或直接用Canvas API手动实现类似效果。


内容的提问来源于stack exchange,提问作者120 099 101 105 110 103

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 11:27:03