列表页图片多、数据量大时,懒加载能明显减少首屏流量与白屏时间。本文提供两个互补方案:图片懒加载指令 v-lazy 用 IntersectionObserver 判定元素是否进入视口;滚动加载 Hook 在页面快到底时自动调用下一页接口。

图片懒加载指令

// src/directives/lazy.js
const lazy = {
    mounted(el, binding) {
        // 真实地址暂存到 dataset,避免直接加载
        el.dataset.src = binding.value;
        const observer = new IntersectionObserver((entries) => {
            entries.forEach((entry) => {
                if (entry.isIntersecting) {
                    el.src = el.dataset.src;
                    observer.unobserve(el);   // 加载后停止观察
                }
            });
        });
        observer.observe(el);
    }
};
export default lazy;

滚动加载 Hook

// src/hooks/useInfiniteScroll.js
import { onMounted, onUnmounted, ref } from "vue";

export function useInfiniteScroll(loadFn) {
    const loading = ref(false);
    const finished = ref(false);

    const onScroll = async () => {
        const doc = document.documentElement;
        const distance = doc.scrollHeight - doc.scrollTop - doc.clientHeight;
        if (distance > 200 || loading.value || finished.value) return;

        loading.value = true;
        try {
            const hasMore = await loadFn();
            if (!hasMore) finished.value = true;
        } finally {
            loading.value = false;
        }
    };

    onMounted(() => window.addEventListener("scroll", onScroll));
    onUnmounted(() => window.removeEventListener("scroll", onScroll));

    return { loading, finished };
}

页面使用

图片元素写 <img v-lazy="item.cover" />,列表页 onMounted 调一次 loadFn 拉第一页,后续触底自动续页;loadFn 返回布尔值表示是否还有下一页。

注意

  • 容器内滚动而不是整页滚动时,需把监听目标换成对应滚动容器的 DOM;
  • IntersectionObserver 支持度较广,低版本浏览器可降级为监听 scroll 加 getBoundingClientRect;
  • 移动端注意 passive 事件与滚动性能,避免加载卡顿。

小结

懒加载与无限滚动是长列表页性能优化的一对组合拳,抽成指令与 Hook 后业务页面只需配置数据来源即可。