清理图标
所有通过 Iconify 提供的图标集都会经过验证和清理流程。
为什么需要这样做?SVG 与其他图像不同,它可能包含大量内容,导致处理起来很困难。
图标通常包含大量无用代码(尤其是从旧版软件导出时)。
图标甚至可能包含恶意代码,例如脚本、事件监听器以及指向外部资源的链接。
在处理单色图标(仅有一种颜色,且该颜色应可更改的图标)时,有些图标使用 fill,有些使用 stroke,有些依赖系统默认颜色,有些使用 black 颜色,还有些使用看似随机的颜色。
您需要的所有工具均可在 Iconify Tools 包中找到。
处理流程
清理流程分为 3 个步骤:
- 验证与初步清理。
- 更改图标调色板。
- 优化。
验证与初步清理
第一部分是代码验证与初步清理。
当设计师从编辑器导出图标时,SVG 通常包含大量显示图标所不需要的额外代码。
图标还可能包含危险元素,例如脚本或外部资源。会对图标进行验证,以确保它们不包含矢量形状以外的任何内容。
请参阅 错误代码示例 以及 SVG 验证工作原理说明。
调色板更改
在初步清理和验证之后,会更改图标的调色板。
对于单色图标,颜色会更改为 currentColor,以确保图标跟随文本颜色。这样无论图标使用的是 fill 还是 stroke,都能轻松更改任何图标的颜色。
对于具有硬编码调色板的图标,解析器会检查图标是否未使用系统默认颜色或 currentColor。
请参阅 解释调色板更改的文章。
优化
最后一步是优化。它通过移除不必要的代码和优化路径来减小图标大小。
请参阅 一篇非常简短的解释图标优化的文章。
代码
想用自己的图标试试吗?您需要的所有函数均可在 Iconify Tools 中找到。
以下是几个示例。
解析单个单色图标
此代码示例解析单个单色图标。图标使用黑色,该颜色将被替换为 currentColor,白色形状将被移除。
图标从单个文件加载,输出存储在另一个文件中。
import { promises as fs } from 'fs';
import { compareColors, stringToColor } from '@iconify/utils/lib/colors';
import {
SVG,
cleanupSVG,
parseColors,
isEmptyColor,
runSVGO,
deOptimisePaths,
} from '@iconify/tools';
(async () => {
const source = 'svg/test.svg';
const target = 'htdocs/assets/test.svg';
// Load icon
const content = await fs.readFile(source, 'utf8');
const svg = new SVG(content);
// Clean up and validate icon
// This will throw an exception if icon is invalid
cleanupSVG(svg);
// Change color to `currentColor`
// Skip this step if icon has hardcoded palette
const blackColor = stringToColor('black');
const whiteColor = stringToColor('white');
parseColors(svg, {
defaultColor: 'currentColor',
callback: (attr, colorStr, color) => {
if (!color) {
// Color cannot be parsed!
throw new Error(`Invalid color: "${colorStr}" in attribute ${attr}`);
}
if (isEmptyColor(color)) {
// Color is empty: 'none' or 'transparent'. Return as is
return color;
}
// Change black to 'currentColor'
if (compareColors(color, blackColor)) {
return 'currentColor';
}
// Remove shapes with white color
if (compareColors(color, whiteColor)) {
return 'remove';
}
throw new Error(`Unexpected color "${colorStr}" in attribute ${attr}`);
},
});
// Optimise
runSVGO(svg);
// Update paths for compatibility with old software
deOptimisePaths(svg);
// Get SVG string. Returned <svg> has dimensions matching viewBox, such as height="24"
const newContent = svg.toMinifiedString();
// Same as above, but exported <svg> will have height="1em". Use this code if
// you need height to be "1em", such as when using this code in unplugin-icons
// const newContent = svg.toMinifiedString({ height: '1em' });
// Save icon
await fs.writeFile(target, newContent, 'utf8');
})();解析整个图标集
此代码示例解析整个图标集,并以 IconifyJSON 格式返回图标集。
它与上面的示例类似,但使用 importDirectory() 导入目录中的所有 SVG 文件,然后将结果存储在 JSON 文件中。每个图标都在异步 forEach() 回调中进行解析。
import { promises as fs } from 'fs';
import { compareColors, stringToColor } from '@iconify/utils/lib/colors';
import {
cleanupSVG,
parseColors,
isEmptyColor,
runSVGO,
deOptimisePaths,
importDirectory,
} from '@iconify/tools';
(async () => {
const source = 'svg';
const prefix = 'test';
const target = 'htdocs/assets/test.json';
// Load icon set
const iconSet = await importDirectory(source, {
// Set prefix for imported icon set to 'test'
prefix
});
// Parse all icons
await iconSet.forEach((name, type) => {
if (type !== 'icon') {
// Do not parse aliases
return;
}
// Get SVG instance for icon
const svg = iconSet.toSVG(name);
// Clean up and validate icon
// This will throw an exception if icon is invalid
cleanupSVG(svg);
// Change color to `currentColor`
// Skip this step if icon has hardcoded palette
const blackColor = stringToColor('black');
const whiteColor = stringToColor('white');
parseColors(svg, {
defaultColor: 'currentColor',
callback: (attr, colorStr, color) => {
if (!color) {
// Color cannot be parsed!
throw new Error(`Invalid color: "${colorStr}" in attribute ${attr}`);
}
if (isEmptyColor(color)) {
// Color is empty: 'none' or 'transparent'. Return as is
return color;
}
// Change black to 'currentColor'
if (compareColors(color, blackColor)) {
return 'currentColor';
}
// Remove shapes with white color
if (compareColors(color, whiteColor)) {
return 'remove';
}
throw new Error(`Unexpected color "${colorStr}" in attribute ${attr}`);
},
});
// Optimise
runSVGO(svg);
// Update paths for compatibility with old software
deOptimisePaths(svg);
// SVG instance is detached from icon set, so changes to
// icon are not stored in icon set automatically.
// Update icon in icon set
iconSet.fromSVG(name, svg);
});
// Save icon set
const iconSetContent = iconSet.export();
await fs.writeFile(
target,
JSON.stringify(iconSetContent, null, '\t'),
'utf8'
);
})();