前言

大家好,我是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",}
  2. npm i vite -D装置vite
  3. 根目录下新建index.htmlindex.js,并在index.html中如下引入index.js

    <script type="module" src="./index.js"></script>

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

//index.jsconst 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.jsexport 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>`

由上图能够看出基本上渲染是没问题的,咱们还须要做的一个事件是给渲染进去的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事件即可。

当然你用框架能够封装成<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 = 0xe000for (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>

伪类

下面咱们是间接应用了字体所对应的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>这样的形式来应用图标了。

最初

以上就是本篇文章的全部内容,你平时我的项目开发过程中是如何应用这样的图标库的呢?欢送留言探讨。如果感觉乏味或者对你有帮忙的话,留下一个赞吧~