react 中, 本地 svg 静态文件,可以像引入字体图标一样自由的设置图标颜色?
平常项目中常用到的引入本地 svg 的方式:
- 使用
<img>
标签导入 SVG 图像 - 使用
<object>
标签导入 SVG 图像 - 使用
<embed>
标签导入 SVG 图像 - 使用
<iframe>
标签导入 SVG 图像 - 使用 CSS 背景图像导入 SVG 图像
这些单独用其它 dom 标签作为载体将 svg 展示出来,展示效果已经到达,但是直接控制 svg 的颜色变化却是难住我们前进的道路。
思路:利用 currentColor 的继承父级颜色的特性,加载 svg xml 到 dom 中,从而达到颜色跟随 css 样式进行转换
下面思路带大家进入新的世界 ↓
-
我们把本地.svg 文件需要更换颜色的元素属性
fill=xxx
替换为currentColor
,。
更换成功如下图:
-
把所有要项目中要引入的 svg 路径集中在一个文件中,做个 map 映射。
import success from "@/assets/icon/success.svg";
import warning from "@/assets/icon/warning.svg";
const iconMap: {
[key: string]: string
} = {
success,
warning
};
- 封装获取.svg 文件 xml 内容的方法,并做缓存,性能优化。
type CatchSvgFileContext = { [key: string]: string}
export let catchSvgFileContext: CatchSvgFileContext = {};
//返回svg静态文件内容
async function readLocalFile(filePath: string) {
if (filePath in catchSvgFileContext) {
return catchSvgFileContext[filePath]
}
let svgHtml = await fetch(filePath)
.then(response => response.text())
catchSvgFileContext[filePath] = svgHtml;
return svgHtml;
}
- 封装获取同步 icon 和异步 icon 的方法, 获取缓存,判断是否存在缓存。
const getIcon = (icon?: string, className?: string) => {
let path = icon ?? "";
return ` <img class="${className ?? ''}" src=${iconMap[path]} alt="" />`
}
const getIconSync = async (icon?: string) => {
return await readLocalFile(iconMap[(icon ?? "")])
}
const getSvgCatch = (icon?: string) => {
let filePath = iconMap[(icon ?? '')];
let svgCatch = catchSvgFileContext[filePath];
if (!svgCatch) {
return getIcon(icon)
}
return svgCatch
}
const iconExistsChach = (icon: string) => {
let filePath = iconMap[(icon ?? '')];
return !!catchSvgFileContext[filePath];
}
- icon.ts 文件 完整示例代码
import success from "@/assets/icon/success.svg";
import warning from "@/assets/icon/warning.svg";
const iconMap: {
[key: string]: string;
} = {
success,
warning,
};
type CatchSvgFileContext = {
[key: string]: string;
};
export let catchSvgFileContext: CatchSvgFileContext = {};
//返回svg静态文件内容
async function readLocalFile(filePath: string) {
if (filePath in catchSvgFileContext) {
return catchSvgFileContext[filePath];
}
let svgHtml = await fetch(filePath).then((response) => response.text());
catchSvgFileContext[filePath] = svgHtml;
return svgHtml;
}
//同步展示icon
export const getIcon = (icon?: string, className?: string) => {
let path = icon ?? "";
return ` <img class="${className ?? ""}" src=${iconMap[path]} alt="" />`;
};
//获取异步的svg xml
export const getIconSync = async (icon?: string) => {
return await readLocalFile(iconMap[icon ?? ""]);
};
//取缓存svg xml, 没有静态普通兼容
export const getSvgCatch = (icon?: string) => {
let filePath = iconMap[icon ?? ""];
let svgCatch = catchSvgFileContext[filePath];
if (!svgCatch) {
return getIcon(icon);
}
return svgCatch;
};
//是否存在缓存
export const iconExistsChach = (icon: string) => {
let filePath = iconMap[icon ?? ""];
return !!catchSvgFileContext[filePath];
};
export const getIconPath = (icon: string) =>
iconMap[(icon ?? "").toLocaleUpperCase()];
- react.tsx 组件中使用。
import React, { useEffect, useMemo, useState } from "react";
import { getIconSync, getSvgCatch, iconExistsChach } from "./icon";
const Box: React.FC<any> = ({ iconList }) => {
const [svgIsLoad, setSvgIsLoad] = useState(false);
//检测展示svg icon的不存在缓存,则更新svg缓存
const initSvgCatch = async () => {
for (let i = 0; i < iconList.length; i++) {
let item = iconList[i];
if (item.icon && !iconExistsChach(item.icon)) {
let svg = await getIconSync(item.icon);
}
}
setSvgIsLoad(true);
};
useEffect(() => {
initSvgCatch();
}, []);
const iconL = useMemo(() => {
if (svgIsLoad) {
return (
iconList &&
iconList.map((item: any, index: number) => (
<span
className="icon-wrap"
key={index}
dangerouslySetInnerHTML={{ __html: getSvgCatch(item.icon) }}
></span>
))
);
}
return null;
}, [svgIsLoad]);
return iconL;
};
export default Box;
- .css 样式实例
.icon-wrap {
color: #8392b8;
}
.icon-wrap:hover {
color: red;
}
总结:
- 该篇文章为核心代码抽离,已删除掉工作中繁杂的业务代码,边界判断还需各位工程师根据业务自行解决。
- react 示例为简易 demo 未经测试。
有疑问的同学可以私信我、对帮助到同学欢迎大家收藏评论。