如何在Angular中不使用任何库实现图片懒加载并优化图片引用?
当然可以手动搞定图片懒加载!其实像ng-defer-load、lazysizes这类库的核心逻辑并不复杂——就是判断图片什么时候进入用户的视口,再触发真实图片的加载。自己实现不仅能减少项目依赖、缩小包体积,还能完全掌控逻辑细节。下面我一步步教你优化你的图片代码:
手动实现图片懒加载的完整步骤
1. 改造现有img标签
首先,我们要把真实图片地址从src移到自定义属性里,同时给src设置一个轻量的占位图(避免初始请求无用资源)。保留width和height属性很重要,能防止图片加载后出现布局偏移。
你的原代码:
<img src="../../../assets/img/logo.png" width="100" height="100" style="display:inline-flex; float:left" />
改造后:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" data-src="../../../assets/img/logo.png" width="100" height="100" style="display:inline-flex; float:left" class="lazy-image" />
这里用了1x1的透明base64占位图,无需额外请求资源;加上lazy-image类方便后续通过JS选择元素。
2. 实现视口检测逻辑
核心是判断图片是否进入视口,这里有两种主流方案,推荐优先用第一种:
方案一:Intersection Observer API(现代浏览器首选)
这是浏览器原生支持的高性能API,由浏览器底层优化,不会像scroll事件那样频繁触发回调,性能更好。
// 获取所有需要懒加载的图片 const lazyImages = document.querySelectorAll('.lazy-image'); // 创建观察者实例 const observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { // 当图片进入视口时 if (entry.isIntersecting) { const img = entry.target; // 把真实地址从data-src赋值给src img.src = img.dataset.src; // 加载完成后移除类并停止监听该图片 img.classList.remove('lazy-image'); observer.unobserve(img); } }); }, { // 可选配置:元素进入视口10%时触发加载 threshold: 0.1 }); // 给每个懒加载图片添加监听 lazyImages.forEach(img => observer.observe(img));
方案二:Scroll事件监听(兼容旧浏览器)
如果需要兼容IE11这类不支持Intersection Observer的浏览器,可以用scroll事件结合getBoundingClientRect()来检测:
const lazyImages = document.querySelectorAll('.lazy-image'); function checkLazyImages() { lazyImages.forEach(img => { const rect = img.getBoundingClientRect(); // 判断图片是否处于视口范围内 const isInViewport = rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0; // 仅当图片在视口且未加载时触发 if (isInViewport && !img.src.includes('data:image')) { img.src = img.dataset.src; img.classList.remove('lazy-image'); } }); } // 页面初始化时先检查一次 checkLazyImages(); // 监听scroll事件,添加节流避免频繁触发 window.addEventListener('scroll', () => { clearTimeout(window.lazyLoadTimeout); window.lazyLoadTimeout = setTimeout(checkLazyImages, 200); }); // 还要监听窗口 resize 和屏幕旋转事件,防止布局变化后漏检 window.addEventListener('resize', checkLazyImages); window.addEventListener('orientationchange', checkLazyImages);
3. 额外优化细节
- 添加加载状态样式:给懒加载图片加个加载动画,提升用户体验:
.lazy-image { background-color: #f5f5f5; background-image: linear-gradient(90deg, rgba(200,200,200,0) 0%, rgba(200,200,200,0.5) 50%, rgba(200,200,200,0) 100%); background-size: 200% 100%; animation: lazy-load-animation 1.5s infinite; } @keyframes lazy-load-animation { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
- 区分首屏图片:首屏可见的图片不要加懒加载,直接保留原
src,避免首屏内容延迟显示。 - 响应式图片适配:如果需要支持不同屏幕尺寸,可以用
data-srcset替代data-src,JS中赋值给srcset即可,逻辑和单图一致。
4. Angular框架适配(可选)
既然你提到了ng-defer-load,推测你用的是Angular。可以把懒加载逻辑封装成指令,在模板里使用更优雅:
import { Directive, ElementRef, OnInit, OnDestroy } from '@angular/core'; @Directive({ selector: '[appLazyLoad]' }) export class LazyLoadDirective implements OnInit, OnDestroy { private observer: IntersectionObserver | null = null; constructor(private el: ElementRef<HTMLImageElement>) {} ngOnInit(): void { const img = this.el.nativeElement; const realSrc = img.dataset.src; if (!realSrc) return; this.observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { img.src = realSrc; this.observer?.unobserve(img); this.observer = null; } }); }); this.observer.observe(img); } ngOnDestroy(): void { this.observer?.unobserve(this.el.nativeElement); this.observer?.disconnect(); } }
模板中使用:
<img appLazyLoad src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" data-src="../../../assets/img/logo.png" width="100" height="100" style="display:inline-flex; float:left" />
内容的提问来源于stack exchange,提问作者user3408531
相关产品推荐
相关产品推荐

