开发人员经常将 SVG 直接插入 JSX 中。这样使用起来很方便,但是会增加 JS 包的大小。为了追求优化,我决定寻找另一种使用 SVG 图标而不会使捆绑包混乱的方法。我们将讨论 SVG 精灵、它们是什么、如何使用它们以及可使用哪些工具来使用它们。
从理论开始,我们将编写一个脚本来逐步生成 SVG 精灵,最后讨论vite和webpack的插件。
图像精灵是放置在单个图像中的图像集合。反过来,SVG Sprite 是 SVG 内容的集合,包装到<symbol />
中,然后将其放入<svg />
中。
例如,我们有一个简单的 SVG 钢笔图标:
为了获得 SVG 精灵,我们将<svg />
标签替换为<symbol />
,并在外部用<svg />
包裹它:
现在它是一个 SVG 精灵,里面有一个id="icon-pen"
图标。
好的,但是我们应该弄清楚如何将该图标放置在 HTML 页面上。我们将使用<use />
标签和href
属性,指定图标的 ID,它将在 SVG 中复制该元素。
让我们看一下<use />
工作原理的示例:
在此示例中,有两个圆圈。第一个具有蓝色轮廓,第二个是第一个的重复,但具有红色填充。
让我们回到 SVG 精灵。随着<use />
的使用,我们将得到:
在这里,我们有一个带有钢笔图标的按钮。
到目前为止,我们在<button />
中使用了没有代码的图标。如果页面上有多个按钮,它不会多次影响 HTML 布局的大小,因为所有图标都来自我们的 SVG 精灵并且可以重复使用。
让我们将 SVG 精灵移动到一个单独的文件中,这样我们就不必弄乱index.html
文件。首先,创建一个sprite.svg
文件并将 SVG 精灵放入其中。下一步是使用<use/>
中的href
属性提供对图标的访问:
为了节省大量图标使用时间,让我们为此过程设置自动化。为了轻松访问图标并按照我们的需要管理它们,必须将它们分开,每个图标都在自己的文件中。
首先,我们应该将所有图标放在同一个文件夹中,例如:
现在,让我们编写一个脚本来获取这些文件并将它们组合成一个 SVG 精灵。
在项目的根目录中创建generateSvgSprite.ts
文件。
安装glob库:
npm i -D glob
使用globSync
获取每个图标的完整路径数组:
现在,我们将迭代每个文件路径并使用 Node 的内置库fs获取文件内容:
太好了,我们有了每个图标的 SVG 代码,现在我们可以将它们组合起来,但是我们应该用symbol
标签替换每个图标内的svg
标签,并删除无用的 SVG 属性。
我们应该使用一些 HTML 解析器库来解析 SVG 代码以获得其 DOM 表示。我将使用node-html-parser :
我们已经解析了 SVG 代码并获得了 SVG 元素,就像它是一个真正的 HTML 元素一样。
使用相同的解析器,创建一个空的symbol
元素以将svgElement
的子元素移动到symbol
:
从svgElement
中提取子元素后,我们还应该从中获取id
和viewBox
属性。让我们设置图标文件的名称作为id
。
现在,我们有了一个可以放置在 SVG 精灵中的symbol
元素。因此,只需在迭代文件之前定义symbols
变量,将symbolElement
转换为字符串,然后将其推入symbols
中:
最后一步是创建 SVG 精灵本身。它表示一个字符串,“root”中包含svg
,符号作为子项:
const svgSprite = `<svg>${symbols.join('')}</svg>`;
如果您不考虑使用插件(我将在下面讨论),您需要将带有创建的精灵的文件放在某个静态文件夹中。大多数捆绑程序使用public
文件夹:
fs.writeFileSync('public/sprite.svg', svgSprite);
就是这样;该脚本已准备好使用:
// generateSvgSprite.ts import { globSync } from 'glob'; import fs from 'fs'; import { HTMLElement, parse } from 'node-html-parser'; import path from 'path'; const svgFiles = globSync('src/icons/*.svg'); const symbols: string[] = []; svgFiles.forEach(file => { const code = fs.readFileSync(file, 'utf-8'); const svgElement = parse(code).querySelector('svg') as HTMLElement; const symbolElement = parse('<symbol/>').querySelector('symbol') as HTMLElement; const fileName = path.basename(file, '.svg'); svgElement.childNodes.forEach(child => symbolElement.appendChild(child)); symbolElement.setAttribute('id', fileName); if (svgElement.attributes.viewBox) { symbolElement.setAttribute('viewBox', svgElement.attributes.viewBox); } symbols.push(symbolElement.toString()); }); const svgSprite = `<svg>${symbols.join('')}</svg>`; fs.writeFileSync('public/sprite.svg', svgSprite);
您可以将此脚本放在项目的根目录中并使用tsx运行它:
npx tsx generateSvgSprite.ts
实际上,我在这里使用tsx是因为我以前到处都用 TypeScript 编写代码,而这个库允许您执行用 TypeScript 编写的节点脚本。如果你想使用纯 JavaScript,那么你可以使用以下命令运行它:
node generateSvgSprite.js
那么,让我们总结一下脚本的作用:
src/icons
文件夹中查找任何.svg
文件。
<svg />.
public
文件夹中创建sprite.svg
文件。让我们讨论一个常见且重要的案例:颜色!我们创建了一个脚本,其中图标进入精灵,但该图标在整个项目中可以有不同的颜色。
我们应该记住,不仅<svg/>
元素可以具有 fill 或 border 属性,还可以path
、 circle
、 line
等属性。有一个非常有用的 CSS 功能可以帮助我们 - currentcolor 。
该关键字表示元素颜色属性的值。例如,如果我们在background: currentcolor
元素上使用color: red
,则该元素将具有红色背景。
基本上,我们需要将每个描边或填充属性值更改为currentcolor
。我希望你没有看到它是手动完成的,呵呵。与非常有用的工具svgo相比,即使编写一些替换或解析 SVG 字符串的代码也不是很有效。
这是一个 SVG 优化器,不仅可以帮助处理颜色,还可以帮助删除 SVG 中的冗余信息。
让我们安装svgo :
npm i -D svgo
svgo
有内置插件,其中之一是convertColors
,它具有属性currentColor: true
。如果我们使用此 SVG 输出,它将用currentcolor
替换颜色。以下是svgo
和convertColors
的用法:
import { optimize } from 'svgo'; const output = optimize( '<svg viewBox="0 0 24 24"><path fill="#000" d="m15 5 4 4" /></svg>', { plugins: [ { name: 'convertColors', params: { currentColor: true, }, } ], } ) console.log(output);
输出将是:
<svg viewBox="0 0 24 24"><path fill="currentColor" d="m15 5 4 4"/></svg>
让我们将svgo
添加到我们在上一部分中编写的神奇脚本中:
// generateSvgSprite.ts import { globSync } from 'glob'; import fs from 'fs'; import { HTMLElement, parse } from 'node-html-parser'; import path from 'path'; import { Config as SVGOConfig, optimize } from 'svgo'; // import `optimize` function const svgoConfig: SVGOConfig = { plugins: [ { name: 'convertColors', params: { currentColor: true, }, } ], }; const svgFiles = globSync('src/icons/*.svg'); const symbols: string[] = []; svgFiles.forEach(file => { const code = fs.readFileSync(file, 'utf-8'); const result = optimize(code, svgoConfig).data; // here goes `svgo` magic with optimization const svgElement = parse(result).querySelector('svg') as HTMLElement; const symbolElement = parse('<symbol/>').querySelector('symbol') as HTMLElement; const fileName = path.basename(file, '.svg'); svgElement.childNodes.forEach(child => symbolElement.appendChild(child)); symbolElement.setAttribute('id', fileName); if (svgElement.attributes.viewBox) { symbolElement.setAttribute('viewBox', svgElement.attributes.viewBox); } symbols.push(symbolElement.toString()); }); const svgSprite = `<svg xmlns="http://www.w3.org/2000/svg">${symbols.join('')}</svg>`; fs.writeFileSync('public/sprite.svg', svgSprite);
并运行脚本:
npx tsx generateSvgSprite.ts
因此,SVG 精灵将包含带有currentColor
的图标。这些图标可以在项目中的任何地方使用您想要的任何颜色。
我们有一个脚本,我们可以随时运行它,但是我们应该手动使用它,有点不方便。因此,我推荐一些可以随时随地观看.svg
文件并生成 SVG 精灵的插件:
vite-plugin-svg-spritemap (适用于vite用户)
这是我的插件,其中基本上包含我们刚刚在本文中创建的脚本。该插件默认启用currentColor
替换,因此您可以非常轻松地设置该插件。
// vite.config.ts import svgSpritemap from 'vite-plugin-svg-spritemap'; export default defineConfig({ plugins: [ svgSpritemap({ pattern: 'src/icons/*.svg', filename: 'sprite.svg', }), ], });
svg-spritemap-webpack-plugin (适用于webpack用户)
我一直使用这个Webpack插件,直到我切换到Vite。但如果你使用 Webpack,这个插件仍然是一个很好的解决方案。您应该手动启用颜色转换,它将如下所示:
// webpack.config.js const SVGSpritemapPlugin = require('svg-spritemap-webpack-plugin'); module.exports = { plugins: [ new SVGSpritemapPlugin('src/icons/*.svg', { output: { svgo: { plugins: [ { name: 'convertColors', params: { currentColor: true, }, }, ], }, filename: 'sprite.svg', }, }), ], }
我将在React中提供一个示例,但您可以在您想要的地方实现它,因为它主要与 HTML 有关。因此,当我们的构建文件夹中有sprite.svg
时,我们可以访问 sprite 文件并创建基本的Icon
组件:
const Icon: FC<{ name: string }> = ({ name }) => ( <svg> <use href={`/sprite.svg#${name}`} /> </svg> ); const App = () => { return <Icon name="pen" />; };
因此,总结一切,为了防止大量手动操作图标,我们:
开发效率不仅仅在于节省时间,还在于节省时间。这是为了释放我们的创造潜力。自动化管理图标等具体任务不仅仅是一种捷径;它是通往更流畅、更有影响力的编码体验的门户。通过节省处理此类日常事务的时间,您可以专注于更复杂的任务并更快地成长为开发人员。