useSnapshot.ts 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import { tryOnMounted, tryOnUnmounted } from '@vueuse/core';
  2. import { snapdom } from '@zumer/snapdom';
  3. export interface SnapshotOption {
  4. immediate?: boolean;
  5. }
  6. type SnapshotTspl = (total?: number) => string;
  7. type SnapshotTemplate = (base64: string) => SnapshotTspl;
  8. export function useSnapshot<P extends Record<string, any>>(loader: () => Promise<{ default: Component }>, props?: P, option?: SnapshotOption) {
  9. const snapshot = shallowRef<string>();
  10. const tspl = shallowRef<SnapshotTspl | void>();
  11. let app: ReturnType<typeof createApp> | null = null;
  12. let container: HTMLDivElement | null = null;
  13. const mounted = (component: Component, _props?: Partial<P>) => {
  14. if (container == null) return;
  15. const { promise, resolve } = Promise.withResolvers<SnapshotTemplate>();
  16. app = createApp(component, { ...props, ..._props, onRendered: resolve });
  17. app.mount(container);
  18. return promise;
  19. };
  20. const unmounted = () => {
  21. app?.unmount();
  22. app = null;
  23. };
  24. const capture = async (template?: SnapshotTemplate) => {
  25. if (container == null) return;
  26. const dom = await snapdom(container, { scale: 1 });
  27. const img = await dom.toPng({ scale: 2 });
  28. snapshot.value = img.src;
  29. tspl.value = template?.(img.src);
  30. };
  31. async function render(props?: Partial<P>, forced = false): Promise<{ snapshot: string; tspl: SnapshotTspl }> {
  32. if (forced) unmounted();
  33. if (forced || !snapshot.value) {
  34. const { default: component } = await loader();
  35. const tspl = await mounted(component, props);
  36. await capture(tspl);
  37. unmounted();
  38. }
  39. return {
  40. snapshot: snapshot.value!!,
  41. tspl: tspl.value!!,
  42. };
  43. }
  44. tryOnMounted(() => {
  45. container = document.createElement('div');
  46. container.style.position = 'fixed';
  47. container.style.left = '-99999px';
  48. container.style.top = '-99999px';
  49. container.style.zIndex = '-1';
  50. document.body.appendChild(container);
  51. if (option?.immediate) render().then();
  52. });
  53. tryOnUnmounted(() => {
  54. if (container) document.body.removeChild(container);
  55. container = null;
  56. unmounted();
  57. snapshot.value = '';
  58. tspl.value = void 0;
  59. });
  60. return { snapshot, tspl, render };
  61. }