🙋 如何对一张人体图的不同肌肉群实现精准的上色处理 ❓
在这篇文章中会介绍在「web」和「微信小程序」两个场景的使用方式。原文可参考我的公众号文章《第一次觉得SVG如此优雅~》
首先想到的应该就是用 Canvas 了,最直接的方式就是:准备一张默认无色的人体肌肉图【default.png】,再准备每块肌肉群的点亮图片【chest.png,leg.png,arm.png, ... 】N 张;编码环节就是在【default.png】上,叠加已经点亮的某一块肌肉群图片。
不过,当想到要准备那么多张图片,还要考虑网络请求量,后期维护工作量,动态绘制的性能等,就会想问一句:还有更简单的方式吗?答案是肯定的,那就是用 SVG。
先看看在微信小程序上实现的效果:
SVG 的一点点了解
SVG(可缩放矢量图形)文件是一种基于 XML 的图形文件格式,用于描述二维矢量图形。
- SVG 是矢量图,文件小,可以任意缩放,不会失真
- SVG 可以嵌入到 html 中,也可以单独保存为文件
- 可以通过 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>
svg的妙用远不止于此,当然要有好的设计师配合,才能最大发挥它的强大能力。