Icon在web应用中的作用
图标,是信息的图像表达方式。在设计页面的时候使用icon有很多优点,icon可以将某个功能进行直观的视觉表达,减少用户的认知成本。大脑处理图片的速度比处理文字的快60000倍。
总结下来,icon的作用主要是视觉信息化和增强交互体验
1. 视觉信息化:
- 行为引导
- 特征标识
- 状态标识
2. 增强交互体验:
动态图标,为页面添加动感
使用Icon的几种基本实现方式
1. 图片图标
- 直接使用img标签加载图片资源-png|jpg|jpeg|svg|gif.......
<img src="https://cdn-icons-png.flaticon.com/512/1581/1581942.png" />
- 通过css设置元素的背景图,可灵活调整位置等,例如:抖音
<style>
.icon {
background: url("https://lf3-cdn-tos.bytegoofy.com/obj/goofy/ies/douyin_web/media/nav_light_entry_optimize_v3.249d142a09878f8c.png");
width: 24px;
height: 24px;
background-repeat: no-repeat;
background-size: 1056px auto;
}
.icon-1 {
background-position: 0px 0px;
}
.icon-2 {
background-position: -24px 0px;
}
.icon-3 {
background-position: -48px 0px;
}
</style>
<div style="display: flex; gap: 10px;">
<div class="icon icon-1"></div>
<div class="icon icon-2"></div>
<div class="icon icon-3"></div>
</div>
3. 通过css的background-image和mask-image的实现,可以参考Antfu的聊聊纯CSS图标
<style>
.icon {
background-color: currentColor;
width: 20px;
height: 20px;
mask-image: url("http://sj8ohpa5l.hd-bkt.clouddn.com/star.png?e=1725371564&token=FamrxkTXFOJ6bzL__LD8nDA7n5SQwS3EBXgF-lRK:Up0ML8cFV35lEkOgBmv9EuGlBb8=");
mask-size: 100%;
mask-position: 0 0;
}
</style>
<div style="color: pink;" class="icon"></div>
2. 字体图标
2.1. 原理
- 将图标转为字体文件,字体文件中含有unicode和图标轮廓信息的映射关系
- 在html导入字体文件
- 设置元素的字体属性
- 使用unicode编码获取图标进行渲染
2.2. 使用
以iconfont为例iconfont使用,早期的字体图标不支持多色图标,后期iconfont通过COLR字体文件类型得以支持
- 直接使用unicode编码
<style>
@font-face {
font-family: 'iconfont';
/* Project id 4635479 */
src: url('//at.alicdn.com/t/c/font_4635479_tjy5p6jrx4n.woff2?t=1722148062923') format('woff2'),
url('//at.alicdn.com/t/c/font_4635479_tjy5p6jrx4n.woff?t=1722148062923') format('woff'),
url('//at.alicdn.com/t/c/font_4635479_tjy5p6jrx4n.ttf?t=1722148062923') format('truetype'),
url('//at.alicdn.com/t/c/font_4635479_tjy5p6jrx4n.svg?t=1722148062923#iconfont') format('svg');
}
.iconfont {
font-family: "iconfont"!important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</style>
<!-- 使用对应的字体类,输入图表对应的unicode编码 -->
<i class="iconfont"></i>
2. 编写icon-class封装,将unicode通过伪元素和css类名进行映射,更好识别和记忆
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
@font-face {
font-family: "iconfont";
/* Project id 4635508 */
src: url('//at.alicdn.com/t/c/font_4635508_hl4msbmuno.woff2?t=1724578708140') format('woff2'),
url('//at.alicdn.com/t/c/font_4635508_hl4msbmuno.woff?t=1724578708140') format('woff'),
url('//at.alicdn.com/t/c/font_4635508_hl4msbmuno.ttf?t=1724578708140') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-tag_car:before {
content: "\e601";
}
.icon-shouye:before {
content: "\e60f";
}
</style>
<i class="iconfont icon-shouye"></i>
3. SVG图标
直接通过SVG标签插入HTML中,通过width,height等修改大小,通过fill修改颜色(通过使用fill="currentColor"来继承父元素字体颜色,方便在外层修改),注意viewBox需要设置合理
<svg width="50px" height="50px" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="25" fill="currentColor" />
</svg>
4. 不同实现的对比
- img标签和css的background-image加载时机的区别
-
使用mask-image可实现动画+大小+颜色的灵活修改,另外mask-image的功能强大,可以通过mask-image实现很多的动画效果,另外bilibili的弹幕防遮挡的效果,也是通过这个属性,配合语义分割算法生成大量的遮罩图片来实现
- 通过一个div实现遮罩动画
<style>
@keyframes mask {
0% {
-webkit-mask-position: 0px 0px;
}
25% {
-webkit-mask-position: 619px 0px;
}
50% {
-webkit-mask-position: 0px 0px;
}
75% {
-webkit-mask-position: 308px 0px;
-webkit-mask-size: 100%;
}
100% {
-webkit-mask-size: 1000%;
}
}
.mask {
width: 700px;
height: 392px;
background: black url("http://sj8ohpa5l.hd-bkt.clouddn.com/bg.jpg?e=1725371507&token=FamrxkTXFOJ6bzL__LD8nDA7n5SQwS3EBXgF-lRK:rcG47nomrr54ZBtqA0N4jDeivAw=");
-webkit-mask-image: url("http://sj8ohpa5l.hd-bkt.clouddn.com/mask.jpg?e=1725371554&token=FamrxkTXFOJ6bzL__LD8nDA7n5SQwS3EBXgF-lRK:07auJhtQ13tElcygkJWAt5f1CbE=");
animation: mask 5s linear infinite forwards;
}
</style>
<div class="mask"> </div>
// 实现效果:http://sj8ohpa5l.hd-bkt.clouddn.com/mask.webp?e=1725371581&token=FamrxkTXFOJ6bzL__LD8nDA7n5SQwS3EBXgF-lRK:q6QXkjvWgztRaaSTyWJyYFUylXg=
2. 通过mask-image和语义分割实现弹幕防遮挡的原理,参考地址
框架中的使用(目前仅介绍React)
1. 项目维护svg文件,封装SVGIcon组件
- 基于svgr+svgo自行封装React-SVGIcon组件或者使用使用Antd的icon组件
import type { DashboardTreeItemType } from '@/types/finds/dashboard';
import classNames from 'classnames';
import React, { CSSProperties } from 'react';
import { ReactComponent as AreaSvg } from '../imgs/area.svg';
import { ReactComponent as CarSvg } from '../imgs/car.svg';
import { ReactComponent as OrgSvg } from '../imgs/org.svg';
import { ReactComponent as StaffSvg } from '../imgs/staff.svg';
interface ICellIcon {
name: DashboardTreeItemType;
className?: string;
style?: CSSProperties;
size?: number;
color?: string;
}
const IconMapping = {
AREA: AreaSvg,
ORG: OrgSvg,
CAR: CarSvg,
STAFF: StaffSvg,
};
const CellIcon: React.FC<ICellIcon> = ({ name, className, style, size, color }) => {
const SVGIconComponent = IconMapping[name];
return (
<SVGIconComponent
className={classNames([size ? `w-[${size}px] h-[${size}px]` : 'w-[15px] h-[15px]', className])}
color={color}
style={style}
/>;
)
};
export default CellIcon;
2. SVG sprite
2.1. SVG sprite 插件
- 配置SVG sprite 插件,配置文件存放路径和图标的命名规范等
- 在main.js中注册import 'virtual:svg-icons-register'
- 在组建中使用svg-use使用组件
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import svgr from "vite-plugin-svgr";
import { resolve } from 'path';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
'@': resolve(__dirname, './src'),
'@assets': resolve(__dirname, './src/assets'),
}
},
plugins: [
react(),
createSvgIconsPlugin({
iconDirs: [resolve(process.cwd(), 'src/assets/icons')],
symbolId: 'icon-[dir]-[name]',
}),
svgr(),
],
})
//main.js文件
import 'virtual:svg-icons-register'
//组件
<svg color="pink">
<use href={`#icon-react`}/>
</svg>
2.2. 基于第三方图标库,比如阿里iconfont进行图标管理
- 直接将iconfont中的symbol的js链接下载(iconfont提供链接不可靠,建议下载本地)
- 然后直接使用script标签加载js文件后直接在组件中使用svg-use指向对应的id即可
3. Antd+iconfont
- 使用antd组件然后使用添加上述iconfont提供的symbol形式链接
- 这种方式已经可以很好地做到多项目的图标共享和管理,但是antd的icon目前还是比较有限,并且强依赖iconfont,iconfont提供的js文件并不稳定
import React from 'react';
import { createFromIconfontCN } from '@ant-design/icons';
import { Space } from 'antd';
const IconFont = createFromIconfontCN({
scriptUrl: [
'//at.alicdn.com/t/font_1788044_0dwu4guekcwr.js', // icon-javascript, icon-java, icon-shoppingcart (overridden)
'//at.alicdn.com/t/font_1788592_a5xf2bdic3u.js', // icon-shoppingcart, icon-python
],
});
const App: React.FC = () => (
<Space>
<IconFont type="icon-javascript" />
<IconFont type="icon-java" />
<IconFont type="icon-shoppingcart" />
<IconFont type="icon-python" />
</Space>
);
export default App;
4. Iconify(推荐)
Iconify的核心是消费json:将svg转为json方便传输,然后客户端组件解析json转为svg进行消费
4.1. 优势
- 100+个图标集,200W+个图标,预设图标丰富
- 同一个库支持多种前端框架,支持原声WebComponent,React,Vue...
- 内置工具丰富,可以将本地图标进行深度清洗,比svgo的压缩清洗程度更高
- 可私有部署,添加私有源
- 可结合UnoCSS预设图标插件,直接使用css图标,具体请参考。这对不支持SVG标签的容器是非常友好的,比如微信小程序(⭐⭐⭐肥肠推荐!!!)
4.2. 不足
目前多项目之间共享自定义图标还不够丝滑,但是可以通过私有部署后,建立一套私有的图标库,目前iconify开源的node服务api非常友好,搭建成本并不是很高。
4.3. 使用
- 通过icons插图标
- 查到图标复制内容
- 建议安装vscode插件@iconify-json/vscode-icons
以下均以React为例进行演示
4.3.1. React直接使用Iconify提供的组件
// terminal
yarn add --dev @iconify/react
// 业务组件
import { Icon } from '@iconify/react';
<Icon icon="mdi-light:home" />
icon使用规则:"@数据源:数据集:图标名",参考
注意,此中使用方式首次请问图标会通过http请求先下载icon对应的json,然后存到localStorage中,后续访问页面走缓存
4.3.2. 私有部署
允许icon源自多个服务,只要能够提供json即可,iconify提供了可私有部署的node服务github.com/iconify/api.
import { addAPIProvider, Icon } from '@iconify/react';
addAPIProvider('local', {
// Array of host names
resources: ['http://localhost:3000'],
});
// Demo using provider in icon name
export function renderHomeIcon() {
return <Icon icon="@local:material-icons:home" />;
}
通过iconify提供的api,进行二开,完成简单的本地图标上传私有服务
本地代码
import {
importDirectorySync,
cleanupSVG,
runSVGO,
parseColors,
isEmptyColor,
} from '@iconify/tools';
import axios from 'axios';
import path from 'path';
console.log("__dirname", process.cwd())
// Import icons
const iconSet = importDirectorySync(path.resolve(process.cwd(), 'src/assets/icons'), {
prefix: 'iconSetName',
});
// Validate, clean up, fix palette and optimise
iconSet.forEachSync((name, type) => {
if (type !== 'icon') {
return;
}
const svg = iconSet.toSVG(name);
if (!svg) {
// Invalid icon
iconSet.remove(name);
return;
}
// Clean up and optimise icons
try {
// Clean up icon code
cleanupSVG(svg);
// Assume icon is monotone: replace color with currentColor, add if missing
// If icon is not monotone, remove this code
parseColors(svg, {
defaultColor: 'currentColor',
callback: (attr, colorStr, color) => {
return !color || isEmptyColor(color)
? colorStr
: 'currentColor';
},
});
// Optimise
runSVGO(svg);
} catch (err) {
// Invalid icon
console.error(`Error parsing ${name}:`, err);
iconSet.remove(name);
return;
}
// Update icon
iconSet.fromSVG(name, svg);
});
axios.post("http://127.0.0.1:3000/upload-icon-set", {
icons: iconSet.export()
}).then(res => {
console.log("success", res)
}).catch(err => {
console.log("upload-error", err)
})
node端代码
import fs from 'fs';
import path from 'path';
import { triggerIconSetsUpdate } from '../../data/icon-sets.js';
const ICONSETDIR = 'icons'
export function handleUpload(req, res, callback) {
const {icons} = req.body;
if (!icons) return res.status(400).send('icons 数据缺失');
const { prefix } = req.body.icons;
if (!prefix) return res.status(400).send('prefix 数据缺失');
const cwd = process.cwd();
const iconSetDir = `${cwd}/${ICONSETDIR}`;
const filePath = `${iconSetDir}/${prefix}.json`;
if (!fs.existsSync(iconSetDir)) {
fs.mkdirSync('icons');
}
if (fs.existsSync(iconSetDir) && !fs.existsSync(filePath)) {
try {
fs.writeFileSync(filePath, JSON.stringify(icons), 'utf8');
} catch (error) {
console.log('white error', error);
res.status(400).send('iconSet 创建失败')
}
// iconify node内置的更新方法
triggerIconSetsUpdate((update) => {
if (update) {
res.send('update');
} else {
res.send('nothing update');
}
});
}
else {
res.status(400).send('iconSet 已存在');
}
}
4.3.3. 结合UnoCSS使用
- 下载图标,预设配置,以及iconify工具包
pnpm add -D @unocss/preset-icons @iconify/json @iconify/utils
2. 配置uno.config.js
import { defineConfig, presetIcons, presetUno } from 'unocss';
import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders';
import {
SVG,
cleanupSVG,
runSVGO,
deOptimisePaths,
} from '@iconify/tools';
export default defineConfig({
presets: [
presetUno(),
presetIcons({
warn: true,
prefix: ['i-'],
collections: {
// 配置本地图标,结合iconify的API,进行清洗,压缩,格式化
"custom": new FileSystemIconLoader(
'./src/assets/icons',
(svg) => {
// 构造iconify中的SVG对象
const svgObj = new SVG(svg);
// 清洗
cleanupSVG(svgObj);
// svgo清洗
runSVGO(svgObj);
// Update paths for compatibility with old software
deOptimisePaths(svg);
// 删除标签空白间隙
const newContent = svgObj.toMinifiedString();
// 格式化,统一设置大小颜色
return newContent.replace(/(<svg.*?width=)"(.*?)"/, '$1"1em"')
.replace(/(<svg.*?height=)"(.*?)"/, '$1"1em"')
.replace(/fill=".*?"/, 'fill="currentColor"')
}
),
// 配置私有化部署的资源
"remote": async (iconName) => {
return await fetch("http://127.0.0.1:3000/iconSetName/"+iconName+".svg").then(res => res.text())
}
},
extraProperties: {
display: 'inline-block',
// -0.125em用于和同行文字居中对齐
'vertical-align': '-0.125em',
},
})
]
})
3. 在main中导入unocss
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import 'virtual:uno.css'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
4. 业务代码使用
function App() {
return (
<>
Welcome to use unocss iconPresets <div className="i-custom-notice" />
</>
)
}
export default App
4. 效果
可以看出,unocss的icon是通过姜svg转为base64,然后通过background-color时延图标颜色,mask-image描绘图标轮廓,可以说是纯css方案
4.4. 小程序的unocss使用
可参考该社区解决方案+本文的unocss图标预设配置,在taro中使用
总结
- 图片图标(jpg,png等)的管理方式不够优雅,切换颜色需要换图。非矢量,在当前的移动端开发中其实不再是很好的选择,往往需要通过多张不同倍率的图片进行分辨率的适配
- 字体图标其实在使用使用上还是比较方便的,但是管理和更新相对比较繁琐,往往依赖第三方平台。且动画实现艰难,无法按需映入
- SVG已然成为目前Icon的首选,矢量图形,灵活的大小形状变换,复杂动画效果实现。
-
- SVG sprite在单项目维护使用体验良好,一次性插入SVG至HTML,可在多处通过svg-use重用。但是需要自己维护一个基础的SVG组件
- Antd+iconfont的图标管理方式,可以做到自定义组件多项目共享,使用体验良好,但是需要借助第三方平台
- Iconify的多数据源图标,满足了开发者的基础需求,且可以通过自定义本地图标,自己维护图标数据源。也可以达到自定义图标多项目共享,并且搭配unocss可以很好地解决小程序不支持SVG的问题,可以将h5项目,通过taro+unocss快速迁移至小程序。