news 2026/6/10 8:11:01

JavaScript实战③|图片懒加载与无限滚动,性能优化技巧

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaScript实战③|图片懒加载与无限滚动,性能优化技巧

author: 专注前端开发,分享JavaScript干货
title: JavaScript实战③|图片懒加载与无限滚动,性能优化技巧
update: 2026-04-28
tags: JavaScript,实战项目,懒加载,无限滚动,性能优化,IntersectionObserver

作者:专注前端开发,分享JavaScript干货
更新时间:2026年4月
适合人群:掌握JS基础,想学习性能优化技巧的开发者


前言:为什么需要懒加载?

网页加载大量图片时:

  • 首屏加载慢
  • 浪费用户流量
  • 页面卡顿

懒加载(Lazy Loading)让图片进入视口才加载,大幅提升性能。


一、图片懒加载

1.1 传统方式(scroll 事件)

// 获取所有需要懒加载的图片constlazyImages=document.querySelectorAll('img[data-src]');functionlazyLoad(){lazyImages.forEach(img=>{// 判断图片是否进入视口constrect=img.getBoundingClientRect();constisInViewport=rect.top<window.innerHeight&&rect.bottom>0;if(isInViewport&&img.dataset.src){img.src=img.dataset.src;img.removeAttribute('data-src');}});}// 监听滚动事件(需要节流)window.addEventListener('scroll',throttle(lazyLoad,200));window.addEventListener('resize',throttle(lazyLoad,200));// 页面加载时执行一次lazyLoad();// 节流函数functionthrottle(fn,delay){letlastTime=0;returnfunction(...args){constnow=Date.now();if(now-lastTime>=delay){fn.apply(this,args);lastTime=now;}};}

1.2 现代方式(IntersectionObserver)

// 使用 IntersectionObserver API(更现代、更高效)constlazyImages=document.querySelectorAll('img[data-src]');constimageObserver=newIntersectionObserver((entries,observer)=>{entries.forEach(entry=>{if(entry.isIntersecting){constimg=entry.target;img.src=img.dataset.src;// 加载完成后移除>.onload=()=>{img.removeAttribute('data-src');};// 停止观察这张图片observer.unobserve(img);}});},{rootMargin:'50px 0px',// 提前 50px 开始加载threshold:0.01});lazyImages.forEach(img=>imageObserver.observe(img));

1.3 HTML 结构

<!-- 懒加载图片 --><imgdata-src="large-image.jpg"src="placeholder.jpg"alt="描述"class="lazy-image"><style>.lazy-image{opacity:0;transition:opacity 0.3s;}.lazy-image[src]:not([data-src]){opacity:1;}</style>

二、无限滚动(Infinite Scroll)

2.1 实现原理

classInfiniteScroll{constructor(options){this.container=document.querySelector(options.container);this.loadMore=options.loadMore;this.loading=false;this.hasMore=true;this.page=1;this.init();}init(){// 使用 IntersectionObserver 监听底部元素constsentinel=document.createElement('div');sentinel.className='scroll-sentinel';sentinel.style.height='10px';this.container.appendChild(sentinel);this.observer=newIntersectionObserver((entries)=>{entries.forEach(entry=>{if(entry.isIntersecting&&!this.loading&&this.hasMore){this.loadNextPage();}});},{rootMargin:'100px 0px'});this.observer.observe(sentinel);// 初始加载this.loadNextPage();}asyncloadNextPage(){if(this.loading||!this.hasMore)return;this.loading=true;this.showLoading();try{constresult=awaitthis.loadMore(this.page);if(result.data.length===0){this.hasMore=false;this.showNoMore();}else{this.renderItems(result.data);this.page++;}}catch(error){console.error('加载失败:',error);this.showError();}finally{this.loading=false;this.hideLoading();}}showLoading(){constloader=document.createElement('div');loader.className='loading-indicator';loader.innerHTML='加载中...';this.container.appendChild(loader);}hideLoading(){constloader=this.container.querySelector('.loading-indicator');if(loader)loader.remove();}showNoMore(){constnoMore=document.createElement('div');noMore.className='no-more';noMore.innerHTML='没有更多内容了';noMore.style.cssText='text-align: center; padding: 20px; color: #999;';this.container.appendChild(noMore);}showError(){consterror=document.createElement('div');error.className='error-message';error.innerHTML='加载失败,<a href="#" onclick="location.reload()">点击重试</a>';this.container.appendChild(error);}renderItems(items){// 子类实现具体的渲染逻辑}}

2.2 使用示例

// 图片列表无限滚动classImageGalleryextendsInfiniteScroll{constructor(){super({container:'.gallery',loadMore:async(page)=>{// 模拟 API 请求constresponse=awaitfetch(`/api/images?page=${page}`);returnresponse.json();}});}renderItems(images){consthtml=images.map(img=>`<div class="gallery-item"> <img>${img.url}" src="placeholder.jpg" alt="${img.title}"> <p>${img.title}</p> </div>`).join('');// 插入到 sentinel 之前constsentinel=this.container.querySelector('.scroll-sentinel');sentinel.insertAdjacentHTML('beforebegin',html);// 对新添加的图片启用懒加载constnewImages=this.container.querySelectorAll('img[data-src]');newImages.forEach(img=>imageObserver.observe(img));}}// 初始化constgallery=newImageGallery();

三、完整示例:图片画廊

<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><title>图片懒加载 + 无限滚动</title><style>*{margin:0;padding:0;box-sizing:border-box;}body{font-family:sans-serif;background:#f5f5f5;}.gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:20px;padding:20px;max-width:1200px;margin:0 auto;}.gallery-item{background:white;border-radius:8px;overflow:hidden;box-shadow:0 2px 8pxrgba(0,0,0,0.1);}.gallery-item img{width:100%;height:200px;object-fit:cover;opacity:0;transition:opacity 0.3s;background:#e0e0e0;}.gallery-item img.loaded{opacity:1;}.gallery-item p{padding:15px;color:#333;}.loading-indicator{grid-column:1 / -1;text-align:center;padding:40px;color:#666;}</style></head><body><divclass="gallery"id="gallery"></div><script>// 模拟图片数据constmockImages=[{url:'https://picsum.photos/400/300?random=1',title:'图片 1'},{url:'https://picsum.photos/400/300?random=2',title:'图片 2'},{url:'https://picsum.photos/400/300?random=3',title:'图片 3'},// ... 更多图片];// 懒加载 ObserverconstimageObserver=newIntersectionObserver((entries)=>{entries.forEach(entry=>{if(entry.isIntersecting){constimg=entry.target;img.src=img.dataset.src;img.onload=()=>{img.classList.add('loaded');img.removeAttribute('data-src');};imageObserver.unobserve(img);}});},{rootMargin:'50px'});// 无限滚动letpage=1;letloading=false;asyncfunctionloadMoreImages(){if(loading)return;loading=true;// 模拟 API 延迟awaitnewPromise(resolve=>setTimeout(resolve,1000));constgallery=document.getElementById('gallery');// 生成 10 张图片for(leti=0;i<10;i++){constrandomId=page*10+i;constitem=document.createElement('div');item.className='gallery-item';item.innerHTML=`<img>${randomId}" alt="图片${randomId}"> <p>图片${randomId}</p>`;gallery.appendChild(item);// 观察新图片imageObserver.observe(item.querySelector('img'));}page++;loading=false;}// 监听滚动constscrollObserver=newIntersectionObserver((entries)=>{entries.forEach(entry=>{if(entry.isIntersecting){loadMoreImages();}});},{rootMargin:'200px'});// 创建底部触发元素consttrigger=document.createElement('div');trigger.style.height='10px';document.body.appendChild(trigger);scrollObserver.observe(trigger);// 初始加载loadMoreImages();</script></body></html>

四、知识点回顾

知识点应用
IntersectionObserver监听元素是否进入视口
data-src存储真实图片地址
rootMargin提前触发加载的距离
throttle节流,优化滚动性能
async/await异步加载数据

五、课后作业

  1. 实现一个带骨架屏的懒加载图片组件
  2. 给无限滚动添加"加载更多"按钮作为降级方案
  3. 实现图片加载失败时的占位图显示

有问题欢迎评论区留言,大家一起讨论!


标签:JavaScript | 实战项目 | 懒加载 | 无限滚动 | 性能优化 | IntersectionObserver

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 8:10:57

大学录取通知书上的二维码,扫出来是什么

高考录取通知书是很多人家里保存时间最长的证件之一。近几年&#xff0c;不少大学的录取通知书上开始印二维码。扫一扫&#xff0c;跳出来的页面里有什么&#xff1f;有学校的欢迎词&#xff0c;有入学须知&#xff0c;有时候还有一段视频。但这个二维码背后&#xff0c;连着的…

作者头像 李华
网站建设 2026/6/10 8:09:33

RV1126——多线程获取高分辨率和低分辨率的H264码流

前面两章我们已经搞定了单路 H264 码流获取、H264/H265 双编码码流获取。 都是固定分辨率取流&#xff0c;虽然能满足基础录像需求&#xff0c;但在真实的 IPC 摄像头项目中远远不够。实际项目里几乎全部是双分辨率方案&#xff1a; 一路高清 1080P做本地精细录像存档&#xff…

作者头像 李华
网站建设 2026/6/10 8:07:04

新乡高考志愿填报指导

每年高考结束后&#xff0c;新乡的考生和家长们都会面临一个重要的选择——志愿填报。这个过程不仅关系到未来几年的学习环境&#xff0c;更影响着长远的职业发展。然而&#xff0c;信息不对称、专业选择迷茫等问题常常困扰着大家。与那些只提供表面化建议的服务不同&#xff0…

作者头像 李华
网站建设 2026/6/10 8:02:12

HCS12单片机模糊控制指令集深度解析与实战应用

1. 项目概述&#xff1a;在HCS12单片机上实现高效模糊控制在嵌入式控制领域&#xff0c;我们常常会遇到一些“说不清、道不明”的控制难题。比如&#xff0c;你怎么用精确的数学公式去描述“水温有点凉&#xff0c;需要稍微加热一点”这种人类直觉&#xff1f;传统的PID控制器在…

作者头像 李华
网站建设 2026/6/10 7:59:17

TikTok评论数据采集难题:浏览器控制台自动化解决方案

TikTok评论数据采集难题&#xff1a;浏览器控制台自动化解决方案 【免费下载链接】TikTokCommentScraper 项目地址: https://gitcode.com/gh_mirrors/ti/TikTokCommentScraper 在社交媒体数据分析领域&#xff0c;TikTok评论数据采集一直是个技术挑战。传统的API方式受…

作者头像 李华