Skip to content

Instantly share code, notes, and snippets.

@unrevised6419
Last active January 25, 2025 11:19
Show Gist options
  • Save unrevised6419/4a959951c148f0f277fdf8bca152aaa5 to your computer and use it in GitHub Desktop.
Save unrevised6419/4a959951c148f0f277fdf8bca152aaa5 to your computer and use it in GitHub Desktop.
Vue InlineSvg Implementation
<script setup lang="ts">
import { onMounted, ref } from "vue";
import InlineSvg from "./components/InlineSvg.vue";
const firstSrc = `https://upload.wikimedia.org/wikipedia/commons/9/95/Vue.js_Logo_2.svg`;
const firstStroke = "blue";
const secondSrc = `https://upload.wikimedia.org/wikipedia/commons/3/30/React_Logo_SVG.svg`;
const secondStroke = "green";
const src = ref(firstSrc);
const stroke = ref(firstStroke);
onMounted(() => {
setInterval(() => {
src.value = src.value === firstSrc ? secondSrc : firstSrc;
stroke.value = stroke.value === firstStroke ? secondStroke : firstStroke;
}, 2000);
});
</script>
<template>
<div>
<InlineSvg :src="src" :stroke="stroke" alt="Company Logo" client-only />
</div>
</template>
<template>
<component :is="vnode" />
</template>
<script setup lang="ts">
import {
h,
onServerPrefetch,
shallowRef,
watch,
type SVGAttributes,
type VNodeChild,
createTextVNode,
useAttrs,
type WatchCallback,
Fragment,
} from "vue";
// @ts-expect-error
import parses from "html-parse-stringify";
interface OwnProps {
src: string;
clientOnly?: boolean;
alt: string;
}
interface Props extends OwnProps, /* @vue-ignore */ SVGAttributes {}
defineOptions({ inheritAttrs: false });
const props = defineProps<Props>();
const attrs = useAttrs() as SVGAttributes;
const vnode = shallowRef<VNodeChild>(undefined);
onServerPrefetch(async () => {
if (props.clientOnly) return;
vnode.value = await createVNode(props.src, props.alt, attrs);
});
if (typeof window !== "undefined" && !props.clientOnly) {
vnode.value = await createVNode(props.src, props.alt, attrs);
}
let run = props.clientOnly;
const callback: WatchCallback<[OwnProps, SVGAttributes]> = async ([p, a]) => {
if (typeof window === "undefined") return;
if (run) {
vnode.value = await createVNode(p.src, p.alt, a);
} else {
run = true;
}
};
watch([() => props, () => attrs], callback, {
flush: "post",
deep: true,
immediate: true,
});
</script>
<script lang="ts">
async function createVNode(src: string, alt: string, attrs: SVGAttributes) {
try {
const response = await fetch(src, { headers: { Accept: "image/svg+xml" } });
if (!response.ok) {
throw new Error("Failed to fetch SVG");
}
const xmlText = await response.text();
const svgText = xmlText.replace(/<\?xml.*?\?>/, "").trim();
if (!svgText.startsWith("<svg")) {
throw new Error("Invalid SVG");
}
const ast = parses.parse(svgText);
// @ts-expect-error
return h(Fragment, render(ast, attrs));
} catch (error) {
console.error(error);
return createTextVNode(alt);
}
}
export function isNode(node: { type: any }): boolean | boolean {
return typeof node === "object" && typeof node.type !== "undefined";
}
type TagNode = {
type: "tag";
name: string;
attrs: Record<string, string>;
children: Node[];
};
type TextNode = {
type: "text";
content: string;
};
type Node = TagNode | TextNode;
function render(node: Node | Node[], attrs: SVGAttributes = {}): VNodeChild {
if (Array.isArray(node)) return node.map((n) => render(n, attrs));
if (!isNode(node)) return;
if (node.type === "text") return node.content;
if (node.type === "tag") {
const props = { ...node.attrs, ...attrs };
const children = node.children.map((c) => render(c));
return h(node.name, props, children);
}
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment