preface

Hi, I’m Jay. Many times during project development, designers will give you an SVG icon to use as a font icon in the project. If you already have this system in your existing projects, you can simply put the file into a folder and run a command to render an icon using something like . What would you do if you had to set up such a system without anything? Let’s read on.

The following icon material is I found on the Internet, here only for learning purposes, delete ~

Practical operation

First use Vite to build a project:

  1. npm initgeneratepackage.jsonFile, as follows:
    {
        "name": "font"."version": "1.0.0"."description": "font project"."main": "index.js"."scripts": {
            "dev": "vite"
        },
        "author": "jayliang"."license": "ISC",}Copy the code
  2. npm i vite -DThe installationvite
  3. Create one in the root directoryindex.htmlandindex.jsAnd, inindex.htmlIs introduced as followsindex.js:
    <script type="module" src="./index.js"></script>
    Copy the code

First we create an Assets folder in the root directory to store the SVG file. Then look at the following code:

//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 ' '
}
Copy the code

In the main render logic, what we actually want to implement is the renderIcon method. RenderIcon appears to return the CORRESPONDING HTML fragment of SVG directly. We only have a bunch of SVG files. How do we get them to return snippets? While it is possible to import files dynamically in a browser environment, reading the text of a file is a bit of a hassle without an API like fs.readfile.

Here we can write a simple script to preprocess, first read the content of the SVG file, and create an ICONS folder from the root directory to store the results of the script processing. The preprocessor script does the following:

  • Read allSVGFile content, transformed into a string export
  • theSVGIn the filewidth,height,fillThe string is extracted and passed in as a parameter.
  • Generate an entry file to expose allSVGFile.

The ICONS folder looks something like this:

Index.js // entry file script.js // Generated file Script home.js //home.svg generated file search.js //search.svg generated fileCopy the code

Script implementation

Let’s take a look at the implementation of script.js

const path = require('path')
const fs = require('fs')
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const assetsDirPath = path.resolve(__dirname, '.. /assets') // Directory for storing SVG files
const assets = fs.readdirSync(assetsDirPath)
const currentPath = path.resolve(__dirname, '/') // The current directory, namely the ICONS directory
assets.forEach(asset= > {
    const assetPath = `${assetsDirPath}/${asset}`
    let res = fs.readFileSync(assetPath, { encoding: 'utf8' })
    const reg = /<svg.*>[\s\S]*<\/svg>/ // Filter out the SVG tags
    let svg = reg.exec(res)[0]
    const dom = new JSDOM(`<div id="container">${svg}</div>`) // To facilitate manipulation of node objects
    const document = dom.window.document;
    const container = document.querySelector('#container');
    const svgDom = container.querySelector('svg')
    svgDom.setAttribute('width'.'${fontSize}') // Handle width and height attributes
    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 attribute processing
    }
    svg = container.innerHTML
    const fileName = asset.split('. ') [0] + '.js'
    // Export function implementation
    const string = `
        export default function({color,fontSize}){
            return \`${svg}` \ `}
    fs.writeFileSync(currentPath + '/' + fileName, string)
})


// import file splice
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)
Copy the code

Any SVG file that is processed into a JS file looks like this:

//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>`
}
    
Copy the code

The resulting entry file index.js looks like this:


import home from './home';
import search from './search';
import set from './set';


export default {
    home,
    search,
    set,
}

Copy the code

renderIconimplementation

After preprocessing the icon file and entry script, the render function of icon is very simple and implemented as follows:

import icon from './icons'
function renderIcon(name, options = { color: 'black', fontSize: 16 }) {
    const iconRenderFunc = icon[name]
    if(! iconRenderFunc ||typeoficonRenderFunc ! = ='function') {
        throw new Error(`icon:${name} is not found`)}const res = iconRenderFunc(options)
    return res
}
Copy the code

To see if the render is as expected:

`
<span>${renderIcon('search', { color: 'red', fontSize: 30 })}</span>
<span>${renderIcon('home', { color: 'pink', fontSize: 50 })}</span>
<span>${renderIcon('set', { color: 'black', fontSize: 16 })}</span>
`
Copy the code

As you can see from the image above, rendering is basically fine, one more thing we need to do is expose a selector for the rendered SVG tag and mouse over it. RenderIcon can be modified as follows:

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)
    })
}
Copy the code

Add a mouseEnterOptions parameter to define the mouseenter and mouseleave parameters, then listen for mouseEnter and mouseleave events.

Options ={color:’black’,fontSize:30}/>

Font icon library

We used node.js preprocessing SVG file + rendering logic above to basically achieve an icon library that can meet most business scenarios. The more common approach in the industry is to use SVG as a font, which is what I started with. Maybe you can render an icon just by . Let’s see how this works.

We will use a very cool font manipulation library ————font-carrier, which is a 1.5K star third-party package on GitHub, to make it very easy to generate fonts using SVG. NPM I font -carrier-d, create a new font directory in the root directory, and create script.js under this directory.

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'
})
Copy the code

Eot. SVG. TTF. Woff. Woff2. Then we define an iconfonts.css file, mainly for defining fonts, which reads as follows:

@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, iOS4.2+ * /url('iconfonts.svg#uxiconfont') format('svg'); / * iOS 4.1 - * /
}

.iconfont {
    font-family: "iconfont";
    font-size: 16px;
    font-style: normal;
}
Copy the code

Once defined, import the CSS file and you can use it as follows:

<span class="iconfont search">&#xE001</span>
<span class="iconfont home">&#xE002</span>
<span class="iconfont setting">&#xE003</span>
Copy the code

pseudo-classes

We used the Unicode encoding of the font directly above, but we can also use the CSS pseudo-class form, which is the most commonly used form in the industry. On the basis of the above, just generate one more icon. CSS to record these pseudo-class information. The code is as follows:

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)
Copy the code

The generated icon.css looks like this:

.icon-home::before {
    content: '\E001'
}

.icon-search::before {
    content: '\E002'
}

.icon-set::before {
    content: '\E003'
}
Copy the code

We can use the icon by

The last

That’s the end of this article. How do you use icon libraries in your daily projects? Welcome to comment. Leave a like if you think it’s funny or helpful