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

前言 大家好,我是jay。在项目开发的过程中,很多时候设计师会给你一个SVG图标,让你用到项目里当作字体icon。可能你现有的项目中已经有前辈搭建好这套体系,你直接把这个文件放入某个文件夹中,然后run一条命令,就可以十分方便的用例如之类的写法把一个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", }

  2. npm i vite -D安装vite
  3. 根目录下新建index.htmlindex.js,并在index.html中如下引入index.js

首先我们在根目录新建一个assets文件夹,用来存放SVG文件。然后来看如下代码:
//index.js const app = document.querySelector('#app')app.innerHTML = render()function render() { return `Hello SVG ${renderIcon('search', { color: 'red', fontSize: 30 })}` }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 = /[\s\S]*<\/svg>/ //将SVG标签过滤出来 let svg = reg.exec(res)[0] const dom = new JSDOM(`${svg}`) //方便操作节点对象 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.jsexport default function({color,fontSize}){ return ` ` }

最后生成的入口文件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 }

来试一下渲染效果是否符合预期:
` ${renderIcon('search', { color: 'red', fontSize: 30 })} ${renderIcon('home', { color: 'pink', fontSize: 50 })} ${renderIcon('set', { color: 'black', fontSize: 16 })} `

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

由上图可以看出基本上渲染是没问题的,我们还需要做的一个事情是给渲染出来的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事件即可。
使用SVG构建你自己的图标库
文章图片

当然你用框架可以封装成这样的使用形式,会更加的优雅。
字体图标库 我们上面利用了node.js预处理SVG文件+渲染逻辑基本实现了一个能满足大多数业务场景的图标库。那么业界更普遍的做法其实是把SVG当成字体来用,也就是我最一开始说的也许你只要就能渲染一个图标,下面我们一起来看一下是如何实现的。
我们会用到一个十分牛逼的字体操作库————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 = https://www.it610.com/article/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文件,然后就可以如下使用了:
  

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

伪类 上面我们是直接使用了字体所对应的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 = https://www.it610.com/article/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' }

我们就能通过这样的方式来使用图标了。
最后 【使用SVG构建你自己的图标库】以上就是本篇文章的全部内容,你平时项目开发过程中是如何使用这样的图标库的呢?欢迎留言讨论。如果觉得有趣或者对你有帮助的话,留下一个赞吧~

    推荐阅读