自制 vite SVG Sprites 插件
由于直接使用<img src={logo} class={s.logo} />加载 svg 时,是在页面渲染后再请求 svg 图片的,所以会导致 svg 渲染时间有所延迟。因此自制 Vite SVG Sprite 解决该问题
安装 svgo 和 svgStore
pnpm -g install svgo
pnpm install --save svgstore
创建 svgstore.js文件
/* eslint-disable */
import path from 'path'
import fs from 'fs'
import store from 'svgstore' // 用于制作 SVG Sprites
import { optimize } from 'svgo' // 用于优化 SVG 文件
export const svgstore = (options = {}) => {
const inputFolder = options.inputFolder || 'src/assets/icons';
return {
name: 'svgstore',
resolveId(id) {
if (id === '@svgstore') {
return 'svg_bundle.js'
}
},
load(id) {
if (id === 'svg_bundle.js') {
const sprites = store(options);
const iconsDir = path.resolve(inputFolder);
for (const file of fs.readdirSync(iconsDir)) {
const filepath = path.join(iconsDir, file);
const svgid = path.parse(file).name
let code = fs.readFileSync(filepath, { encoding: 'utf-8' });
sprites.add(svgid, code)
}
const { data: code } = optimize(sprites.toString({ inline: options.inline }), {
plugins: [
'cleanupAttrs', 'removeDoctype', 'removeComments', 'removeTitle', 'removeDesc',
'removeEmptyAttrs',
{ name: "removeAttrs", params: { attrs: "(data-name|data-xxx)" } }
]
})
return `const div = document.createElement('div')
div.innerHTML = \`${code}\`
const svg = div.getElementsByTagName('svg')[0]
if (svg) {
svg.style.position = 'absolute'
svg.style.width = 0
svg.style.height = 0
svg.style.overflow = 'hidden'
svg.setAttribute("aria-hidden", "true")
}
// listen dom ready event
document.addEventListener('DOMContentLoaded', () => {
if (document.body.firstChild) {
document.body.insertBefore(div, document.body.firstChild)
} else {
document.body.appendChild(div)
}
})`
}
}
}
}
tsconfig.node.json 加入 include
"include": [
"vite.config.ts",
"src/vite_plugins/**/*",
]
vite.config.ts 引入 svgstore()
// @ts-nocheck
import { svgstore } from "./src/vite_plugins/svgstore";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
svgstore(), //使用svgstore
...
],
});
全局引用svgstore
import '@svgstore'
svg的使用
<svg>
<use xlinkHref="#icon名" />
</svg>
自定义滑动事件
首先,封装一个自定义滑动的组件
useSwipe.tsx
import { computed, onMounted, onUnmounted, ref, Ref } from "vue";
type Point = {
x: number;
y: number;
};
interface Options {
beforeStart?: (e: TouchEvent) => void;
afterStart?: (e: TouchEvent) => void;
beforeMove?: (e: TouchEvent) => void;
afterMove?: (e: TouchEvent) => void;
beforeEnd?: (e: TouchEvent) => void;
afterEnd?: (e: TouchEvent) => void;
}
export const useSwipe = (
element: Ref<HTMLElement | undefined>,
options?: Options
) => {
const start = ref<Point>();
const end = ref<Point>();
const swiping = ref(false);
const distance = computed(() => {
if (!start.value || !end.value) {
return null;
}
return {
x: end.value.x - start.value.x,
y: end.value.y - start.value.y,
};
});
const direction = computed(() => {
if (!distance.value) {
return "";
}
const { x, y } = distance.value;
if (Math.abs(x) > Math.abs(y)) {
return x > 0 ? "right" : "left";
} else {
return y > 0 ? "down" : "up";
}
});
const onStart = (e: TouchEvent) => {
options?.beforeStart?.(e);
swiping.value = true;
end.value = start.value = {
x: e.touches[0].screenX,
y: e.touches[0].screenY,
};
options?.afterStart?.(e);
};
const onMove = (e: TouchEvent) => {
options?.beforeMove?.(e);
if (!start.value) {
return;
}
end.value = { x: e.touches[0].screenX, y: e.touches[0].screenY };
options?.afterMove?.(e);
};
const onEnd = (e: TouchEvent) => {
options?.beforeEnd?.(e);
swiping.value = false;
options?.afterEnd?.(e);
};
onMounted(() => {
if (!element.value) {
return;
}
element.value.addEventListener("touchstart", onStart);
element.value.addEventListener("touchmove", onMove);
element.value.addEventListener("touchend", onEnd);
});
onUnmounted(() => {
if (!element.value) {
return;
}
element.value.removeEventListener("touchstart", onStart);
element.value.removeEventListener("touchmove", onMove);
element.value.removeEventListener("touchend", onEnd);
});
return {
swiping,
direction,
distance,
};
};
注意:interface拥有继承属性,后面只能写对象。而type可以用来写简单类型和对象。inferface可以循环用自身,而type则不可以
然后在需要用的文件内引用自定义的滑动组件
import { defineComponent, ref, Transition, VNode, watchEffect } from "vue";
import { RouteLocationNormalizedLoaded, RouterView, useRoute, useRouter } from "vue-router";
import { useSwipe } from "../hooks/useSwipe";
import { throttle } from "../shared/throttle";
import s from "./Welcome.module.scss";
const pushMap: Record<string, string> = {
Welcome1: "/welcome/2",
Welcome2: "/welcome/3",
Welcome3: "/welcome/4",
Welcome4: "/items"
};
export const Welcome = defineComponent({
setup: (props, context) => {
const main = ref<HTMLElement>();
const { direction, swiping } = useSwipe(main, {
beforeStart: (e) => e.preventDefault()
});
const route = useRoute();
const router = useRouter();
const replace = throttle(() => {
const name = (route.name || "Welcome1").toString();
router.replace(pushMap[name]);
}, 500);
watchEffect(() => {
if (swiping.value && direction.value === "left") {
replace();
}
});
return () => (
<div class={s.wrapper}>
<header>
<svg>
<use xlinkHref="#lamb"></use>
</svg>
<h1>小羊记账</h1>
</header>
<main class={s.main} ref={main}>
<RouterView name="main">
{({ Component: X, route: R }: { Component: VNode; route: RouteLocationNormalizedLoaded }) => (
<Transition
enterFromClass={s.slide_fade_enter_from}
enterActiveClass={s.slide_fade_enter_active}
leaveToClass={s.slide_fade_leave_to}
leaveActiveClass={s.slide_fade_leave_active}
>
{X}
</Transition>
)}
</RouterView>
</main>
<footer>
<RouterView name="footer" />
</footer>
</div>
);
}
});