IntersectionObserver

一些基础知识

const observer = new IntersectionObserver(callback, {
  root: null, // 根元素(默认视窗)
  rootMargin: "0px", // 根元素边界扩展(类似 CSS margin)
  threshold: 0.5, // 触发阈值(0-1 或数组 [0, 0.25, 1])
});

图片懒加载

const vLazy = (observer: IntersectionObserver) => {
  return {
    beforeMount: (el: HTMLImageElement, binding : DirectiveBinding) => {
      el.classList.add("op-lazyload");
      const { value } = binding;
      el.dataset.origin = value;
      observer.observe(el);
    },
  };
};

const lazyPlugin = {
  install(app : App) {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach((item) => {
        if (item.isIntersecting) {
          const el = item.target as HTMLImageElement;
          el.src = el.dataset.origin as string;
          el.classList.remove("op-lazyload");
          observer.unobserve(el);
        }
      });
    });
  },
};

组件懒加载

import {
  h,
  defineAsyncComponent,
  defineComponent,
  ref,
  onMounted,
  AsyncComponentLoader,
  Component,
} from 'vue';

type ComponentResolver = (component: Component) => void

export const lazyLoadComponentIfVisible = ({
  // 目标组件加载函数
  componentLoader,
  // 目标组件加载时使用的占位组件
  loadingComponent,
  errorComponent,
  delay,
  timeout
}: {
  componentLoader: AsyncComponentLoader;
  loadingComponent: Component;
  errorComponent?: Component;
  delay?: number;
  timeout?: number;
}) => {
  let resolveComponent: ComponentResolver;

  return defineAsyncComponent({
    // the loader function
    loader: () => {
      return new Promise((resolve) => {
        resolveComponent = resolve as ComponentResolver;
      });
    },
    loadingComponent: defineComponent({
      setup() {
        const elRef = ref();

        async function loadComponent() {
            const component = await componentLoader()
            resolveComponent(component)
        }

        onMounted(async() => {
          if (!('IntersectionObserver' in window)) {
            await loadComponent();
            return;
          }

          const observer = new IntersectionObserver((entries) => {
            if (!entries[0].isIntersecting) {
              return;
            }
            observer.unobserve(elRef.value);
            await loadComponent();
          });
          observer.observe(elRef.value);
        });

        return () => {
          return h('div', { ref: elRef }, loadingComponent);
        };
      },
    }),
    delay,
    errorComponent,
    timeout,
  });
};

使用

<script setup lang="ts">
import Loading from './components/Loading.vue';
import { lazyLoadComponentIfVisible } from './utils';

const LazyLoaded = lazyLoadComponentIfVisible({
  componentLoader: () => import('./components/HelloWorld.vue'),
  loadingComponent: Loading,
});
</script>

<template>
  <LazyLoaded />
</template>