阿里iconfont挂了之后...小程序项目内维护图标的方案

最近项目迭代要加些页面,在蓝湖上下好图标素材准备上传到阿里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换色

1
2
3
4
5
6
7
8
9
.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

1
2
<!--close-circle-->
<view wx:if="{{name === 'close-circle'}}" style="background-image: url({{quot}}data:image/svg+xml, %3Csvg viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='{{svgSize}}px' height='{{svgSize}}px'%3E%3Cpath d='' fill='{{(isStr ? colors : colors[0]) || 'rgb(51,51,51)'}}' /%3E%3C/svg%3E{{quot}}); width: {{svgSize}}px; height: {{svgSize}}px; " class="icon" />

有了思路就好说了,剩下的都是体力活,不过在开始编码之前先要明确一下需求。

需求

  1. 兼容项目之前的iconfont组件写法,最好能做到无痛平替。
  2. 兼容原生小程序和uniapp。
  3. 图标能传入高度(size),且能根据高度算出宽度,使其能保持宽高比。
  4. 传入颜色(color)时能改变图标颜色,不传入不改变颜色使其保持源图标源颜色。

编写过程

首先为了兼容原生小程序和uniapp,就必须得使用原生的小程序组件来写。在uniapp项目的src/wxcomponents下新建一个iconfont原生组件。
iShot_2022-06-23_16.49.12.png

上面的需求有几个prop参数,size默认给个18、color颜色、还有一个就是让组件做到用的是哪个图标的name。在data里需要一个变量svgStyle存处理好的backgroundImage字符串和宽高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Component({
properties: {
name: {
type: String
},
color: {
type: String
},
size: {
type: Number,
value: 18
}
},
data: {
svgStyle: ""
},
})

wxml里就很简单,放个容器标签就行

1
<view class="svg_icon" wx:if="{{svgStyle}}" style="{{svgStyle}};"></view>

wxss里设置组件host为行内元素就行

1
2
3
:host{
display: inline-block;
}

当这个组件加载时首先要通过小程序的api和图标name读取放在项目内的svg字符串,然后做一些处理。这里我把svg图标都放在uniapp项目下的src/static/icon中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
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("<svg.*?/svg>")[0];
} catch (e) {
console.error(e);
}
}
}
}

然后通过svg自带的宽高和传入的size得倒页面中实际的宽度,并替换掉源svg中的宽高属性。宽高一般在svg标签上,用正则提取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ...
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的时候不改颜色,这里要做一下判断处理。

1
2
3
4
5
6
7
8
9
// ...
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上。最后就大功告成了。

1
2
3
4
5
6
7
8
9
10
11
// ...
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在项目根目录下
    iShot_2022-06-23_17.29.54.png

下载好组件源码后,uniapp一定要放在src/wxcomponents里,原生无所谓。

想全局使用原生组件的话需要在page.json里注册组件
uniapp在globalStyleusingComponents注册

1
2
3
4
5
6
7
{
"globalStyle": {
"usingComponents": {
"iconfont": "/wxcomponents/iconfont/iconfont",
}
},
}

原生小程序直接在usingComponents注册

1
2
3
4
5
6
{
"usingComponents": {
"iconfont": "components/iconfont/iconfont"
},
}

最后在页面中使用就行

1
2
3
4
5
6
7
8
<view>
<iconfont
name="phone"
size="21"
color="#000000"
class="my-icon"
></iconfont>
</view>

需要注意的是size为px单位,如果是蓝湖750的设计稿需要除以2。

最后

一点想法

  • 小程序为什么这么麻烦?
  • 阿里iconfont不可靠,就算好了也不敢用了。

阿里iconfont挂了之后...小程序项目内维护图标的方案

https://www.yw3.fun/article/df8c4804f9cb.html

作者

全肝鸽鸽

发布于

2022-11-17

更新于

2024-04-30

许可协议

评论