马黑黑 发表于 2025-11-10 18:17

处理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>

马黑黑 发表于 2025-11-10 18:29

有时候拿到一个 path 路径数据却不知道如何将其绘制到SVG画布上。主要原因是,我们很难从路径中分析它基于什么尺寸进行设计。

本 Demo,在 0*0 的临时SVG画布上先测试路径的占位,使用 getBbox 获得路径在画布上的宽高占位,获取一个合理的 viewBox 属性待用;接着将路径置入一个 symbol 标签,将获得的 viewBox 属性赋予它;最后,以 use 标签实例化symbol,画出路径图像。

点击获取SVG代码按钮,在最后一个文本框可以生成完整的SVG源码,得到源码后可根据需要修改 use 标签的位置、尺寸、着色以及描边等数据。

马黑黑 发表于 2025-11-10 18:29

本帖最后由 马黑黑 于 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>

马黑黑 发表于 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 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

梦江南 发表于 2025-11-10 19:16

黑黑老师辛苦了!{:4_190:}

马黑黑 发表于 2025-11-10 19:35

梦江南 发表于 2025-11-10 19:16
黑黑老师辛苦了!

{:4_180:}

杨帆 发表于 2025-11-10 20:53

哇,马老师威武!让抽象的SVG path d路径数据实现了可视化,并提供了新的抓手,可操作性大大增强{:4_180:}

小辣椒 发表于 2025-11-10 22:11

马黑黑 发表于 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...

一颗红心有这么长的代码哦

小辣椒 发表于 2025-11-10 22:12

黑黑现在是都是精品展示了{:4_170:}

红影 发表于 2025-11-10 22:33

这个好,可以让svg 路径展现为可见的图像,还能知道路径的宽高占位,让svg绘图学习更直观了{:4_199:}

马黑黑 发表于 2025-11-10 23:20

小辣椒 发表于 2025-11-10 22:12
黑黑现在是都是精品展示了

果酱果酱

马黑黑 发表于 2025-11-10 23:20

小辣椒 发表于 2025-11-10 22:11
一颗红心有这么长的代码哦

更长的都还有

马黑黑 发表于 2025-11-10 23:21

杨帆 发表于 2025-11-10 20:53
哇,马老师威武!让抽象的SVG path d路径数据实现了可视化,并提供了新的抓手,可操作性大大增强

{:4_190:}

马黑黑 发表于 2025-11-10 23:24

红影 发表于 2025-11-10 22:33
这个好,可以让svg 路径展现为可见的图像,还能知道路径的宽高占位,让svg绘图学习更直观了

刚刚做了一些改进

红影 发表于 2025-11-10 23:58

马黑黑 发表于 2025-11-10 23:24
刚刚做了一些改进

嗯,增加了填充和描边的选择{:4_187:}

马黑黑 发表于 2025-11-11 11:56

红影 发表于 2025-11-10 23:58
嗯,增加了填充和描边的选择
内部还加入了一个 d 属性值合法与否的正则检测机制,初筛一下非法属性值

红影 发表于 2025-11-12 22:12

马黑黑 发表于 2025-11-11 11:56
内部还加入了一个 d 属性值合法与否的正则检测机制,初筛一下非法属性值

原来加了这么多呢{:4_187:}

马黑黑 发表于 2025-11-12 23:03

红影 发表于 2025-11-12 22:12
原来加了这么多呢

这东东看似简单,其实坑很多也很深。网上搜了一下,这类需求还不少,一直是个痛点。如果不借助第三方专业工具,目前的解决思路主要两种:

一是保留原始 d 数据,通过设置使用者 viewBox 去适配它。这是一楼的做法;

二是修改 d 数据,令其适配视框。这需要知道很多已知条件,比如原始 viewBox,这个最难,一般来说仅根据一个 d 数据分析不出来,但对修改尺寸而言这又不是问题(比方讲是自己原来做的路径,0 0 400 400,现在像压缩或放大路径);另外实际修改 d 数据时,考虑的因素也很多,不同的 SVG 指令解析的逻辑和方式不同,需要一一处理,条件分支不少。可以做出来,就是没有方法一那么灵便。

红影 发表于 2025-11-14 22:12

马黑黑 发表于 2025-11-12 23:03
这东东看似简单,其实坑很多也很深。网上搜了一下,这类需求还不少,一直是个痛点。如果不借助第三方专业 ...

玩得越是深入,需求也就越多。黑黑探索过了,我们只跟后面听讲就可以了,现在知道了使用者 viewBox 去适配是最灵便的{:4_187:}

马黑黑 发表于 2025-11-15 11:23

红影 发表于 2025-11-14 22:12
玩得越是深入,需求也就越多。黑黑探索过了,我们只跟后面听讲就可以了,现在知道了使用者 viewBox 去适 ...
Kimi给我详尽介绍过方法二,但它的代码很粗糙、经不起考验,需要大量修改。

Kimi可能更擅长做文案,据称,它理解长文的能力超常,百度的问心未必是它的对手。
页: [1] 2
查看完整版本: 处理SVG path d路径数据