/** * LazyLoader - 图片懒加载组件 * 使用Intersection Observer API实现高性能图片懒加载 */ class LazyLoader { constructor(options = {}) { // 配置选项 this.rootMargin = options.rootMargin || '200px'; // 提前加载距离 this.threshold = options.threshold || 0.01; // 触发阈值 this.placeholderSrc = options.placeholderSrc || this.getDefaultPlaceholder(); // Intersection Observer实例 this.observer = null; // 统计信息 this.stats = { total: 0, loaded: 0, error: 0 }; this.init(); } /** * 初始化Intersection Observer */ init() { // 检查浏览器支持 if (!('IntersectionObserver' in window)) { console.warn('⚠️ 浏览器不支持Intersection Observer,降级为立即加载'); this.fallbackLoad = true; return; } // 创建Observer this.observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { this.loadImage(entry.target); } }); }, { rootMargin: this.rootMargin, threshold: this.threshold }); console.log('✓ 图片懒加载初始化完成'); } /** * 加载单张图片 * @param {HTMLImageElement} img - 图片元素 */ loadImage(img) { const src = img.dataset.src; if (!src || img.classList.contains('loaded') || img.classList.contains('loading')) { return; } // 标记为加载中 img.classList.add('loading'); // 创建临时Image对象预加载 const tempImg = new Image(); tempImg.onload = () => { // 加载成功,设置src img.src = src; img.classList.remove('loading'); img.classList.add('loaded'); // 移除data-src属性 delete img.dataset.src; // 停止观察 if (this.observer) { this.observer.unobserve(img); } // 更新统计 this.stats.loaded++; }; tempImg.onerror = () => { // 加载失败,使用错误占位图 img.src = this.getErrorPlaceholder(); img.classList.remove('loading'); img.classList.add('error'); // 停止观察 if (this.observer) { this.observer.unobserve(img); } // 更新统计 this.stats.error++; console.error(`✗ 图片加载失败: ${src}`); }; // 开始加载 tempImg.src = src; } /** * 注册需要懒加载的图片 * @param {HTMLImageElement} img - 图片元素 */ observe(img) { if (!img || !(img instanceof HTMLImageElement)) { return; } this.stats.total++; // 如果不支持Intersection Observer,直接加载 if (this.fallbackLoad) { this.loadImage(img); return; } // 设置占位图 if (!img.src || img.src === '') { img.src = this.placeholderSrc; } // 添加到Observer if (this.observer) { this.observer.observe(img); } } /** * 批量注册图片 * @param {NodeList|Array} images - 图片元素列表 */ observeAll(images) { if (!images || images.length === 0) { return; } images.forEach(img => this.observe(img)); } /** * 取消观察图片 * @param {HTMLImageElement} img - 图片元素 */ unobserve(img) { if (this.observer && img) { this.observer.unobserve(img); } } /** * 立即加载所有待加载图片(强制加载) */ loadAll() { const lazyImages = document.querySelectorAll('img[data-src]'); lazyImages.forEach(img => this.loadImage(img)); } /** * 获取默认占位图(SVG Data URI) * @returns {string} */ getDefaultPlaceholder() { return 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="200" height="200"%3E%3Crect width="200" height="200" fill="%23f0f0f0"/%3E%3Ctext x="50%25" y="50%25" dominant-baseline="middle" text-anchor="middle" fill="%23ccc" font-size="14"%3E加载中...%3C/text%3E%3C/svg%3E'; } /** * 获取错误占位图(SVG Data URI) * @returns {string} */ getErrorPlaceholder() { return 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="200" height="200"%3E%3Crect width="200" height="200" fill="%23f5f5f5"/%3E%3Ctext x="50%25" y="50%25" dominant-baseline="middle" text-anchor="middle" fill="%23999" font-size="14"%3E图片加载失败%3C/text%3E%3C/svg%3E'; } /** * 获取加载统计信息 * @returns {Object} */ getStats() { return { ...this.stats, pending: this.stats.total - this.stats.loaded - this.stats.error, successRate: this.stats.total > 0 ? ((this.stats.loaded / this.stats.total) * 100).toFixed(2) + '%' : '0%' }; } /** * 重置统计信息 */ resetStats() { this.stats = { total: 0, loaded: 0, error: 0 }; } /** * 销毁懒加载器 */ destroy() { if (this.observer) { this.observer.disconnect(); this.observer = null; } this.resetStats(); } }