使用SVG构建你自己的图标库

简介: 使用SVG构建你自己的图标库

前言

大家好,我是jay。在项目开发的过程中,很多时候设计师会给你一个SVG图标,让你用到项目里当作字体icon。可能你现有的项目中已经有前辈搭建好这套体系,你直接把这个文件放入某个文件夹中,然后run一条命令,就可以十分方便的用例如<span class="icon icon-search"></span>之类的写法把一个icon渲染出来。那假如现在什么都没有,让你自己去搭建这么一套体系,你会怎么做呢?我们一起往下看吧。

以下图标素材都是我在网上找的,在这里仅当作学习用途,侵删~

实战操作

首先使用vite搭建一个工程:

  1. npm init生成package.json文件,内容如下:
{
    "name": "font",
    "version": "1.0.0",
    "description": "font project",
    "main": "index.js",
    "scripts": {
        "dev": "vite"
    },
    "author": "jayliang",
    "license": "ISC",
}

  1. npm i vite -D安装vite
  2. 根目录下新建index.htmlindex.js,并在index.html中如下引入index.js
<script type="module" src="./index.js"></script>

首先我们在根目录新建一个assets文件夹,用来存放SVG文件。然后来看如下代码:

//index.js
const app = document.querySelector('#app')

app.innerHTML = render()

function render() {
    return `
        <div class="container">
            <h1>Hello SVG</h1>
            <span>${renderIcon('search', { color: 'red', fontSize: 30 })}</span>
        </div>
    `
}

function renderIcon(name, options = { color: 'black', fontSize: 16 }) {
    return ''
}

在主渲染逻辑中,我们实际上要实现的是renderIcon方法。看上面这架势,renderIcon看来是要直接返回SVG对应的HTML片段了。我们现在手里只有一批SVG文件,怎么让他返回代码片段呢?在浏览器环境开发的时候虽说可以动态引入文件,但是读取文件的原文还是比较难搞的事情毕竟没有fs.readFile之类的API

这里我们可以写一个简单的脚本预处理一下,先把SVG文件的内容读出来,根目录新建一个icons文件夹来存放脚本处理的结果。这个预处理脚本要处理的事情如下:

  • 读取所有的SVG文件内容,改造成字符串导出
  • SVG文件中的widthheightfill字符串提取出来,后续作为参数传入。
  • 生成一个入口文件暴露所有SVG文件。

icons文件夹大概长成这个样子:

index.js // 入口文件
script.js //生成文件脚本
home.js //home.svg生成的文件
search.js //search.svg生成的文件

脚本实现

下面一起来看一下script.js脚本的实现

const path = require('path')
const fs = require('fs')
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const assetsDirPath = path.resolve(__dirname, '../assets') //存放SVG文件的目录
const assets = fs.readdirSync(assetsDirPath)
const currentPath = path.resolve(__dirname, './') //当前目录,即icons目录
assets.forEach(asset => {
    const assetPath = `${assetsDirPath}/${asset}`
    let res = fs.readFileSync(assetPath, { encoding: 'utf8' })
    const reg = /<svg.*>[\s\S]*<\/svg>/ //将SVG标签过滤出来
    let svg = reg.exec(res)[0]
    const dom = new JSDOM(`<div id="container">${svg}</div>`) //方便操作节点对象
    const document = dom.window.document;
    const container = document.querySelector('#container');
    const svgDom = container.querySelector('svg')
    svgDom.setAttribute('width', '${fontSize}') // width与height属性处理
    svgDom.setAttribute('height', '${fontSize}')
    const paths = svgDom.querySelectorAll('path')
    for (let i = 0; i < paths.length; i++) {
        const path = paths[i]
        path.setAttribute('fill', '${color}') //path属性处理
    }
    svg = container.innerHTML
    const fileName = asset.split('.')[0] + '.js'
    //导出函数实现
    const string = `
        export default function({color,fontSize}){
            return \`${svg}\`  
        }
    `
    fs.writeFileSync(currentPath + '/' + fileName, string)
})


//入口文件拼接
let importStr = ``
let exportStr = ``

assets.forEach(asset => {
    const fileName = asset.split('.')[0]
    importStr += `import ${fileName} from './${fileName}';\n`
    exportStr += `${fileName},\n`
})
const content = `
    ${importStr}

    export default {
        ${exportStr}
    }
`
fs.writeFileSync(currentPath + '/index.js',content)

任意一个SVG文件经过处理后转成的JS文件内容是这样子的:

//home.js

export default function({color,fontSize}){
    return `<svg t="1648047237899" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1683" xmlns:xlink="http://www.w3.org/1999/xlink" width="${fontSize}" height="${fontSize}">
            <path d="....." fill="${color}"></path>
        </svg>`
}
    

最后生成的入口文件index.js内容是这样子的:


import home from './home';
import search from './search';
import set from './set';


export default {
    home,
    search,
    set,
}

renderIcon实现

在上述预处理好icon文件与入口脚本之后,iconrender函数就十分简单了,实现如下:

import icon from './icons'
function renderIcon(name, options = { color: 'black', fontSize: 16 }) {
    const iconRenderFunc = icon[name]
    if (!iconRenderFunc || typeof iconRenderFunc !== 'function') {
        throw new Error(`icon:${name} is not found`)
    }
    const res = iconRenderFunc(options)
    return res
}

来试一下渲染效果是否符合预期:

`
<span>${renderIcon('search', { color: 'red', fontSize: 30 })}</span>
<span>${renderIcon('home', { color: 'pink', fontSize: 50 })}</span>
<span>${renderIcon('set', { color: 'black', fontSize: 16 })}</span>
`

99.png


由上图可以看出基本上渲染是没问题的,我们还需要做的一个事情是给渲染出来的SVG标签暴露一个选择器以及对其加上鼠标移上效果处理。改造renderIcon方法如下:

function renderIcon(name, options = { color: 'black', fontSize: 16 }, mouseEnterOptions = {}) {
    // ......
    const id = genId()
    svg.id = id
    svg.classList += ` icon-${name}`

    if (Object.keys(mouseEnterOptions).length > 0) {
        setTimeout(() => {
            const dom = document.querySelector(`#${id}`)
            const { color, fontSize } = mouseEnterOptions
            const { color: originColor, fontSize: originFontsize } = options
            let resetPathColor = null
            let resetFontSize = null
            dom.addEventListener('mouseenter', () => {
                if (color) {
                    setPathColor(dom, color)
                    resetPathColor = setPathColor
                }
                if (fontSize) {
                    setSvgFontsize(dom, fontSize)
                    resetFontSize = setSvgFontsize
                }
            })
            dom.addEventListener('mouseleave', () => {
                resetPathColor && resetPathColor(dom, originColor)
                resetFontSize && resetFontSize(dom, originFontsize)
            })
        }, 0)
    }
}

function setSvgFontsize(svg, fontSize) {
    svg.setAttribute('width', fontSize)
    svg.setAttribute('height', fontSize)
}

function setPathColor(svg, color) {
    const paths = svg.querySelectorAll('path');
    [...paths].forEach(path => {
        path.setAttribute('fill', color)
    })
}

加多一个mouseEnterOptions参数定义鼠标移入的参数,然后监听mouseentermouseleave事件即可。

99.png 链接


当然你用框架可以封装成<Icon name="search" options={color:'black',fontSize:30}/>这样的使用形式,会更加的优雅。

字体图标库

我们上面利用了node.js预处理SVG文件+渲染逻辑基本实现了一个能满足大多数业务场景的图标库。那么业界更普遍的做法其实是把SVG当成字体来用,也就是我最一开始说的也许你只要<span class="icon icon-search"><span>就能渲染一个图标,下面我们一起来看一下是如何实现的。

我们会用到一个十分牛逼的字体操作库————font-carrier,是在GitHubstar1.5k的明星第三方包,可以利用它很方便地使用SVG生成字体。先来安装一下npm i font-carrier -D,然后在根目录新建一个fonts目录,在这个目录下新建一个script.js,内容编写如下:

const fontCarrier = require('font-carrier')
const path = require('path')
const fs = require('fs')
const assetsDirPath = path.resolve(__dirname, '../assets')
const assets = fs.readdirSync(assetsDirPath)
const font = fontCarrier.create()
let initValue = 0xe000
for (let i = 0; i < assets.length; i++) {
    const assetPath = `${assetsDirPath}/${assets[i]}`
    const res = fs.readFileSync(assetPath).toString()
    initValue += 1
    const char = String.fromCharCode(initValue)
    font.setSvg(char, res)
}

font.output({
    path: './iconfonts'
})

默认会输出.eot.svg.ttf.woff.woff2,默认会输出这几个字体文件,究其原因是各个浏览器对字体的实现不一样,所以这是为了兼容大多数的浏览器。然后我们再定义一个iconfonts.css文件,主要为了定义字体,内容如下:

@font-face {
    font-family: 'iconfont';
    src: url('iconfonts.eot'); /* IE9*/
    src: url('iconfonts.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
    url('iconfonts.woff') format('woff'), /* chrome、firefox */
    url('iconfonts.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/
    url('iconfonts.svg#uxiconfont') format('svg'); /* iOS 4.1- */
}

.iconfont {
    font-family: "iconfont";
    font-size: 16px;
    font-style: normal;
}

定义完之后,引入这个CSS文件,然后就可以如下使用了:

<span class="iconfont search">&#xE001</span>
<span class="iconfont home">&#xE002</span>
<span class="iconfont setting">&#xE003</span>

98.png 链接


伪类

上面我们是直接使用了字体所对应的unicode编码,其实也可以使用CSS伪类的形式,这也是业界用的最多的形式。在上面的基础上,只要生成多一个icon.css记录这些伪类信息就行。代码如下:

const iconMap = {}
for (let i = 0; i < assets.length; i++) {
    //......
    iconMap[assets[i]] = '\\' + initValue.toString(16).toUpperCase()
}
let content = ``

Object.keys(iconMap).forEach(key => {
    const name = key.replace('.svg','')
    const value = iconMap[key]
    content += `
        .icon-${name}::before {
            content:'${value}'
        }    `
})
fs.writeFileSync('./icon.css',content)

生成的icon.css内容如下:

.icon-home::before {
    content: '\E001'
}

.icon-search::before {
    content: '\E002'
}

.icon-set::before {
    content: '\E003'
}

我们就能通过<span class="iconfont icon-home"></span>这样的方式来使用图标了。

最后

以上就是本篇文章的全部内容,你平时项目开发过程中是如何使用这样的图标库的呢?欢迎留言讨论。如果觉得有趣或者对你有帮助的话,留下一个赞吧~


相关文章
|
前端开发
【CSS】CSS字体样式【CSS基础知识详解】
【CSS】CSS字体样式【CSS基础知识详解】
CSS3 transform 立体导航栏
CSS3 transform 立体导航栏
53 0
|
6月前
|
前端开发
CSS实现瀑布流
CSS实现瀑布流
|
3月前
|
前端开发
CSS——通过CSS实现展示更多选项和收起
CSS——通过CSS实现展示更多选项和收起
31 0
|
4月前
|
移动开发 前端开发 HTML5
CSS 【实战】 “四合院”布局
CSS 【实战】 “四合院”布局
38 0
CSS 【实战】 “四合院”布局
|
4月前
|
前端开发
css 绘图——钉子
css 绘图——钉子
34 1
|
5月前
|
Web App开发 机器学习/深度学习 前端开发
|
5月前
|
前端开发
程序与技术分享:css常见自适应布局
程序与技术分享:css常见自适应布局
|
前端开发 容器
【css】css实现图片或动图边缘模糊化处理(附示例代码)
【css】css实现图片或动图边缘模糊化处理(附示例代码)
|
前端开发 UED 开发者
前端祖传三件套CSS的CSS3新特性之渐变
在前端开发中,CSS是不可或缺的一部分。作为前端祖传三件套之一,CSS在网页设计中有着非常重要的作用。而在CSS3中,渐变(gradient)是一个非常实用且常用的新特性,它可以让我们为页面的背景、字体颜色等元素添加平滑的过渡和渐变效果,从而提升网页的美观度和用户体验。
95 0