背景
在微信小程序开发过程中难免会遇到需要使用多色icon的场景,项目中的icon一般存放在iconfont上。
iconfont有三种引用方式(参考https://www.iconfont.cn/help/detail?spm=a313x.7781069.1998910419.d8cf4382a&helptype=code)
- unicode引用:因为是字体,所以不支持多色
- font-class引用:本质上还是使用的字体,所以多色图标还是不支持的
- symbol引用:支持多色,使用svg渲染
但是微信小程序iconfont并不支持<svg>
标签,只支持以background形式渲染.svg文件。基于此,可以使用Node编写脚本,将iconfont项目里的多色icon下载到项目中,生成
.arrow-down--colorful {
background: url('./arrow-down.[hash].svg');
}
// 或者有CDN的可以
.arrow-down--colorful {
background: url('//[cdn]/imgs/arrow-down.[hash].svg');
}
步骤
步骤一、 上传icon至iconfont
注意在上传时,带颜色的图标需要有固定后缀来区分。例如普通icon为icon-name,则带颜色的icon需要为icon-name--colorful
步骤二、脚本下载icon_**.css
使用Node下载项目对应的css脚本,格式为:
@font-face {font-family: "iconfont";
src: url('//at.alicdn.com/t/font_**.eot?t=1571134856668');
src: url('//at.alicdn.com/t/font_385238_**.eot?t=1571134856668#iefix') format('embedded-opentype'),
url('data:application/x-font-woff2;**') format('woff2'),
url('//at.alicdn.com/t/font_**.woff?t=**') format('woff'),
url('//at.alicdn.com/t/font_**.ttf?t=**') format('truetype'),
url('//at.alicdn.com/t/font_385238_**.svg?t=**#iconfont') format('svg');
}
.iconfont {
font-family: "iconfont" !important;
...
}
.icon-name:before {
content: "\e600";
}
.icon-name--colorful:before {
content: "\e653";
}
删除其中无用的font格式,以及带--colorful的css定义。使用脚本将其格式化为
@font-face {font-family: "iconfont";
src: url('data:application/x-font-woff2;**') format('woff2');
}
.iconfont {
font-family: "iconfont" !important;
...
}
.icon-name:before {
content: "\e600";
}
步骤三、脚本下载icon_**.js
使用Node下载项目对应的js脚本,格式为:
!function(o){var t,l='<svg><symbol id="icon-name” viewBox="0 0 1024 1024"><path …>…</path></symbol>symbol id="icon-name—colorful” viewBox="0 0 1024 1024"><path …>…</path></symbol></svg>’,…}(window);
提取出其中带颜色(id以--colorful结尾)的<symbol></symbol>标签,使用xml解析工具,得到
<path d="..." fill="..." ></path><path d="..." fill="..." ></path>
注意:如果步骤一不能实现按照--colorful区分是否是多色icon,则需要分析各个path的颜色是否一致来区分是否是多色。
拼接上<svg></svg>
标签
function getHash(cont: string) {
return cryptoNode
.createHash('md5')
.update(cont)
.digest('hex')
.substr(0, 8);
}
步骤四、生成hash值及图片文件
最终得到的svgStr的hash值
function getHash(cont: string) {
return cryptoNode
.createHash('md5')
.update(cont)
.digest('hex')
.substr(0, 8);
}
使用hash值是为了避免更换icon但是不换名字的场景
生成本地图片文件
const hash = getHash(svgStr);
const fileName = `${iconId}.${hash}.svg`;
const filePath = `${stylePath}${fileName}`;
fs.writeFileSync(filePath, svgStr);
步骤五、将图片上传至CDN并删除本地文件(可选)
如果有CDN资源可以将图片上传至CDN,可以节约打包出的项目体积
uploadFileToCDN(filePath);
/** 将pathWithHash文件上传到CDN */
async function uploadFileToCDN(pathWithHash: string) {
return new Promise((resolve, reject) => {
exec(
`${此处为上传至CDN的命令行}`,
(error: any, stdout: any, stderr: any) => {
if (error) {
console.error(`exec error: ${error}`);
reject(error);
return;
}
resolve('');
/** 删除文件 */
fs.unlinkSync(pathWithHash);
}
);
});
}
步骤六、生成完整的css内容并写入本地
给步骤二的css文件拼接上带颜色图片的css内容
@font-face {font-family: "iconfont";
src: url('data:application/x-font-woff2;**') format('woff2');
}
.iconfont {
font-family: "iconfont" !important;
...
}
.icon-name:before {
content: "\e600";
}
.icon-name--colorful:before {
background: url('cdnUrl');
}
并写入本地
fs.writeFileSync(cssPath, fileContent.replace(/"/g, "'"), 'utf8');
完整的脚本文件示例
const http = require('http');
const cryptoNode = require('crypto');
const { exec } = require('child_process');
const xml2js = require('xml2js');
const fs = require('fs');
const config = {
url: '//at.alicdn.com/t/font_***.js'
};
// 上传至CDN后的前缀
const cdnPath = '***';
/** 生成的css文件path */
const cssPath = 'src/styles/iconfont.scss';
const stylePath = 'src/styles/';
const { parseString } = xml2js;
// 替换iconfont.scss
const iconfontUrl = config.url.replace(/.*\/\//, 'http://');
const iconfontUrl_css = iconfontUrl.replace('.js', '.css');
let fileContent = '';
type IPath = {
$: {
[key: string]: string;
};
};
type ISymbol = {
$: {
id: string;
viewBox: string;
};
path: IPath[];
};
/** 读取css文件,去掉远程连接 */
async function generateFile() {
const cssData = await readRemoteFile(iconfontUrl_css);
fileContent = cssData.replace(/[^,;]*at.alicdn.com.*\*\//g, '');
// 替换掉woff
fileContent = fileContent.replace(/[^)]*at.alicdn.com.*\),/g, ';');
// 替换掉colorful
fileContent = fileContent.replace(/[^}]*colorful[^}]*}/g, '');
// 替换src
fileContent = fileContent.replace('url(', 'src: url(');
// 加换行
fileContent = fileContent.replace('{font-family', '{\n font-family');
// 加换行
fileContent = fileContent.replace(') format(', ')\n format(');
// 去除最后一个换行
fileContent = fileContent.replace(/\n$/, '');
// 有颜色的图标生成background: url('cdnUrl')
const fontXml = await fetchXml(iconfontUrl);
fontXml.svg.symbol.forEach((item: ISymbol) => {
const iconId = item.$.id;
if (/colorful/.test(iconId)) {
let svgStr = '';
item.path.forEach((path: IPath) => {
const keys = Object.keys(path.$);
let attrStr = '';
keys.forEach(k => {
attrStr += `${k}="${path.$[k]}" `;
});
svgStr = `${svgStr}<path ${attrStr}></path>`;
});
svgStr = `<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em">${svgStr}</svg>`;
const hash = getHash(svgStr);
const fileName = `${iconId}.${hash}.svg`;
const filePath = `${stylePath}${fileName}`;
fs.writeFileSync(filePath, svgStr);
uploadFileToCDN(filePath);
svgStr = `\n.${iconId} {\n background: url('${cdnPath}${fileName}');\n}\n`;
fileContent += svgStr;
}
});
}
function getHash(cont: string) {
return cryptoNode
.createHash('md5')
.update(cont)
.digest('hex')
.substr(0, 8);
}
async function uploadAndUpdate() {
// 写文件
fs.writeFileSync(cssPath, fileContent.replace(/"/g, "'"), 'utf8');
}
generateFile().then(() => {
uploadAndUpdate();
});
function readRemoteFile(remoteUrl: string): Promise<string> {
let _data = '';
return new Promise(resolve => {
http.get(remoteUrl, (res: any) => {
//数据
res.on('data', function(chunk: string) {
_data += chunk;
});
res.on('end', function() {
resolve(_data);
});
});
});
}
async function fetchXml(
url: string
): Promise<{
svg: {
symbol: ISymbol[];
};
}> {
const data = await readRemoteFile(url);
const matches = String(data).match(/'<svg>(.+?)<\/svg>'/);
return new Promise((resolve, reject) => {
parseString(`<svg>${matches ? matches[1] : ''}</svg>`, (err: any, result: any) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
/** 将pathWithHash文件上传到CDN */
async function uploadFileToCDN(pathWithHash: string) {
return new Promise((resolve, reject) => {
exec(`${此处为上传至CDN的命令行}`, (error: any, stdout: any, stderr: any) => {
if (error) {
console.error(`exec error: ${error}`);
reject(error);
return;
}
resolve('');
/** 删除文件 */
fs.unlinkSync(pathWithHash);
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
});
}
再在package.json
中的scripts中配置
"icon": "ts-node scripts/iconfont.ts"
则每次更新iconfont后,更新对应的url,再运行npm run icon
即可
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。