关于javascript:使用SVG构建你自己的图标库

49次阅读

共计 6859 个字符,预计需要花费 18 分钟才能阅读完成。

前言

大家好,我是 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.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>
`

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

伪类

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

最初

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

正文完
 0