Next.js图片加载优化:预加载图片提升性能
解决Next.js滚动图片加载性能问题的预加载方案
核心思路
提前把所有需要的图片预加载到浏览器缓存,避免滚动时动态加载引发的卡顿;同时优化滚动事件处理逻辑,减少组件不必要的重渲染次数。
具体实现步骤
1. 添加图片预加载逻辑
组件挂载时,循环生成所有图片路径(从0.png到180.png),通过原生Image对象完成预加载,确保滚动时图片已在本地缓存中:
useEffect(() => { // 预加载所有图片资源 const preloadImages = () => { for (let i = 0; i <= 180; i++) { const img = new Image(); img.src = `/headphone/${i}.png`; } }; preloadImages(); // 滚动事件处理(后续优化防抖) const handleScroll = () => { const scrollPercentage = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 180; setHeadphones(scrollPercentage); }; window.addEventListener('scroll', handleScroll); return () => { window.removeEventListener('scroll', handleScroll); }; }, []);
2. 防抖优化滚动事件
滚动事件会高频触发,添加防抖函数减少setState调用频率,降低组件重渲染压力:
// 防抖工具函数 const debounce = (func: Function, delay: number) => { let timeoutId: NodeJS.Timeout; return (...args: any[]) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => func.apply(this, args), delay); }; }; // 在useEffect中替换为防抖后的处理函数 useEffect(() => { // ...预加载逻辑保持不变 const debouncedHandleScroll = debounce(() => { const scrollPercentage = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 180; setHeadphones(scrollPercentage); }, 16); // 约60fps间隔,平衡响应性和性能 window.addEventListener('scroll', debouncedHandleScroll); return () => { window.removeEventListener('scroll', debouncedHandleScroll); }; }, []);
3. 简化Image组件配置
预加载完成后,无需自定义loader,直接使用生成的imagePath作为src即可;同时移除priority属性(预加载已提前完成资源加载):
// 移除原有的imageLoader函数 const imagePath = `/headphone/${Math.round(headphones)}.png`; // 修改后的Image组件 <Image src={imagePath} alt={`Headphone image ${Math.round(headphones)}`} width={1920} height={1080} className='w-[calc(100vw-7px)] absolute z-10' />
完整修改后的代码
'use client' import Image from 'next/image'; import { useEffect, useState } from 'react'; export default function Home() { const [headphones, setHeadphones] = useState(0); // 防抖工具函数 const debounce = (func: Function, delay: number) => { let timeoutId: NodeJS.Timeout; return (...args: any[]) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => func.apply(this, args), delay); }; }; useEffect(() => { // 预加载所有图片 const preloadImages = () => { for (let i = 0; i <= 180; i++) { const img = new Image(); img.src = `/headphone/${i}.png`; } }; preloadImages(); const debouncedHandleScroll = debounce(() => { const scrollPercentage = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 180; setHeadphones(scrollPercentage); }, 16); window.addEventListener('scroll', debouncedHandleScroll); return () => { window.removeEventListener('scroll', debouncedHandleScroll); }; }, []); const imagePath = `/headphone/${Math.round(headphones)}.png`; return ( <main className='h-[100vh] w-[100vw] sticky top-0 flex flex-col justify-center items-center'> <Image src={imagePath} alt={`Headphone image ${Math.round(headphones)}`} width={1920} height={1080} className='w-[calc(100vw-7px)] absolute z-10' /> <div className='w-[100vw] flex flex-col items-end'> <div className='flex flex-col items-center'> <h1 className={`title text-center w-[7ch] m-[5rem] transition-fade-in ${headphones >= 19 ? 'hidden' : ''}`}>IMMERSE IT IN</h1> <p className={`buy transition-fade-in bg-[red] w-[15rem] rounded-full ${headphones >= 19 ? 'hidden' : ''}`}>BUY</p> </div> </div> <p className={`w-[15ch] transition-fade-in ${headphones <= 16 ? 'hidden' : headphones >= 25 && headphones <= 40 ? 'absolute right-0 bottom-[10%] m-24' : 'hidden'}`}><b className='font-300'>Noise reduction technology</b> for the Best immersion.</p> <p className={`w-[18ch] transition-fade-in ${headphones <= 16 ? 'hidden' : headphones >= 25 && headphones <= 40 ? 'absolute left-0 w-[22ch] text-left m-24' : 'hidden'}`}><b className='font-300'>Talk</b> instead of typing with our <b className='font-300'>IA powered</b> assistent. <b className='font-300'>Just ask.</b></p> <p className={`w-[21ch] transition-fade-in ${headphones <= 16 ? 'hidden' : headphones >= 50 && headphones <= 70 ? 'absolute right-0 bottom-[10%] m-24' : 'hidden'}`}>Revolutionary <b className='font-300'>charging technology</b>. Stay <b className='font-300'>connected</b>.</p> <p className={` transition-fade-in ${headphones <= 16 ? 'hidden' : headphones >= 50 && headphones <= 70 ? 'absolute left-0 top-0 w-[20ch] text-left m-24' : 'hidden'}`}><b className='font-300'>Charge in minutes</b> and <b className='font-300'>use for days.</b></p> <p className={` transition-fade-in ${headphones <= 16 ? 'hidden' : headphones >= 50 && headphones <= 70 ? 'absolute left-0 top-0 w-[20ch] text-left m-24' : 'hidden'}`}><b className='font-300'>Charge in minutes</b> and <b className='font-300'>use for days.</b></p> <div className='w-[100vw] flex flex-col items-center'> <p className={`text-center w-[100vw] text-[200px] transition-fade-in ${headphones <= 16 ? 'hidden' : headphones >= 70 && headphones <= 100 ? '' : 'hidden'}`}>Connect <b className='font-300'>Anywhere</b> on <b className='font-300'>anything</b>.</p> <p className={`text-center w-[100vw] text-[50px] transition-fade-in ${headphones <= 16 ? 'hidden' : headphones >= 70 && headphones <= 100 ? '' : 'hidden'}`}>Integrated Adapters for all your devices.</p> </div> <p className={` transition-fade-in ${headphones <= 16 ? 'hidden' : headphones >= 100 && headphones <= 160 ? 'absolute right-0 bottom-0 w-[15ch] text-[150px] text-right m-24' : 'hidden'}`}><b className='font-300'>2 GB</b> of <b className='font-300'>memory. Never stop vibing.</b> </p> <div className='w-[100vw] flex flex-col items-center'> <p className={`absolute text-center w-[100vw] text-[150px] tstroke transition-fade-in ${headphones <= 16 ? 'hidden' : headphones >= 160 ? 'transition-fade-out' : 'hidden'}`}><b className='font-300'>Bluethoth Integrated.</b></p> <p className={`text-center w-[100vw] text-[150px] transition-fade-in ${headphones <= 16 ? 'hidden' : headphones >= 160 ? '' : 'hidden'}`}><b className='font-300'>Bluethoth Integrated.</b></p> </div> </main> ) }
内容的提问来源于stack exchange,提问作者user22857802
相关产品推荐
相关产品推荐

