处理SVG path d路径数据
本帖最后由 马黑黑 于 2025-11-11 00:11 编辑 <br /><br /><style>.mainBox { margin: 0 auto; padding: 0 20px 20px 20px; height: 95vh; display: flex; flex-direction: column; background: linear-gradient(to right top, beige, silver); border-radius: 10px;}
#pathBox, #resultBox { width: 100%; min-height: 100px; padding: 10px; tab-size: 4; box-sizing: border-box; resize: none; }
#resultBox { height: 100%; }
#msvg { width: 100%; height: 100%; }
.mainBox button { margin-left: 16px; }
.mainBox h2 { margin: 16px; }
.mainBox p { padding-top: 20px; }
.tMid { text-align: center; }
.tRight { text-align: right; }
.item { width: 100%; height: 100%; }
.item1 { flex: 2; }
.item2 { flex: 4; }
.item3 { flex: 1; }
</style>
<div class="mainBox">
<div class="item item1">
<h2 class="tMid">处理SVG path d路径数据</h2>
<textarea id="pathBox" placeholder="输入 d 路径数据">M0 0 C-100 100,100 100,0 0</textarea>
<p class="tRight">
<input id="cbFill" type="checkbox" name="chkbox" value="fill" checked />
<label for="cbFill">填充</label>
<input id="cbStroke" type="checkbox" name="chkbox" value="stroke" />
<label for="cbStroke">描边</label>
<button id="btnBegin" type="button" value="btn">生成SVG图像</button>
<button id="btnGetCode" type="button">获取SVG代码</button>
</p>
</div>
<div class="item item2">
<svg id="msvg">
<symbol id="symbol1">
<path id="path1" />
</symbol>
<use id="use1" x="0" y="0" width="200" height="200" href="#symbol1" />
</svg>
</div>
<div class="item item3">
<textarea id="resultBox" placeholder="信息区" readonly></textarea>
</div>
</div>
<script type="text/javascript">
const ns = 'http://www.w3.org/2000/svg';
const cbFill = document.getElementById('cbFill');
const cbStroke = document.getElementById('cbStroke');
const use = document.getElementById('use1');
const sym = document.getElementById('symbol1');
const path = document.getElementById('path1');
const strokeColor = 'magenta';
const fillColor = 'plum';
cbFill.onclick = () => {
use.setAttribute('fill', cbFill.checked ? fillColor : 'none');
};
cbStroke.onclick = () => {
use.setAttribute('stroke', cbStroke.checked ? 'magenta' : 'none');
};
btnBegin.onclick = () => {
const pathData = pathBox.value.trim();
if (isInvalidD(pathData)) {
resultBox.value = '路径不合法,请检查好后再试!';
return;
}
const { x, y, width, height } = getVBoxFromPath(pathData);
sym.setAttribute('viewBox', `${Math.ceil(x - 2)} ${Math.ceil(y - 2)} ${Math.ceil(width + 4)} ${Math.ceil(height + 4)}`);
resultBox.value = `viewBox(${Math.round(x)} ${Math.round(y)} ${Math.round(width)} ${Math.round(height)})`
path.setAttribute('d', pathData);
if (cbFill.checked) use.setAttribute('fill', fillColor);
if (cbStroke.checked) use.setAttribute('stroke', strokeColor);
};
btnGetCode.onclick = () => {
resultBox.value = msvg.outerHTML;
}
function getVBoxFromPath(pathstr) {
const tsvg = document.createElementNS(ns, 'svg');
tsvg.setAttribute('width', '0');
tsvg.setAttribute('height', '0');
const path = document.createElementNS(ns, 'path');
path.setAttribute('d', pathstr);
tsvg.appendChild(path);
document.body.appendChild(tsvg);
const resultData = path.getBBox();
document.body.removeChild(tsvg);
return resultData;
}
function isInvalidD(s) {
const reEverythingAllowed = //g;
const bContainsIllegalCharacter = !!s.replace(reEverythingAllowed,'').length;
const bContainsAdjacentLetters = //.test(s);
const bInvalidStart = /^/.test(s);
const bInvalidEnd = /.*[-,.]$/.test(s.trim());
return bContainsIllegalCharacter || bContainsAdjacentLetters || bInvalidStart || bInvalidEnd;
}
</script> 有时候拿到一个 path 路径数据却不知道如何将其绘制到SVG画布上。主要原因是,我们很难从路径中分析它基于什么尺寸进行设计。
本 Demo,在 0*0 的临时SVG画布上先测试路径的占位,使用 getBbox 获得路径在画布上的宽高占位,获取一个合理的 viewBox 属性待用;接着将路径置入一个 symbol 标签,将获得的 viewBox 属性赋予它;最后,以 use 标签实例化symbol,画出路径图像。
点击获取SVG代码按钮,在最后一个文本框可以生成完整的SVG源码,得到源码后可根据需要修改 use 标签的位置、尺寸、着色以及描边等数据。 本帖最后由 马黑黑 于 2025-11-10 23:23 编辑
一楼源码共参考:
<style>
.mainBox { margin: 0 auto; padding: 0 20px 20px 20px; height: 95vh; display: flex; flex-direction: column; background: linear-gradient(to right top, beige, silver); border-radius: 10px;}
#pathBox, #resultBox { width: 100%; min-height: 100px; padding: 10px; tab-size: 4; box-sizing: border-box; resize: none; }
#resultBox { height: 100%; }
#msvg { width: 100%; height: 100%; }
.mainBox button { margin-left: 16px; }
.tMid { text-align: center; }
.tRight { text-align: right; }
.item { width: 100%; height: 100%; }
.item1 { flex: 2; }
.item2 { flex: 4; }
.item3 { flex: 1; }
</style>
<div class="mainBox">
<div class="item item1">
<h2 class="tMid">处理SVG path d路径数据</h2>
<textarea id="pathBox" placeholder="输入 d 路径数据">M0 0 C-100 100,100 100,0 0</textarea>
<p class="tRight">
<input id="cbFill" type="checkbox" name="chkbox" value="fill" checked />
<label for="cbFill">填充</label>
<input id="cbStroke" type="checkbox" name="chkbox" value="stroke" />
<label for="cbStroke">描边</label>
<button id="btnBegin" type="button" value="btn">生成SVG图像</button>
<button id="btnGetCode" type="button">获取SVG代码</button>
</p>
</div>
<div class="item item2">
<svg id="msvg">
<symbol id="symbol1">
<path id="path1" />
</symbol>
<use id="use1" x="0" y="0" width="200" height="200" href="#symbol1" />
</svg>
</div>
<div class="item item3">
<textarea id="resultBox" placeholder="信息区" readonly></textarea>
</div>
</div>
<script type="text/javascript">
const ns = 'http://www.w3.org/2000/svg';
const cbFill = document.getElementById('cbFill');
const cbStroke = document.getElementById('cbStroke');
const use = document.getElementById('use1');
const sym = document.getElementById('symbol1');
const path = document.getElementById('path1');
const strokeColor = 'magenta';
const fillColor = 'plum';
cbFill.onclick = () => {
use.setAttribute('fill', cbFill.checked ? fillColor : 'none');
};
cbStroke.onclick = () => {
use.setAttribute('stroke', cbStroke.checked ? 'magenta' : 'none');
};
btnBegin.onclick = () => {
const pathData = pathBox.value.trim();
if (isInvalidD(pathData)) {
resultBox.value = '路径不合法,请检查好后再试!';
return;
}
const { x, y, width, height } = getVBoxFromPath(pathData);
sym.setAttribute('viewBox', `${Math.ceil(x - 2)} ${Math.ceil(y - 2)} ${Math.ceil(width + 4)} ${Math.ceil(height + 4)}`);
resultBox.value = `viewBox(${Math.round(x)} ${Math.round(y)} ${Math.round(width)} ${Math.round(height)})`
path.setAttribute('d', pathData);
if (cbFill.checked) use.setAttribute('fill', fillColor);
if (cbStroke.checked) use.setAttribute('stroke', strokeColor);
};
btnGetCode.onclick = () => {
resultBox.value = msvg.outerHTML;
}
function getVBoxFromPath(pathstr) {
const tsvg = document.createElementNS(ns, 'svg');
tsvg.setAttribute('width', '0');
tsvg.setAttribute('height', '0');
const path = document.createElementNS(ns, 'path');
path.setAttribute('d', pathstr);
tsvg.appendChild(path);
document.body.appendChild(tsvg);
const resultData = path.getBBox();
document.body.removeChild(tsvg);
return resultData;
}
function isInvalidD(s) {
const reEverythingAllowed = //g;
const bContainsIllegalCharacter = !!s.replace(reEverythingAllowed,'').length;
const bContainsAdjacentLetters = //.test(s);
const bInvalidStart = /^/.test(s);
const bInvalidEnd = /.*[-,.]$/.test(s.trim());
return bContainsIllegalCharacter || bContainsAdjacentLetters || bInvalidStart || bInvalidEnd;
}
</script>
提供一个较长的路径:
M 95 65 l -35 35 l -35 -35 a 25 25 90 0 1 35 -36 a 25 25 90 0 1 35 36 l -35 33 l 38 -37 l -39 36 l 41 -40 l -42 39 l 43 -42 l -44 41 l 45 -44 l -46 43 l 46 -45 l -47 44 l 47 -46 l -48 45 l 48 -47 l -49 46 l 49 -48 l -50 47 l 49 -48 l -50 47 l 50 -49 l -51 48 l 50 -49 l -51 48 l 51 -50 l -52 49 l 51 -50 l -52 49 l 51 -50 l -52 49 l 52 -51 l -53 50 l 52 -51 l -53 50 l 52 -51 l -53 50 l 52 -51 l -53 50 l 52 -51 l -53 50 l 52 -51 l -53 50 l 52 -51 l -53 50 l 51 -50 l -52 49 l 51 -50 l -52 49 l 51 -50 l -52 49 l 51 -50 l -52 49 l 50 -49 l -51 48 l 50 -49 l -51 48 l 49 -48 l -50 47 l 48 -47 l -49 46 l 47 -46 l -48 45 l 46 -45 l -47 44 l 45 -44 l -46 43 l 44 -43 l -45 42 l 42 -41 l -43 40 l 39 -38 l -40 37 l 35 -34 l -36 33 l 34 -33 l -35 32 l 34 -33 l -35 32 l 34 -33 l -35 32 l 34 -33 l -35 32 l 34 -33 l -34 31 l 32 -31 l -33 30 l 32 -31 l -32 29 l 30 -29 l -30 27 l 29 -28 l -30 27 l 28 -27 l -28 25 l 26 -25 l -26 23 l 24 -23 l -24 21 l 22 -21 l -21 18 l 19 -18 l -19 16 l 16 -15 l -15 13 l 13 -13 l -11 9 l 7 -7z 黑黑老师辛苦了!{:4_190:} 梦江南 发表于 2025-11-10 19:16
黑黑老师辛苦了!
{:4_180:} 哇,马老师威武!让抽象的SVG path d路径数据实现了可视化,并提供了新的抓手,可操作性大大增强{:4_180:} 马黑黑 发表于 2025-11-10 18:44
提供一个较长的路径:
M 95 65 l -35 35 l -35 -35 a 25 25 90 0 1 35 -36 a 25 25 90 0 1 35 36 l -35...
一颗红心有这么长的代码哦 黑黑现在是都是精品展示了{:4_170:} 这个好,可以让svg 路径展现为可见的图像,还能知道路径的宽高占位,让svg绘图学习更直观了{:4_199:} 小辣椒 发表于 2025-11-10 22:12
黑黑现在是都是精品展示了
果酱果酱 小辣椒 发表于 2025-11-10 22:11
一颗红心有这么长的代码哦
更长的都还有 杨帆 发表于 2025-11-10 20:53
哇,马老师威武!让抽象的SVG path d路径数据实现了可视化,并提供了新的抓手,可操作性大大增强
{:4_190:} 红影 发表于 2025-11-10 22:33
这个好,可以让svg 路径展现为可见的图像,还能知道路径的宽高占位,让svg绘图学习更直观了
刚刚做了一些改进 马黑黑 发表于 2025-11-10 23:24
刚刚做了一些改进
嗯,增加了填充和描边的选择{:4_187:} 红影 发表于 2025-11-10 23:58
嗯,增加了填充和描边的选择
内部还加入了一个 d 属性值合法与否的正则检测机制,初筛一下非法属性值 马黑黑 发表于 2025-11-11 11:56
内部还加入了一个 d 属性值合法与否的正则检测机制,初筛一下非法属性值
原来加了这么多呢{:4_187:} 红影 发表于 2025-11-12 22:12
原来加了这么多呢
这东东看似简单,其实坑很多也很深。网上搜了一下,这类需求还不少,一直是个痛点。如果不借助第三方专业工具,目前的解决思路主要两种:
一是保留原始 d 数据,通过设置使用者 viewBox 去适配它。这是一楼的做法;
二是修改 d 数据,令其适配视框。这需要知道很多已知条件,比如原始 viewBox,这个最难,一般来说仅根据一个 d 数据分析不出来,但对修改尺寸而言这又不是问题(比方讲是自己原来做的路径,0 0 400 400,现在像压缩或放大路径);另外实际修改 d 数据时,考虑的因素也很多,不同的 SVG 指令解析的逻辑和方式不同,需要一一处理,条件分支不少。可以做出来,就是没有方法一那么灵便。 马黑黑 发表于 2025-11-12 23:03
这东东看似简单,其实坑很多也很深。网上搜了一下,这类需求还不少,一直是个痛点。如果不借助第三方专业 ...
玩得越是深入,需求也就越多。黑黑探索过了,我们只跟后面听讲就可以了,现在知道了使用者 viewBox 去适配是最灵便的{:4_187:} 红影 发表于 2025-11-14 22:12
玩得越是深入,需求也就越多。黑黑探索过了,我们只跟后面听讲就可以了,现在知道了使用者 viewBox 去适 ...
Kimi给我详尽介绍过方法二,但它的代码很粗糙、经不起考验,需要大量修改。
Kimi可能更擅长做文案,据称,它理解长文的能力超常,百度的问心未必是它的对手。
页:
[1]
2