网络日志

开发日记(04) - iconfont 挂了,项目内的图标怎么办?

📌 0x1 问题

iconfont 应该是挂了有 1-2 两个月了,不管是因为什么原因,肯定会有一大堆的项目有一阵子图标出不来。

作为开发者以来就一直有个感觉,那就是在线服务都不可靠。项目中第三方服务越多,更多不可控的因素就越多。如果都是在自己的服务器上,至少我可以控制服务启动,关闭。都是在同一时间,而不会出现这种能用,但是又没有完全能用的状态,这比服务挂了还恶心。

Node 创始人新搞的 deno 也支持引入在线的脚本,我看到这段代码第一时间不是竟然还能这样写,给我的第一感觉就是这代码某天可能就挂了,导致整个项目起不来。

import { VERSION } from 'https://deno.land/std/version.ts'
console.log(VERSION)

📌 0x2 需要达到的效果

因为是 uni-app 开发的项目,且需要兼容 app 和 nvue。因此图标只能使用单色字体图标的形式,无法直接使用 svg 的形式。
  • 本地导入,不依赖任何的第三方
  • 支持 weex 的 nvue 界面
  • 兼容 h5 和 app 的显示
  • 组件形式使用
  • (web 也是差不多的思路,这里以 uni-app 为例)

📌 0x3 解决思路

既然在线服务不可靠,那么肯定是使用本地导入方式了。现在的 iconfont 是无法使用,无法上传和下载。看看还有什么办法将 svg 转换到字体图标了。github 上有不少将 svg 转换到 webfont 的库。

我尝试下载了几个测试,发现都存在一个问题,就是我从 MasterGo 导出的 svg 图标使用这些工具进行转换的话,在项目内使用始终是一坨黑色的。猜测是 svg 需要进行一些处理。因为我从 iconfont 下载一个 svg 下来进行转换是 ok。这里不展开说,也不是自己擅长的方向。如果你想了解 iconfont 做了什么可以看看这个

里面讲的比较详细,字体图标处理其实有很大的学问,图标很小,做的事情可不少。

📌 0x4 icomoon svg 处理

这是一个类似于 iconfont 的平台,缺点就是只能本地维护,所有的数据都存在缓存,如果清理了浏览器缓存数据就被清除了。不过这个支持我从设计图直接导出的 svg 进行处理。这里以 MasterGo 进行演示实际上就是获取 svg 图标的过程。

4x1 文件改名

首先让 ui 整理好项目内用到的图标,命名好。可以让我直接导出成为 svg。

4x2 上传到 icomoon

拿到导出的 svg 图标,上传到 icomoon。点击 icomoon app 进入 icomoon

导入 svg 图标

选择从 MasterGo 导出的图标,上传

选中所有的图标点击生成字体

点击预设设置为 class 选择器,取消支持 ie 8,点击 download 下载字体包

4x3 拿到导出的文件

看起来和 iconfont 导出的没啥区别。

📌 0x5 图标组件

c-icon-moon.vue

普通的 vue 组件,里面的 #ifdef 写法是 uni-app 的条件编译。为了兼容多个平台需要使用到条件编译。

<template>
  <!-- #ifdef APP-NVUE -->
  <text @click="onClick" class="icomoon" :style="iconStyle">{{ icons[name] }}</text>
  <!-- #endif -->

  <!-- #ifndef APP-NVUE -->
  <text @click="onClick" class="icomoon" :class="name" :style="iconStyle" />
  <!-- #endif -->
</template>

<script>
// #ifdef APP-NVUE
import icons from './icons'
// #endif
import { addUnit } from '@/utils'

export default {
  name: 'CustomIconMoon',
  props: {
    size: {
      type: [Number, String],
      default: 'inherit',
    },
    width: {
      type: [Number, String],
      default: 'auto',
    },
    weight: {
      type: String,
      default: 'normal',
    },
    height: {
      type: [Number, String],
      default: 'auto',
    },
    color: {
      type: String,
      default: '#909399',
    },
    name: {
      type: String,
      required: true,
    },
    rpx: {
      type: Boolean,
      required: false,
      default: false,
    },
    bubble: {
      type: Boolean,
      required: false,
      default: true,
    },
  },
  computed: {
    iconStyle() {
      return {
        fontSize: this.size === 'inherit' ? 'inherit' : addUnit(this.size, this.rpx),
        width: addUnit(this.width, this.rpx),
        height: addUnit(this.height, this.rpx),
        color: this.color,
        'line-height': addUnit(this.height, this.rpx),
        'font-weight': this.weight,
      }
    },
  },
  methods: {
    onClick(e) {
      this.$emit('click')
      if (!this.bubble && e) {
        e.stopPropagation()
      }
    },
  },
  // #ifdef APP-NVUE
  data() {
    return {
      icons: icons,
    }
  },
  // #endif
}
</script>

<style scoped>
/* #ifndef APP-NVUE */
@import './style.css';

/* #endif */

.icomoon {
  font-family: icomoon;
}
</style>

addUnit 方法

// 添加单位,如果有rpx,%,px等单位结尾或者值为auto,直接返回,否则加上rpx单位结尾
export function addUnit(value = 'auto', rpx = false) {
  value = String(value)
  return isNumber(value) ? `${value}${rpx ? 'rpx' : 'px'}` : value
}

/**
 * 验证十进制数字
 */
export function isNumber(value) {
  return /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value)
}

icomoon-ttf-base64

为了兼容 nvue 需要将 fonts/icomoon.ttf 文件转换为 base64 写入到该文件

转换网址:https://www.giftofspeed.com/base64-encoder/

export default 'AAEAAAALAIAAAwAw...`

icomoon.woff

icons.js

这里配置 nvue 需要显示的图标名字。

\ue912 类似的字符可以打开 style.css 找到你需要在 nvue 显示的图标名字复制。

/**
 * 这里配置需要在 nvue 显示的图标
 */
export default {
  // 指纹
  'icomoon-fingerprint': '\ue912',
}

nvue-helper

nvue 需要的初始化配置。

import icomoonTtfBase64 from './icomoon-ttf-base64'

/**
 * iconfont 需要用ttf转base64
 * 转换网址:https://www.giftofspeed.com/base64-encoder/
 */
export function onInitNvueFontIcon() {
  const dom = uni.requireNativePlugin('dom')
  dom.addRule('fontFace', {
    fontFamily: 'icomoon',
    src: `url('data:font/truetype;charset=utf-8;base64,${icomoonTtfBase64}')`,
  })
}

style.css

删除不需要的格式化,只留下 src: url('icomoon.woff') format('woff'); 这一段就行。

@font-face {
  font-family: icomoon;
  font-style: normal;
  font-weight: normal;
  font-display: block;
  src: url('icomoon.woff') format('woff');
}

.icomoon {
  font-family: icomoon !important;

  /* Better Font Rendering =========== */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-style: normal;
  font-variant: normal;
  font-weight: normal;
  line-height: 1;
  text-transform: none;
}

.icomoon-add::before {
  content: '\e900';
}

/* ... */

📌 0x6 使用

vue 界面

直接参考 组件使用示例 就可以。

Nvue 界面

nvue 界面使用需要导入字体文件,全局只需要导入一次就行了

App.vue

<script>
// #ifdef APP-NVUE
import { onInitNvueFontIcon } from '@/components/c-icon-moon/nvue-helper'
// #endif

export default {
  onLaunch() {
    // eslint-disable-next-line no-console
    console.log('App Launch')
    
    /**
     * Nvue 启动处理
     */
    // #ifdef APP-NVUE
    onInitNvueFontIcon()
    // #endif
  },
  onShow() {
    // eslint-disable-next-line no-console
    console.log('App Show')
  },
  onHide() {
    // eslint-disable-next-line no-console
    console.log('App Hide')
  },
}
</script>

组件使用示例

其实就是 vue 组件的使用了。这里贴一个示例。

<c-icon-moon size="60" name="icomoon-fingerprint" color="red" />

显示效果

📌 iconfont 正名

这里为 iconfont 正名一下,先看下网友的情绪 https://github.com/thx/iconfont-plus/issues

阿里是大,但不是什么部门都大,内部是有预算分配的,人家是企业,不是慈善机构。从使用 iconfont 这个平台以来一直都是白嫖,准确来说好像没有付费项目。

再来看看今天用到的 icomoon 平台订阅费用,当然是举个例子,有多少公司愿意在 icon 上有这个预算的呢?

评论区留给大家。

📔 开发日记系列

只记录些平时开发觉得有用的东西,有问题请务必斧正,拜托了 🙏🙏🙏
  1. 开发日记(01) - uni-app 使用等宽字体对其数字显示
  2. 开发日记(02) - js 异步任务队列
  3. 开发日记(03) - uni-app 打包为 app
  4. 开发日记(04) - iconfont 挂了,项目内的图标怎么办?

关于我

SunSeekerX,

全栈开发、区块链开发、移动端开发、前后端开发、NodeJS 开发、小程序、uni-app 开发、等

喜欢探讨技术实现方案和细节,完美主义者,见不得 bug

Github:https://github.com/SunSeekerX

个人博客:https://yoouu.cn/

个人在线笔记:https://doc.yoouu.cn/