SVG这么用真方便啊~

1,013 阅读3分钟

🙋 如何对一张人体图的不同肌肉群实现精准的上色处理 ❓

在这篇文章中会介绍在「web」和「微信小程序」两个场景的使用方式。原文可参考我的公众号文章《第一次觉得SVG如此优雅~

default.png

首先想到的应该就是用 Canvas 了,最直接的方式就是:准备一张默认无色的人体肌肉图【default.png】,再准备每块肌肉群的点亮图片【chest.png,leg.png,arm.png, ... 】N 张;编码环节就是在【default.png】上,叠加已经点亮的某一块肌肉群图片。

不过,当想到要准备那么多张图片,还要考虑网络请求量,后期维护工作量,动态绘制的性能等,就会想问一句:还有更简单的方式吗?答案是肯定的,那就是用 SVG。

先看看在微信小程序上实现的效果:

2024-04-19 15.20.44.gif

SVG 的一点点了解

SVG(可缩放矢量图形)文件是一种基于 XML 的图形文件格式,用于描述二维矢量图形。

  1. SVG 是矢量图,文件小,可以任意缩放,不会失真
  2. SVG 可以嵌入到 html 中,也可以单独保存为文件
  3. 可以通过 js 操作 SVG 的标签,设置样式

在 HTML 中使用 SVG

准备一个 SVG 文件:

设计给的 SVG 文件,内部 xml 结构的节点上没有包含方便我们操作的 id或者class 属性,我们可以在 SVG 文件中手动添加。就像写html 一样,可以添加 style 和 给元素增加 id 或者 class

<?xml version="1.0" encoding="utf-8"?>
<svg
  version="1.1"
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  x="0px"
  y="0px"
  viewBox="0 0 740 768"
  style="enable-background:new 0 0 740 768;"
  xml:space="preserve"
>
  <style type="text/css">
    g {
      fill: #ffffff;
    }
  </style>
  <g id="胸">
    <path class="st0" d="M181.13..." />
  </g>
  <g id="背">
    <path class="st0" d="M181.13..." />
  </g>
  ...
</svg>

1.引入

  • <img /> 标签可以引入 SVG 用于静态展示,但是如果想要去操作 SVG 的话,需要使用 <object><iframe> 的方式;
<body>
  <object id="svg_object" data="./body.svg" type="image/svg+xml"></object>
</body>

2.操作

拿到 SVG 的 contentDocument 后,接下来就有点像操作 html 了,.querySelector 选中节点元素,.style. 设置节点样式。

<script>
    const parts = ['背', '胸'];
    const svgObject = document.getElementById('svg_object');
    const svgDocument = svgObject.contentDocument;

    function setGroupPathStyle(selector, color) {
        const group = svgDocument.querySelector(selector);
        // group.style.fill = 'yellow';// 无法覆盖其内部path的颜色
        if (!group) {
            return;
        }

        // 获取 <g> 元素内所有 <path> 元素
        const paths = group.getElementsByTagName('path');

        // 设置所有 <path> 元素的颜色为红色
        for (let i = 0; i < paths.length; i++) {
            paths[i].style.fill = color;
        }
    }

    svgObject.addEventListener('load', async function () {
        run();
    });

    async function run() {
        for (let i = 0; i < parts.length; i++) {
            setGroupPathStyle('#' + parts[i], '#fdd000')
        }
    }
</script>

在 微信小程序 中使用 SVG ❗️

微信小程序中,使用 SVG 的方式,需要先将 SVG 文件转成URI资源,再通过 <image> 标签引入。

因此,也就丧失基于 contentDocument 提供的操作能力了。但是也不是完全没有办法,我们可以用正则表达式,去替换SVG的URL编码中代表颜色的字符串,达到修改样式的效果。

具体可以采用两种思路,但是理念都是一样的:操作字符串。

  • 1.整体设置

如果是对整体的样式,比如颜色的修改,那么直接替换 uriSVG 中代表颜色的字符串即可。

代码演示:

let svgXML = `<?xml version="1.0" encoding="utf-8"?>...</svg>`;

let uriSVG = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
  svgXML
)}`;

// 把“ffffff”替换为“fdd000”
uriSVG = uriSVG.replace(/ffffff/, "fdd000");
this.setData({
  uriSVG,
});
  • 2.局部设置

如果是局部设置,比如我们要设置每个肌肉群显示不同的颜色,那么可以替换<g>id或者class属性,达到切换样式的效果。

前提是我们先对 SVG 文件进行预处理,给每个肌肉群添加上id或者class属性,并在style里写好对应点亮后的样式,如下:

<?xml version="1.0" encoding="utf-8"?>
<svg
  version="1.1"
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  x="0px"
  y="0px"
  viewBox="0 0 740 768"
  style="enable-background:new 0 0 740 768;"
  xml:space="preserve"
>
  <style type="text/css">
    g {
      fill: #ffffff;
    }
    #胸_1 {
      fill: #fdd000;
    }
    #背_1 {
      fill: #fdd000;
    }
  </style>
  <g id="胸">
    <path class="st0" d="M181.13..." />
  </g>
  <g id="背">
    <path class="st0" d="M181.13..." />
  </g>
  ...
</svg>

设置了#胸_1#背_1 两个样式,给 <g> 设置了唯一的 id 属性。

代码演示:

let svgXML = `<?xml version="1.0" encoding="utf-8"?>...</svg>`;

let uriSVG = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
  svgXML
)}`;

// 把“背”替换为“背_1”
let from = encodeURIComponent("背");
let to = encodeURIComponent("背_1");

uriSVG = uriSVG.replace(from, to);
this.setData({
  uriSVG,
});

wxml 里:

<view class="body_muscle_box" style="background-color: {{bgColor}};">
  <image style="width: 100%;" src="{{uriSVG}}" mode="widthFix" />
</view>

back_active.png

svg的妙用远不止于此,当然要有好的设计师配合,才能最大发挥它的强大能力。