乐趣区

关于前端:如何在微信小程序中使用多色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 即可

退出移动版