Erstellen Sie Ihre eigene Symbolbibliothek mit SVG

Vorwort

Hallo zusammen, mein Name ist Jay. Während der Projektentwicklung gibt Ihnen der Designer oft ein SVGSymbol, das Sie als Schriftart im Projekt verwenden können icon. Vielleicht haben einige Senioren in Ihrem bestehenden Projekt dieses System bereits gebaut. Sie können diese Datei direkt in einen bestimmten Ordner legen und dann runeinen Befehl eingeben , um eine Datei mit <span class="icon icon-search"></span>einer Schreibmethode wie zum Beispiel zu iconrendern . Wenn es im Moment nichts gibt und Sie gebeten werden, selbst ein solches System zu bauen, was werden Sie tun? Schauen wir gemeinsam nach unten.

Die folgenden Symbolmaterialien sind alles, was ich im Internet gefunden habe, und sie werden hier nur zu Lernzwecken verwendet.

eigentlicher Kampfeinsatz

Erste Verwendung zum viteErstellen eines Projekts:

  1. npm initErzeugen Sie eine package.jsonDatei mit folgendem Inhalt:
    {
        "name": "font",
        "version": "1.0.0",
        "description": "font project",
        "main": "index.js",
        "scripts": {
            "dev": "vite"
        },
        "author": "jayliang",
        "license": "ISC",
    }
    
    复制代码
  2. npm i vite -DInstallierenvite
  3. index.htmlErstellen Sie ein neues und index.jsim Stammverzeichnis und index.htmlimportieren Sie es wie folgt index.js:
    <script type="module" src="./index.js"></script>
    复制代码

assetsZuerst erstellen wir einen neuen Ordner im Stammverzeichnis , um SVGDateien zu speichern. Sehen Sie sich dann den folgenden Code an:

//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 ''
}
复制代码

In der Hauptrenderlogik wollen wir eigentlich renderIconMethoden implementieren. Wenn man sich die obige Haltung ansieht, renderIconscheint es, dass sie direkt SVGzum entsprechenden HTMLFragment zurückkehren wird. Wir haben jetzt nur einen Stapel SVGDateien in der Hand, wie können wir ihm Rückgabecode-Schnipsel machen? Obwohl es möglich ist, Dateien während der Entwicklung der Browserumgebung dynamisch zu importieren, ist es immer noch schwierig, den Originaltext der Datei zu lesen, da es fs.readFileso etwas nicht gibt API.

Hier können wir ein einfaches Skript zur Vorverarbeitung schreiben, zuerst den Inhalt der Datei lesen und im SVGStammverzeichnis einen neuen iconsOrdner erstellen, um die Ergebnisse der Skriptverarbeitung zu speichern. Die Dinge, die dieses Vorverarbeitungsskript handhabt, sind die folgenden:

  • Lesen Sie alle SVGDateiinhalte und wandeln Sie sie für den Export in Zeichenfolgen um
  • 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>
`
复制代码

0w29t6fmke.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事件即可。

pfnjr5olyc.gif

当然你用框架可以封装成<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>
复制代码

hscpajyttc.gif

伪类

上面我们是直接使用了字体所对应的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>这样的方式来使用图标了。

最后

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

Ich denke du magst

Origin juejin.im/post/7079080370590711822
Empfohlen
Rangfolge