IntersectionObserver结合lazyload的首页csr模式下多模块加载优化方案

前言

笔者最近接连收到好几个做多模块官网的需求,这类需求一般是做一个长内容的pc官网,外加响应式能适配移动端即可。这类需求一般都有一下特性:

1.多模块 2.内容为主 3.图文视频直播元素丰富 4.长度高的几千像素,长滚动

这种元素 布局 丰富的官网固然非常美观,但是性能因素却令人担忧,因为如果不加处理,首页加载速度一定会非常慢,就像早高峰的南光高速一样。

因为直播元素使用了腾讯播放器sdk,在gzip模式下也能大到210kb,算上字体文件,如果一股脑加载的话,整个官网首页的大小可能得达到几M,完整加载可能要到数秒以上的时间。

接下来我会展示怎么处理这类性能问题,让整个项目实现秒开。

拆分模块,实现lazyload

as we all know,如果我们不加拆分模块,那么所有的内容都会被打包进一个js,这个js就会包含所有的内容。所以我们第一步,把首页的每一个模块单独抽离成为组件。

第二步,我们使用React的能力,将每个模块作为dynamic module import

截止到目前为止,我们已经初步完成了lazyload的工作,接下来,我们要做的工作就是如何,我们在pc浏览器把首页打开,只加载banner相关的,其他所有模块的东西不加载,这时候就要请出IntersectionObserver

IntersectionObserver

不知道读者们有没有经历过这样的需求,当你想要监控视窗内的元素在不在可视区,或者想知道元素在可视区的哪个位置,如果用常规js来写,兼容问题会让你头疼,于是这种需求被浏览器原生实现了。

于是乎,我就想到一种方案,给每一个模块提前生成一个占位元素,这样能撑起滚动,再结合IntersectionObserver,当滚动到对应的占位元素时,我再把真正的内容模块替换掉占位元素进行渲染。

代码实现如下,我先对IntersectionObserver进行了hook 封装

ts 复制代码
import { useEffect, useRef } from "react";

export default function useIntersectionObserver(eleKey: string, handlers = {} as any) {
    const { onShow = Function.prototype, onHide = Function.prototype, ifInit = true } = handlers;
    const obj = useRef(null as any);

    useEffect(() => {

        if (!ifInit) {
            return;
        }
        const observer = obj.current = new IntersectionObserver(changes => {

            for (const change of changes) {

                if (change.isIntersecting) {
                    onShow();
                } else {
                    onHide();
                }
            }
        }, {});
        observer.observe(document.querySelector(eleKey)!);


        return () => {
            obj.current && obj.current.disconnect();
        }
    }, [ifInit]);
}

再后来,我需要专门设计一个组件,既能产生一个占位元素,又能使用useIntersectionObserver进行模块替换

tsx 复制代码
import useIntersectionObserver from "@/hooks/useElementObserver";
import { useEffect, useMemo, useState } from "react";
import "./index.less";
import eventEmitter from "@/helpers/event";

/**
 * 懒加载模块
 */
export default function LazyModule(props) {
    const [importModule, setImportModule] = useState(false);
    const [module, setModule] = useState(null as any);

    const eleId = useMemo(() => `lazy${Date.now().toString(16)}-${props.moduleName}`, [props.moduleName]);

    useIntersectionObserver(`#${eleId}`, {
        onShow: () => {
            console.log(`%celement of ${eleId} is show`, 'color: #43bb88;font-size: 12px;');
            setImportModule(true)
        },
    });

    const loadModule = async () => {
        const Module = props.module;

        if (module) return;

        setModule(<Module />)
    }
    useEffect(() => {
        importModule && loadModule();
    }, [importModule]);

    useEffect(() => {
        const eventLoad = () => {
            setImportModule(i => !i ? true : i);
        };

        props.moduleName && eventEmitter.on(`lazy-load`, eventLoad);

        return () => {
            eventEmitter.off(`lazy-load`, eventLoad);
        }
    }, []);

    if (module) return module;

    return <div id={eleId} className="lazy-module">
        <svg className="placeImg" viewBox="0 0 1024 3024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5013" width="750" height="1440"><path d="M928 896H96a53.393333 53.393333 0 0 1-53.333333-53.333333V181.333333a53.393333 53.393333 0 0 1 53.333333-53.333333h832a53.393333 53.393333 0 0 1 53.333333 53.333333v661.333334a53.393333 53.393333 0 0 1-53.333333 53.333333zM96 170.666667a10.666667 10.666667 0 0 0-10.666667 10.666666v661.333334a10.666667 10.666667 0 0 0 10.666667 10.666666h832a10.666667 10.666667 0 0 0 10.666667-10.666666V181.333333a10.666667 10.666667 0 0 0-10.666667-10.666666z" fill="#5C5C66" p-id="5014"></path></svg>
    </div>
}

原理非常简单,一个简单的条件渲染,而我采用的占位元素只是一个简单的svg,因为svg不占请求资源,浏览器渲染压力也非常小。

最后的布局如下

效果

页面打开,只加载了必须的react/lodash/vendor等cdn资源,不加载模块等业务资源

当我滚动的时候,资源随着滚动的加载情况

浏览器渲染情况 FP总体在1秒以内,实现秒开,各项任务也是绿色的 没有明显的阻塞

红框内的资源都是滚动的时候时序加载的显示

以此实现了多模块/元素/资源的页面打开的最优解

相关推荐
weixin_382395237 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__7 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.7 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~8 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华9 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子11 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅11 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni12 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学13 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端