最近项目迭代要加些页面,在蓝湖上下好图标素材准备上传到阿里iconfont上去。打开网站一看,好家伙,维护中...
使用方法和源码可以直接跳到 我的方法-使用。
前言
但我还是菜鸟的时候,公司的老前端和我说,我挂了阿里都不会挂,叫我放心用阿里的iconfont。现在阿里挂了,图标没法换,项目没发上线,要是被老板发现了,我也要一起挂了。
怎么办?突然想到了后台管理项目的图标管理方案,使用svg雪碧图。可是看了一下小程序的文档,小程序不支持svg标签,只能使用image载入src,这样的话就不能改颜色了...
这可不行,于是百度了一下,找到了2种方法。
百度来的方法
drop-shadow
使用css的drop-shadow
属性可以让我们给一个元素添加长得一摸一样的阴影,给阴影设置个颜色,把源元素隐藏起来只显示阴影不就达到换色的效果了吗。
emmm,试了一下,在开发者工具上果然可以,拿起iphone调试一看,一片空白....
为什么呢?搜索了一下社区,原来在ios上,如果把源元素隐藏了,阴影也不会被渲染。还好底下评论有解决方案,给加一个transform:translateZ(0);
强制gpu渲染。于是又试了一下,效果也不太理想,ios上有的图标出来了,有的出不来,android上是彻底出不来了。
那只能换个思路,源元素隐藏阴影出不来,这样的话网上有人通过设置border-right
使源元素始终显示,这样行不行呢?试了一下,android正常了,ios还是上一个方法的样子,有的出的来,有的出不来。排查了一下这些图标,出的来的都是线条贴边占满整个svg的图标,现在我又不能控制ui提供svg是啥样,设置阴影换颜色的方法只能放弃了。
mask
css里可以给元素设置一个mask然后改变背景颜色可以曲线实现svg换色
.colorful {
display: inline-block;
width: 32px; height: 32px;
background-color: #f4615c;
-webkit-mask: url(./xin.svg) no-repeat;
mask: url(./xin.svg) no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
}
实际试了一下,还是老样子,android可以,ios不行。
我的方法
这下就难住了,真的要提桶跑路了吗。绝望的时候看了一下之前的小程序使用阿里iconfont方案mini-program-iconfont-cli,发现作者的这个方案其实就是拉取存在阿里iconfont的svg,读取修改svg的字符串,通过组件传入颜色用js改变svg字符串的颜色属性,最后内联到标签的background-image
上
有了思路就好说了,剩下的都是体力活,不过在开始编码之前先要明确一下需求。
需求
- 兼容项目之前的iconfont组件写法,最好能做到无痛平替。
- 兼容原生小程序和uniapp。
- 图标能传入高度(size),且能根据高度算出宽度,使其能保持宽高比。
- 传入颜色(color)时能改变图标颜色,不传入不改变颜色使其保持源图标源颜色。
编写过程
首先为了兼容原生小程序和uniapp,就必须得使用原生的小程序组件来写。在uniapp项目的src/wxcomponents
下新建一个iconfont原生组件。
上面的需求有几个prop参数,size默认给个18、color颜色、还有一个就是让组件做到用的是哪个图标的name。在data里需要一个变量svgStyle存处理好的backgroundImage
字符串和宽高。
Component({
properties: {
name: {
type: String
},
color: {
type: String
},
size: {
type: Number,
value: 18
}
},
data: {
svgStyle: ""
},
})
wxml里就很简单,放个容器标签就行
wxss里设置组件host为行内元素就行
:host{
display: inline-block;
}
当这个组件加载时首先要通过小程序的api和图标name读取放在项目内的svg字符串,然后做一些处理。这里我把svg图标都放在uniapp项目下的src/static/icon
中。
{
lifetimes: {
attached() {
const fs = wx.getFileSystemManager();
try {
const res = fs.readFileSync(
`/static/icon/${this.data.name}.svg`,
"utf8",
0
);
// 读取到之后需要删掉换行符避免报错,且只提取svg标签部分内容
const svgStr = res.replaceAll("\n", "").match("")[0];
} catch (e) {
console.error(e);
}
}
}
}
然后通过svg自带的宽高和传入的size得倒页面中实际的宽度,并替换掉源svg中的宽高属性。宽高一般在svg标签上,用正则提取。
// ...
const divideIndex = svgStr.indexOf(">");
let firstStr = svgStr.slice(0, divideIndex);
let otherStr = svgStr.slice(divideIndex);
// 根据size 修改height width
const [heightString, heightValue] = firstStr.match(
/height="(\d+(\.\d*|))(?:px|)"/
);
const [widthString, widthValue] = firstStr.match(
/width="(\d+(\.\d*|))(?:px|)"/
);
const finalHeight = this.data.size;
const finalWidth = (widthValue / heightValue) * finalHeight;
firstStr = firstStr
.replace(widthString, `width="${finalWidth}px"`)
.replace(heightString, `height="${finalHeight}px"`);
接下来需要处理svg的颜色。svg的颜色属性fill一般放在svg标签上,如果有多个路径,可能也会在别的标签上,为了保险起见,我们都替换掉。而且还要移除一些影响展示效果的属性,如opacity什么的。根据需求不传color的时候不改颜色,这里要做一下判断处理。
// ...
if (this.data.color) {
firstStr += ` fill="${this.data.color}"`;
otherStr = otherStr.replaceAll(
/fill="(.*?)"/g,
`fill="${this.data.color}"`
);
otherStr = otherStr.replaceAll(/opacity="(.*?)"/g, "");
}
最后需要把标签转译一下放到backgound-image
里,并加上宽高苏醒设置到svgStyle
上。最后就大功告成了。
// ...
let clStr = (firstStr + otherStr)
.replaceAll("<", "%3C")
.replaceAll(">", "%3E")
.replaceAll('"', `'`)
.replaceAll("#", "%23");
const resultStr = `background-image:url("data:image/svg+xml, ${clStr}");width:${finalWidth}px;height:${finalHeight}px`;
this.setData({
svgStyle: resultStr
});
使用
源码地址 mp-svg-icon
首先将图标放在static目录的img文件夹下
- unapp 的static在src下
-
原生小程序的static在项目根目录下
下载好组件源码后,uniapp一定要放在src/wxcomponents
里,原生无所谓。
想全局使用原生组件的话需要在page.json里注册组件
uniapp在globalStyle
的usingComponents
注册
{
"globalStyle": {
"usingComponents": {
"iconfont": "/wxcomponents/iconfont/iconfont",
}
},
}
原生小程序直接在usingComponents
注册
{
"usingComponents": {
"iconfont": "components/iconfont/iconfont"
},
}
最后在页面中使用就行
需要注意的是size为px单位,如果是蓝湖750的设计稿需要除以2。
最后
一点想法
- 小程序为什么这么麻烦?
- 阿里iconfont不可靠,就算好了也不敢用了。
文章来源:阿里iconfont挂了之后...小程序项目内维护图标的方案 - Devin's Blog (yw3.fun)