paint-brush
如何使用图标创建 SVG Sprite经过@gmakarov
3,193 讀數
3,193 讀數

如何使用图标创建 SVG Sprite

经过 German Makarov10m2023/12/23
Read on Terminal Reader

太長; 讀書

开发人员经常将 SVG 直接插入 JSX 中。这样使用起来很方便,但是会增加 JS 包的大小。为了追求优化,我决定寻找另一种使用 SVG 图标而不会使捆绑包混乱的方法。我们将讨论 SVG 精灵、它们是什么、如何使用它们以及可使用哪些工具来使用它们。 从理论开始,我们将编写一个脚本来逐步生成 SVG 精灵,最后讨论 vite 和 webpack 的插件。
featured image - 如何使用图标创建 SVG Sprite
German Makarov HackerNoon profile picture

开发人员经常将 SVG 直接插入 JSX 中。这样使用起来很方便,但是会增加 JS 包的大小。为了追求优化,我决定寻找另一种使用 SVG 图标而不会使捆绑包混乱的方法。我们将讨论 SVG 精灵、它们是什么、如何使用它们以及可使用哪些工具来使用它们。


从理论开始,我们将编写一个脚本来逐步生成 SVG 精灵,最后讨论vitewebpack的插件。

什么是 SVG 精灵?

图像精灵是放置在单个图像中的图像集合。反过来,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 Sprite 文件

让我们将 SVG 精灵移动到一个单独的文件中,这样我们就不必弄乱index.html文件。首先,创建一个sprite.svg文件并将 SVG 精灵放入其中。下一步是使用<use/>中的href属性提供对图标的访问:

自动创建 SVG Sprite

为了节省大量图标使用时间,让我们为此过程设置自动化。为了轻松访问图标并按照我们的需要管理它们,必须将它们分开,每个图标都在自己的文件中。


首先,我们应该将所有图标放在同一个文件夹中,例如:

现在,让我们编写一个脚本来获取这些文件并将它们组合成一个 SVG 精灵。


  1. 在项目的根目录中创建generateSvgSprite.ts文件。


  2. 安装glob库:

     npm i -D glob


  3. 使用globSync获取每个图标的完整路径数组:

  4. 现在,我们将迭代每个文件路径并使用 Node 的内置库fs获取文件内容:

    太好了,我们有了每个图标的 SVG 代码,现在我们可以将它们组合起来,但是我们应该用symbol标签替换每个图标内的svg标签,并删除无用的 SVG 属性。


  5. 我们应该使用一些 HTML 解析器库来解析 SVG 代码以获得其 DOM 表示。我将使用node-html-parser

    我们已经解析了 SVG 代码并获得了 SVG 元素,就像它是一个真正的 HTML 元素一样。


  6. 使用相同的解析器,创建一个空的symbol元素以将svgElement的子元素移动到symbol

  7. svgElement中提取子元素后,我们还应该从中获取idviewBox属性。让我们设置图标文件的名称作为id

  8. 现在,我们有了一个可以放置在 SVG 精灵中的symbol元素。因此,只需在迭代文件之前定义symbols变量,将symbolElement转换为字符串,然后将其推入symbols中:

  9. 最后一步是创建 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 属性,还可以pathcircleline等属性。有一个非常有用的 CSS 功能可以帮助我们 - currentcolor


该关键字表示元素颜色属性的值。例如,如果我们在background: currentcolor元素上使用color: red ,则该元素将具有红色背景。


基本上,我们需要将每个描边或填充属性值更改为currentcolor 。我希望你没有看到它是手动完成的,呵呵。与非常有用的工具svgo相比,即使编写一些替换或解析 SVG 字符串的代码也不是很有效。


这是一个 SVG 优化器,不仅可以帮助处理颜色,还可以帮助删除 SVG 中的冗余信息。


让我们安装svgo

 npm i -D svgo


svgo有内置插件,其中之一是convertColors ,它具有属性currentColor: true 。如果我们使用此 SVG 输出,它将用currentcolor替换颜色。以下是svgoconvertColors的用法:

 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 精灵的插件:


  1. 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', }), ], });


  2. 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" />; };

最终结果

因此,总结一切,为了防止大量手动操作图标,我们:

  • 可以轻松地在项目中保存和保留组织良好的图标,并使用所需的名称


  • 有一个脚本,将所有图标组合成一个单独文件中的单个精灵,从而减少包大小并允许我们在项目中的任何位置使用这些图标


  • 有一个有用的工具,可以帮助我们清理图标中不必要的属性,并在使用的地方更改颜色


  • 有一个插件可以在构建过程中监视我们的图标文件并生成精灵


  • 有一个图标组件,上面有一颗樱桃

结论

开发效率不仅仅在于节省时间,还在于节省时间。这是为了释放我们的创造潜力。自动化管理图标等具体任务不仅仅是一种捷径;它是通往更流畅、更有影响力的编码体验的门户。通过节省处理此类日常事务的时间,您可以专注于更复杂的任务并更快地成长为开发人员。