在Vue-cli项目,通常会用到svg-sprite-loader插件对 svg 图标进行一个预处理,再通过require.context方法实现对图标的自动引入,然后进行一个全局的icon组件封装,实现优雅的封装使用效果。
到了vite,由于缺少类似svg-sprite-loader的插件,为了实现类似的封装方式,借用了iconfont 关于 svg symbol的实现方法。
实现
通过 vite的import.meta.globEager实现svg icon的自动引入,然后遍历把每个svg icon的path标签和相关路径属性插入到symbol标签里,再由函数return一个被 svg标签 包裹的多个symbol的dom结构字符串即可,具体代码如下。
代码
用于输出字符串dom的函数
function domToString(node) {
let tmpNode = document.createElement("div");
tmpNode.appendChild(node);
let str = tmpNode.innerHTML;
tmpNode = node = null; // 解除引用,以便于垃圾回收
return str;
}
遍历path,生成path标签和相关路径属性的函数
function createEl(children) {
let frag = document.createDocumentFragment();
children.map((item) => {
let node = document.createElement(item.type);
for (const key in item.props) {
if (key && key !== "fill") node.setAttribute(key, item.props[key]);
}
if (item.children) {
node.appendChild(createEl(item.children));
}
frag.appendChild(node);
});
return frag;
}
实现过程
//引入svg图标
const modules = import.meta.globEager("./svg/*.svg");
let svgEl = document.createElement("svg");
for (const path in modules) {
//用于赋值symbol的id
let id = path.split("/")[2].split(".svg")[0];
// dom节点不一定是svg, 还可能包含 icon作者信息
let tmp = modules[path].render();
let node;
if (tmp.type !== "svg") {
tmp.children.map((item) => {
if (item.type === "svg") node = item;
});
} else {
node = tmp;
}
let width = node.props.width;
let height = node.props.height;
let viewBox = node.props.viewBox
? node.props.viewBox
: `0 0 ${width} ${height}`;
let symbol = document.createElement("symbol");
symbol.setAttribute("viewBox", viewBox);
symbol.setAttribute("id", id);
if (node.children[0].type !== "symbol") {
symbol.appendChild(createEl(node.children));
} else {
symbol.appendChild(createEl(node.children[0].children));
}
svgEl.appendChild(symbol);
}
let svgStr = domToString(svgEl);
把dom字符串挂载到iconfont函数上
(window._iconfont_svg_string_3788124 = svgStr),
(function (l) {
var t = (t = document.getElementsByTagName("script"))[t.length - 1],
a = t.getAttribute("data-injectcss"),
t = t.getAttribute("data-disable-injectsvg");
if (!t) {
var e,
h,
c,
o,
n,
i = function (t, a) {
a.parentNode.insertBefore(t, a);
};
if (a && !l.__iconfont__svg__cssinject__) {
l.__iconfont__svg__cssinject__ = !0;
try {
document.write(
"<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>"
);
} catch (t) {
console && console.log(t);
}
}
(e = function () {
var t,
a = document.createElement("div");
(a.innerHTML = l._iconfont_svg_string_3788124),
(a = a.getElementsByTagName("svg")[0]) &&
(a.setAttribute("aria-hidden", "true"),
(a.style.position = "absolute"),
(a.style.width = 0),
(a.style.height = 0),
(a.style.overflow = "hidden"),
(a = a),
(t = document.body).firstChild
? i(a, t.firstChild)
: t.appendChild(a));
}),
document.addEventListener
? ~["complete", "loaded", "interactive"].indexOf(document.readyState)
? setTimeout(e, 0)
: ((h = function () {
document.removeEventListener("DOMContentLoaded", h, !1), e();
}),
document.addEventListener("DOMContentLoaded", h, !1))
: document.attachEvent &&
((c = e),
(o = l.document),
(n = !1),
s(),
(o.onreadystatechange = function () {
"complete" == o.readyState &&
((o.onreadystatechange = null), d());
}));
}
function d() {
n || ((n = !0), c());
}
function s() {
try {
o.documentElement.doScroll("left");
} catch (t) {
return void setTimeout(s, 50);
}
d();
}
})(window);
最后,再进行svg-icon 的组件封装, 并全局注册
// src/components/SvgIcon.vue
<template>
<svg>
<use :xlink:href="`#${symbolId}`" />
</svg>
</template>
<script setup>
defineProps({
symbolId: {
type: String,
default: "",
require: true,
},
});
</script>
<style lang="scss" scoped>
svg {
width: 1em;
height: 1em;
fill: currentColor;
overflow: hidden;
}
</style>
// main.js
import "./icons/index.js";
import SvgIcon from "@/components/SvgIcon.vue";
app.component("SvgIcon", SvgIcon);
至此,就已经能实现vue2上对于icon的优雅使用效果
总结
查阅了一些文章,发现大多使用vite-svg-loader,但对于webpack上的svg-sprite-loader还是难受了点,如果有更好的方案,还请留言分享下,感谢。
完整代码
// src/
const modules = import.meta.globEager("./svg/*.svg");
let svgEl = document.createElement("svg");
for (const path in modules) {
let id = path.split("/")[2].split(".svg")[0];
// dom节点不一定是svg, 还可能包含 icon作者信息
let tmp = modules[path].render();
let node;
if (tmp.type !== "svg") {
tmp.children.map((item) => {
if (item.type === "svg") node = item;
});
} else {
node = tmp;
}
let width = node.props.width;
let height = node.props.height;
let viewBox = node.props.viewBox
? node.props.viewBox
: `0 0 ${width} ${height}`;
let symbol = document.createElement("symbol");
symbol.setAttribute("viewBox", viewBox);
symbol.setAttribute("id", id);
if (node.children[0].type !== "symbol") {
symbol.appendChild(createEl(node.children));
} else {
symbol.appendChild(createEl(node.children[0].children));
}
svgEl.appendChild(symbol);
}
let svgStr = domToString(svgEl);
/**
*
* @param {Dom} node dom节点
* @returns dom结构的字符串
*/
function domToString(node) {
let tmpNode = document.createElement("div");
tmpNode.appendChild(node);
let str = tmpNode.innerHTML;
tmpNode = node = null; // 解除引用,以便于垃圾回收
return str;
}
/**
*
* @param {Array} children 用于遍历的children
* @returns
*/
function createEl(children) {
let frag = document.createDocumentFragment();
children.map((item) => {
let node = document.createElement(item.type);
for (const key in item.props) {
if (key && key !== "fill") node.setAttribute(key, item.props[key]);
}
if (item.children) {
node.appendChild(createEl(item.children));
}
frag.appendChild(node);
});
return frag;
}
(window._iconfont_svg_string_3788124 = svgStr),
(function (l) {
var t = (t = document.getElementsByTagName("script"))[t.length - 1],
a = t.getAttribute("data-injectcss"),
t = t.getAttribute("data-disable-injectsvg");
if (!t) {
var e,
h,
c,
o,
n,
i = function (t, a) {
a.parentNode.insertBefore(t, a);
};
if (a && !l.__iconfont__svg__cssinject__) {
l.__iconfont__svg__cssinject__ = !0;
try {
document.write(
"<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>"
);
} catch (t) {
console && console.log(t);
}
}
(e = function () {
var t,
a = document.createElement("div");
(a.innerHTML = l._iconfont_svg_string_3788124),
(a = a.getElementsByTagName("svg")[0]) &&
(a.setAttribute("aria-hidden", "true"),
(a.style.position = "absolute"),
(a.style.width = 0),
(a.style.height = 0),
(a.style.overflow = "hidden"),
(a = a),
(t = document.body).firstChild
? i(a, t.firstChild)
: t.appendChild(a));
}),
document.addEventListener
? ~["complete", "loaded", "interactive"].indexOf(document.readyState)
? setTimeout(e, 0)
: ((h = function () {
document.removeEventListener("DOMContentLoaded", h, !1), e();
}),
document.addEventListener("DOMContentLoaded", h, !1))
: document.attachEvent &&
((c = e),
(o = l.document),
(n = !1),
s(),
(o.onreadystatechange = function () {
"complete" == o.readyState &&
((o.onreadystatechange = null), d());
}));
}
function d() {
n || ((n = !0), c());
}
function s() {
try {
o.documentElement.doScroll("left");
} catch (t) {
return void setTimeout(s, 50);
}
d();
}
})(window);