前端组长问我为什么要使用2500万个icon?!

325 阅读2分钟

就是要用这么多图标!!!

image.png

你问我用这么多图标项目打包后体积不会很大吗?我的回答是0

直接进入主题!(注意本文章只围绕iconfont进行icon使用

封装组件按需加载方案地址:iconfont在vue3中按需使用和编译打包示例

资源分离加载方案地址:资源分离加载iconfont图标

icon需求

可配置、可复用、按需加载、平台兼容(pc、小程序、安卓、ios通用一套icon)

历史方案

class名称

导出一堆文件,引入后直接使用class名进行使用,刚开始接触使用

svg的id

结合插件生成svg文件导入,使用use到id的方式使用

封装组件按需加载

使用innerHtml对svg组装后生成效果,见iconfont一键下载所有图标、在vue3中的按需使用和多色配置

资源分离加载

将svg图标转json文件后远程加载使用,达到项目体积0入侵,高度可配置:单色、多色、大小,远程动态更新新图标,达到2500万图标随时替换!!!

如何选择适合的方案?

项目固定图标、无需配置

这种情况会使用使用按需加载,在开发的时候就确定了所有内容,直接将icon打包编译进项目中,加载就进行使用

需要后台配置,动态更换

使用资源分离加载,这样出现新图标只需要将静态资源更新,后台配置完后前端就能直接显示,前端0改动,直接读取配置即可,不需要关心图标的变动信息

实现效果

封装组件按需加载

Vue3版本:iconfont在vue3中按需使用和编译打包示例

动态更换

Vue3版本:资源分离加载iconfont图标

资源分离加载实现流程

先将iconfont图标转换为本地资源文件,配置加载代码的远程地址

后台配置效果:

image.png

image.png

image.png

加载icon代码 :远程加载对应图标进行显示,使用innerHTML显示内容

<template>
    <div ref="iconRef" style="display: inline-flex" :style="{ fontSize: `${size}px`, color: color as any }"></div>
</template>

<script lang="ts" setup>
import { FileUtil } from 'tools-vue3'
import { onMounted, ref, watch } from 'vue'
import SText from '~/store/SText'

const props = defineProps({
    name: { default: 'Ant' },
    type: {
        default: 'up'
    },
    size: { default: '20' },
    color: { default: undefined as any }
})

watch(
    () => [props.type, props.color],
    () => {
        init()
    }
)

onMounted(() => {
    init()
})

const iconRef = ref<HTMLDivElement>({} as any)
const viewBoxStr = '#{viewBox}'
const viewBoxDefault = '0 0 1024 1024'
const str = {
    svg: [
        `<svg style="width: 1em;height: 1em;vertical-align: middle;overflow: hidden;" viewBox="${viewBoxStr}" version="1.1" xmlns="http://www.w3.org/2000/svg">`,
        '</svg>'
    ],
    get0: (replacestr: string) => {
        return str.svg[0].replace(viewBoxStr, replacestr)
    },
    getPath: (param: { d: string; fill?: string }) => {
        return `<path d="${param.d}" fill="${param.fill ?? 'currentColor'}" />`
    }
}

if (!window.sicon) {
    window.sicon = {
        resource: {},
        load: {},
        getResorce: async (_url: string) => {
            let filestr: any
            const req = window.sicon.load[_url]
            if (req) {
                filestr = await req
            } else {
                window.sicon.load[_url] = new Promise(async (res) => {
                    const _json = await FileUtil.getFile(_url)
                    res(_json)
                })
                filestr = await window.sicon.load[_url]
            }
            return filestr
        }
    }
}

const init = async () => {
    const color = ['color:black;', 'color:red;']
    if (StrUtil.isNull(props.type)) return
    const _url = `${SText.icon.url}/${props.name}/${props.type}.json`
    let filestr: any
    const _icon_resource = window.sicon.resource[_url]
    if (_icon_resource) {
        filestr = _icon_resource
    } else {
        filestr = await window.sicon.getResorce(_url)
        if (typeof filestr === 'object') {
            filestr = JSON.stringify(filestr)
        }
        window.sicon.resource[_url] = filestr
    }
    if (!filestr) {
        console.info(
            `%c icon <%c${props.name}-${props.type}%c> 不存在`,
            `${color[0]} font-size:12px`,
            color[1],
            color[0]
        )
        return
    }
    let res = ''
    if (!filestr.startsWith('{')) {
        res = `${str.get0(viewBoxDefault)}<path d="${filestr}" fill="currentColor" />${str.svg[1]}`
    } else {
        let objt: { d: string[]; fill: { [key: string]: number[] }; viewBox?: string } = JSON.parse(filestr)
        res = str.get0(objt.viewBox ?? viewBoxDefault)
        const getFill = (fill: { [key: string]: number[] }, index: number) => {
            const fillkeys = Object.keys(fill)
            const colors = props.color

            for (let i = 0; i < fillkeys.length; i++) {
                const key = fillkeys[i]
                const nums = fill[key]
                if (nums.includes(index)) {
                    if (colors) {
                        if (Array.isArray(colors)) return colors[i] ?? colors[colors.length - 1]
                        return colors
                    } else {
                        return key
                    }
                }
            }

            return 'currentColor'
        }

        objt.d.forEach((item, index) => {
            res += str.getPath({
                d: item,
                fill: getFill(objt.fill, index)
            })
        })
        res += str.svg[1]
    }

    iconRef.value.innerHTML = res
}
</script>

使用代码


<sicon name="Ant" type="up" color="#000000" />
<sicon name="Ant" type="up" :color="['#000000','#000000']" />

结语

因为iconfont的图标很多,也在不停更新好的图标,所以一直在使用iconfont做项目,不断的解决自己的“懒”的需求,用最快的时间做更多的需求~ 有问题欢迎提出讨论~ 总会解决的~