自制 vite SVG Sprites 插件 及 自定义滑动事件

214 阅读2分钟

自制 vite SVG Sprites 插件

由于直接使用<img src={logo} class={s.logo} />加载 svg 时,是在页面渲染后再请求 svg 图片的,所以会导致 svg 渲染时间有所延迟。因此自制 Vite SVG Sprite 解决该问题

安装 svgo 和 svgStore

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>
    );
  }
});