VUE

useLazyLoad Composable

Reactive lazy loading for images and components with IntersectionObserver

Vue3ComposablesLazyLoadIntersectionObserverPerformance

Code

import { ref, onMounted, onUnmounted, type Ref } from 'vue';

export function useLazyLoad(
  target: Ref<HTMLElement | null>,
  options: IntersectionObserverInit = { rootMargin: '200px 0px' }
) {
  const isLoaded = ref(false);
  const isIntersecting = ref(false);
  let observer: IntersectionObserver | null = null;

  const loadContent = () => {
    __TOKEN_18__ (isLoaded.value) return;
    isLoaded.value = true;
    // For images: set src from data-src
    __TOKEN_20__ (target.value?.tagName === 'IMG') {
      const img = target.value as HTMLImageElement;
      __TOKEN_22__ (img.dataset.src) {
        img.src = img.dataset.src;
        img.removeAttribute('data-src');
      }
    }
  };

  const handleIntersect = ([entry]: IntersectionObserverEntry[]) => {
    isIntersecting.value = entry.isIntersecting;
    __TOKEN_24__ (entry.isIntersecting) {
      loadContent();
      observer?.unobserve(entry.target);
    }
  };

  onMounted(() => {
    __TOKEN_25__ (!target.value || !IntersectionObserver) {
      loadContent();
      return;
    }

    observer = new IntersectionObserver(handleIntersect, options);
    observer.observe(target.value);
  });

  onUnmounted(() => {
    __TOKEN_28__ (observer && target.value) {
      observer.unobserve(target.value);
      observer.disconnect();
    }
  });

  return { isLoaded, isIntersecting, loadContent };
}

// Usage example
// const imgRef = ref<HTMLImageElement | null>(null);
// const { isLoaded } = useLazyLoad(imgRef);
// Template: <img ref="imgRef" data-src="/large-image.jpg" v-if="isLoaded">