共计 6859 个字符,预计需要花费 18 分钟才能阅读完成。
前言
大家好,我是 jay。在我的项目开发的过程中,很多时候设计师会给你一个 SVG
图标,让你用到我的项目里当作字体 icon
。可能你现有的我的项目中曾经有前辈搭建好这套体系,你间接把这个文件放入某个文件夹中,而后run
一条命令,就能够非常不便的用例如 <span class="icon icon-search"></span>
之类的写法把一个 icon
渲染进去。那如果当初什么都没有,让你本人去搭建这么一套体系,你会怎么做呢?咱们一起往下看吧。
以下图标素材都是我在网上找的,在这里仅当作学习用处,侵删~
实战操作
首先应用 vite
搭建一个工程:
-
npm init
生成package.json
文件,内容如下:{ "name": "font", "version": "1.0.0", "description": "font project", "main": "index.js", "scripts": {"dev": "vite"}, "author": "jayliang", "license": "ISC", }
npm i vite -D
装置vite
-
根目录下新建
index.html
和index.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
文件中的width
、height
、fill
字符串提取进去,后续作为参数传入。 - 生成一个入口文件裸露所有
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
文件与入口脚本之后,icon
的 render
函数就非常简略了,实现如下:
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>
`
由上图能够看出基本上渲染是没问题的,咱们还须要做的一个事件是给渲染进去的 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
参数定义鼠标移入的参数,而后监听 mouseenter
和mouseleave
事件即可。
当然你用框架能够封装成 <Icon name="search" options={color:'black',fontSize:30}/>
这样的应用模式,会更加的优雅。
字体图标库
咱们下面利用了 node.js
预处理 SVG
文件 + 渲染逻辑根本实现了一个能满足大多数业务场景的图标库。那么业界更广泛的做法其实是把 SVG
当成字体来用,也就是我最一开始说的兴许你只有 <span class="icon icon-search"><span>
就能渲染一个图标,上面咱们一起来看一下是如何实现的。
咱们会用到一个非常牛逼的字体操作库————font-carrier,是在 GitHub
上star
有 1.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"></span>
<span class="iconfont home"></span>
<span class="iconfont setting"></span>
伪类
下面咱们是间接应用了字体所对应的 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>
这样的形式来应用图标了。
最初
以上就是本篇文章的全部内容,你平时我的项目开发过程中是如何应用这样的图标库的呢?欢送留言探讨。如果感觉乏味或者对你有帮忙的话,留下一个赞吧~