如何在微信小程序中使用多色icon
背景
在微信小程序开发过程中难免会遇到需要使用多色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
即可
- Kotlin 和 Jetpack 视频合集 | MAD Skills
- 实战 Java 16 值类型 Record - 1. Record 的默认方法使用以及基于预编译生成相关字节码的底层实现
- 优先使用 KTX 库 | MAD Skills
- 实操搭建企业级Harbor v1.10.6
- 推荐一个开源文件管理系统,简单好用
- 一文搞懂MySQL体系架构!!
- 使用Documentfragment优化DOM操作
- 深入理解浏览器缓存机制
- 百度搜索与推荐引擎的云原生改造 | Geek大咖说第一期
- 从零搭建自己的社区系统,这个开源项目值得拥有
- 茫茫内存,我该如何用 windbg 找到你 ?
- Java中对象的生与灭- 核心篇
- 使用JavaScript学习设计模式
- 一文带你剖析LiteOS互斥锁Mutex源代码
- 小胖问我:MySQL 事务与 MVCC 原理?
- 免费正版 IntelliJ IDEA license 详细指南
- 纯函数是什么?怎么合理运用纯函数?
- 聊聊dddsample-core的Specification
- 关于从入门three.js到做出3d地球这件事(第四篇: 贴图地球)
- 端路由原理及react-router的常用组件