uni-app的uni-icons通过 fontFamily 自定义图标

1,925 阅读5分钟

这篇笔记主要记录uni-icons通过fontFamily自定义图标, 使用的自定义图标来自iconfont, 涉及到的知识点: HTML 实体 vs Unicode 字符,微信小程序v-html会被转为 rich-text

用法
  1. 选择自定义图标,下载自定义图标文件(这里我选择的是iconfont图标)
  2. 项目引入图标文件,页面使用
1.选择自定义图标,下载自定义图标文件

这里我以iconfont为例,选择了几个图标,然后点击下载本地

image.png

2.项目引入图标文件,页面使用
  1. 项目加载字体文件,放到static/fonts文件夹
  2. App.vue引入字体文件
  3. 页面使用

项目加载字体文件 image.png

App.vue引入字体文件

<style lang="scss">
@import '@/static/fonts/iconfont.css'
</style>

页面使用, 注意这里fontFamily,如果没设置过默认就是iconfont, 然后那个unicode码来自于iconfont网站,我们复制下就行

image.png

<uni-icons size="18" color="#111" fontFamily="iconfont">
  &#xe8b2;
</uni-icons>

image.png

HTML 实体 vs Unicode 字符, 为什么要用"\ue8b2"这种形式

先看一个问题,最后一个图标无法正常显示

image.png

<script>
const icon = "\ue8b2";
const icon1 = "&#xe8b2;";
</script>
<template>
      <uni-icons size="18" color="#11f011" fontFamily="iconfont">
        &#xe8b2;
      </uni-icons>
      <uni-icons size="18" color="#11f011" fontFamily="iconfont">
        "\ue8b2"
      </uni-icons>
      <uni-icons size="18" color="#111" fontFamily="iconfont">
        "&#xe8b2;"
      </uni-icons>
    <uni-icons size="18" color="#11f011" fontFamily="iconfont">
        {{ icon }}
      </uni-icons>
      <uni-icons size="18" color="#111" fontFamily="iconfont">
        {{ icon1 }}
      </uni-icons>
</template>

原因就是vue 的模版表达式{{}}会直接转成字符串,不会把&#xe8b2;当成HTML实体去渲染

HTML 实体(HTML Entity)是一种在 HTML 文档中表示特殊字符的编码方式。当你想在 HTML 中显示某些特殊字符(例如 <、>、& 等),或者是某些不能直接在 HTML 源码中输入的字符时,使用 HTML 实体来代替这些字符。HTML 实体通常以 & 开头,以 ; 结尾。实体有两种类型:命名实体数字字符引用

1. 命名实体(Named Entity)

命名实体使用特定的名称来表示一些常见的符号或字符。它们的格式是 &name;,例如: &lt; 表示<, &gt; 表示 >, &amp;表示 &, 这些符号在 HTML 里通常有特殊含义,所以使用命名实体来避免与 HTML 标签冲突。

2. 数字字符引用(Numeric Character Reference)

当某个字符没有命名实体,或者你希望使用某个特定的 Unicode 字符时,可以使用数字字符引用来表示它。数字字符引用有两种形式:

  • 十进制格式:&#number;,number 是字符的 Unicode 码点。 例如:&#60; 表示 <(Unicode 码点为 60)
  • 十六进制格式:&#xhexnumber;,hexnumber 是字符的 Unicode 码点的十六进制形式,x 表示十六进制。例如:&#x3C; 表示 <(十六进制为 3C)

&#xe8b2; 是一种 字符引用,也叫 字符实体,但它不同于常见的 HTML 实体(如 < 表示 <、& 表示 &)。&#xe8b2; 这种形式实际上是 十六进制的字符编码,表示的是一个 Unicode 码点,也可以被称为 数字字符引用

那我们用v-html去渲染好了,试试

 <uni-icons size="18" color="#e71111" fontFamily="iconfont" 
	  v-html="icon1">

我们发现h5可以正常渲染,小程序不行,

image.png 看uni-appvue文档, 使用v-html会把组件转为rich-text, 肯定就没法正常显示icon了

App端和H5端支持 v-html ,微信小程序会被转为 rich-text,其他端不支持 v-html

还有一个疑问, 为什么{{ "&#xe8b2;" }}这样可以解析,{{ unicode }}这样不行, unicode="&#xe8b2;"

这是因为当我们直接在模板中写 &#xe8b2;,Vue 将它当作模板中的 HTML 实体,浏览器会在渲染时自动解析这个 HTML 实体为图标。这与变量插值不同,因为在模板的 HTML 内容中,浏览器会直接处理 HTML 实体,而 Vue 不会干涉这种处理。

unicode = '&#xe8b2;' 赋值给变量时,插值语法 {{ unicode }} 仍然会将 unicode 变量的内容作为纯文本进行插入。这意味着浏览器直接展示了字符串 &#xe8b2;,而不会去解析它为 HTML 实体。

我们现在知道了,如果在script定义变量,变量一定要用"\ue8b2"这种形式, 看下官方文档,用的都是这种形式

image.png

怎么把"&#xe8ad;"转成这种 "\ue8ad"

"&#xe8ad;" 和 "\ue8ad" 是两种不同的表示方式,它们的主要区别在于它们的编码格式和使用场景:

  1. "&#xe8ad;"

    • 这是一个 HTML 实体编码(HTML Character Entity Reference),通常在 HTML 或 XML 中使用。它表示的是一个字符的 Unicode 代码点。
    • &#xe8ad; 中的 e8ad 是十六进制的 Unicode 编码。
    • HTML 解析器在渲染时会将其转换为对应的字符。
  2. "\ue8ad"

    • 这是 JavaScript 中的 Unicode 转义序列。它直接表示字符,不依赖于 HTML 解析器。
    • 在 JavaScript 中,\u 后面跟四个十六进制数字(在此案例中,e8ad 是十六进制数)表示对应的 Unicode 字符。
    • JavaScript 解释器将其解析为字符,通常用于字符串中。

转换方法

要将 HTML 实体编码转换为 JavaScript Unicode 转义序列,可以按照以下步骤进行:

  1. 提取十六进制值:从 &#xe8ad; 中提取出 e8ad。
  2. 添加 \u 前缀:将其转换为 \ue8ad。

转换示例

const htmlEntities = [
  "&#xe8ad;",
  "&#xe602;",
  "&#xe8bc;",
  "&#xe8b2;",
  "&#xe8b4;",
  "&#xe8bd;"
];

// 转换为 JavaScript Unicode 表示
const unicodeCharacters = htmlEntities.map(entity => {
  // 使用正则表达式提取十六进制数字
  const hex = entity.match(/&#x([0-9A-Fa-f]+);/)[1];
  return `\\u${hex}`; // 返回以 \u 开头的格式
});

// 输出结果
console.log(unicodeCharacters); // [ '\ue8ad', '\ue602', '\ue8bc', '\ue8b2', '\ue8b4', '\ue8bd' ]

假设我们有一个数组包含 HTML 实体编码,我们可以使用 JavaScript 的字符串处理函数进行转换。

看下我们要实现的效果
<template>
  <view class="self">
    <view :style="{ height: getNavBarHeight() + 'px' }"></view>
    <view class="userinfo">
      <view class="left">
        <view class="avatar">
          <image src="../../static/logo.png" mode=""></image>
        </view>
        <view class="info">
          <view class="username">编辑资料</view>
          <view class="text">创作的第109天</view>
        </view>
      </view>
      <view class="right">
        <view class="text">编辑资料</view>
        <view class="icon">
          <uni-icons type="right" size="20" color="#999"></uni-icons>
        </view>
      </view>
    </view>

    <view class="cardLayout">
      <view class="list">
        <view
          class="item"
          v-for="(item, index) in sentenceList"
          :key="item.unicode">
          <view class="left">
            <view class="icon" :style="{ background: generateGradient(index) }">
              <uni-icons size="18" color="#ffffff" fontFamily="iconfont">
                {{ item.unicode }}
              </uni-icons>
            </view>
            <view class="name">{{ item.name }}</view>
          </view>
          <view class="right">
            <view class="text">{{ item.count }}</view>
            <uni-icons type="right" size="20" color="#999"></uni-icons>
          </view>
        </view>
      </view>
    </view>

    <view class="cardLayout">
      <view class="list">
        <view
          class="item"
          v-for="(item, index) in sentenceList1"
          :key="item.unicode">
          <view class="left">
            <view class="icon" :style="{ background: generateGradient(index+6) }">
              <uni-icons
                size="18"
                color="#ffffff"
                :type="item.type"></uni-icons>
            </view>
            <view class="name">{{ item.name }}</view>
          </view>
          <view class="right">
            <view class="text">{{ item.count }}</view>
            <uni-icons type="right" size="20" color="#999"></uni-icons>
          </view>
        </view>
      </view>
    </view>
  </view>
</template>

<script setup>
import { onMounted, ref } from "vue";
import { getNavBarHeight } from "@/utils/system.js";

const icon = "\ue8b2";
const icon1 = "&#xe8b2;";

const sentenceList = [
  {
    name: "随手记",
    unicode: "\ue8ad", // 转换为 Unicode 字符
  },
  {
    name: "审核",
    unicode: "\ue602", // 转换为 Unicode 字符
    count: 9,
  },
  {
    name: "收藏",
    unicode: "\ue8bc", // 转换为 Unicode 字符
    count: 99,
  },
  {
    name: "积分",
    unicode: "\ue8b2", // 转换为 Unicode 字符
    count: 199,
  },
  {
    name: "句子森林",
    unicode: "\ue8b4", // 转换为 Unicode 字符
  },
  {
    name: "联系我们",
    unicode: "\ue8bd", // 转换为 Unicode 字符
  },
];

const sentenceList1 = ref([
  {
    name: "偏好设置",
    type: "tune",
  },
  {
    name: "退出登录",
    type: "contact;",
  },
]);

// Function to generate unique gradients
const generateGradient = (index) => {
  const gradients = [
    "linear-gradient(to right, #ff7e5f, #feb47b)", // Gradient 1
    "linear-gradient(to right, #43cea2, #185a9d)", // Gradient 2
    "linear-gradient(to right, #ff6a00, #ee0979)", // Gradient 3
    "linear-gradient(to right, #00c6ff, #0072ff)", // Gradient 4
    "linear-gradient(to right, #f7971e, #ffd200)", // Gradient 5
    "linear-gradient(to right, #ad5389, #3c1053)", // Gradient 6
    "linear-gradient(to right, #8e2de2, #4a00e0)",
    "linear-gradient(to right, #ffe000, #799f0c)", 
  ];
  return gradients[index % gradients.length];
};

const queryDatabase = async () => {
  uni.navigateTo({
    url: "/pages/chore/add",
  });
};

// Define onShareAppMessage function
onMounted(() => {});
</script>

<style lang="scss">
.self {
  background: $page-bg-color;
  min-height: 100vh;
  padding-bottom: 30rpx;
  .userinfo {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 50rpx;
    .left {
      display: flex;
      align-items: center;
      .avatar {
        width: 120rpx;
        height: 120rpx;
        border: 3px solid #fff;
        border-radius: 50%;
        overflow: hidden;
        image {
          width: 100%;
          height: 100%;
        }
      }
      .info {
        padding-left: 20rpx;
        .username {
          font-size: 38rpx;
          font-weight: 600;
          color: #111;
        }
        .text {
          font-size: 26rpx;
          font-weight: 100;
          color: $text-font-color-3;
          padding-top: 10rpx;
        }
      }
    }

    .right {
      display: flex;
      align-items: center;
      .text {
        font-size: 28rpx;
        color: #999;
      }
    }
  }

  .cardLayout {
    width: 690rpx;
    background: #fff;
    margin: 30rpx auto;
    border-radius: 20rpx;
    .list {
      padding: 30rpx;
      .item {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 34rpx 0;
        border-bottom: 1px solid $border-color-light;
        &:last-child {
          border: none;
        }
        .left {
          display: flex;
          .icon {
            width: 50rpx;
            height: 50rpx;
            background: #ccc;
            border-radius: 50%;
            overflow: hidden;
            display: flex;
            justify-content: center;
            align-items: center;
          }
          .name {
            font-size: 38rpx;
            padding-left: 20rpx;
          }
        }
        .right {
          display: flex;
          align-items: center;
          font-size: 26rpx;
          color: #999;
        }
      }
    }
  }
}
</style>

image.png