React/Next.js中不使用<Suspense>或onLoad的加载屏最优实现方案
最优加载屏实现方案
针对你的需求,以下是覆盖首次访问和页面切换场景、精准控制加载时机的实现方案,解决你之前遇到的所有问题:
核心思路
- 区分「DOM就绪」和「全部资源加载完成」:监听全局事件确保DOM、图片、样式、脚本都加载完毕
- 路由联动:页面切换时重置加载状态,结合当前页面的业务逻辑(数据请求、组件渲染)控制加载屏关闭时机
- 平滑过渡:用CSS实现加载屏的淡出动画,避免生硬跳转
具体实现
1. 全局加载器组件(App.jsx)
结合React Router监听路由变化,同时监听全局资源加载状态:
import { useState, useEffect } from 'react'; import { useLocation, Routes, Route } from 'react-router-dom'; import ScreenLoader from './ScreenLoader'; import Home from './pages/Home'; import About from './pages/About'; function App() { const [isLoading, setIsLoading] = useState(true); const location = useLocation(); // 首次加载:监听全局资源加载完成 useEffect(() => { const handleGlobalLoad = () => { // 延迟300ms确保页面动画初始化完成,避免加载屏突然消失 setTimeout(() => setIsLoading(false), 300); }; // 如果DOM已经就绪,直接触发 if (document.readyState === 'complete') { handleGlobalLoad(); } else { window.addEventListener('load', handleGlobalLoad); return () => window.removeEventListener('load', handleGlobalLoad); } }, []); // 路由切换:重置加载状态,等待当前页面加载完成 useEffect(() => { setIsLoading(true); // 监听页面自定义加载完成事件(由页面组件触发) const handlePageLoaded = () => setIsLoading(false); window.addEventListener('pageLoaded', handlePageLoaded); return () => { window.removeEventListener('pageLoaded', handlePageLoaded); }; }, [location.pathname]); return ( <> <ScreenLoader isVisible={isLoading} /> <div className="app-container"> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </div> </> ); } export default App;
2. 加载屏样式(ScreenLoader.css)
实现平滑淡出过渡,避免遮挡页面交互:
.screen-loader { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: #ffffff; display: flex; align-items: center; justify-content: center; z-index: 9999; /* 平滑过渡效果 */ transition: opacity 0.4s ease-in-out, visibility 0.4s ease-in-out; } .screen-loader.hidden { opacity: 0; visibility: hidden; pointer-events: none; /* 加载屏隐藏后不再拦截点击 */ } /* 加载动画示例(可替换成你的动画) */ .loader-spinner { width: 48px; height: 48px; border: 5px solid #f3f3f3; border-top-color: #3498db; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } }
3. 页面组件内控制加载状态(以Home.jsx为例)
当页面数据请求、内部资源加载完成后,通知全局加载器关闭:
import { useState, useEffect } from 'react'; function Home() { const [pageData, setPageData] = useState(null); useEffect(() => { const loadPageResources = async () => { try { // 1. 加载页面数据 const res = await fetch('/api/home-data'); const data = await res.json(); setPageData(data); // 2. 等待页面内所有图片加载完成 const pageImages = document.querySelectorAll('.home-page img'); if (pageImages.length > 0) { await Promise.all( Array.from(pageImages).map(img => { if (img.complete) return Promise.resolve(); return new Promise(resolve => img.onload = resolve); }) ); } // 3. 通知全局加载器关闭 window.dispatchEvent(new CustomEvent('pageLoaded')); } catch (err) { console.error('页面加载失败:', err); // 即使出错也关闭加载屏,避免卡住用户 window.dispatchEvent(new CustomEvent('pageLoaded')); } }; loadPageResources(); }, []); if (!pageData) return null; // 或页面内局部加载动画 return ( <div className="home-page"> {/* 页面内容 */} <img src="/hero-banner.jpg" alt="Hero Banner" /> <h1>{pageData.title}</h1> </div> ); } export default Home;
关键优化点
- 摒弃
setTimeout硬编码延迟:通过window.load事件监听全局资源,结合页面自定义事件精准控制关闭时机 - 修复
onLoad无效问题:div元素没有onLoad事件,改用全局window.load或针对具体资源(如图片)监听加载 - 路由切换适配:通过
useLocation监听路由变化,每次切换时重置加载状态,避免页面切换卡顿 - 平滑过渡:用CSS
transition实现加载屏淡出,提升用户体验
内容的提问来源于stack exchange,提问作者Olujimi David
相关产品推荐
相关产品推荐

