Skip to content

微信小程序图片预加载方案

1. 方案概述

为了解决微信小程序中图片加载导致的界面卡顿或空白问题,本文设计了一种全局范围内的图片资源预加载方案。该方案能够在小程序启动阶段或指定时机预先加载关键图片资源,提升用户体验。

2. 核心功能模块

2.1 资源列表配置机制

创建一个集中管理的图片资源配置文件,方便统一管理和维护:

javascript
// utils/imagePreload.js
const IMAGE_RESOURCES = {
  // 启动页相关图片
  splash: [
    'https://example.com/splash-bg.jpg',
    'https://example.com/logo.png'
  ],
  // 首页相关图片
  home: [
    'https://example.com/banner1.jpg',
    'https://example.com/banner2.jpg',
    'https://example.com/icon1.png',
    'https://example.com/icon2.png'
  ],
  // 商品页相关图片
  product: [
    'https://example.com/product-placeholder.jpg',
    'https://example.com/star-icon.png'
  ],
  // 其他页面图片...
};

2.2 预加载核心实现

创建图片预加载管理器,实现预加载、缓存管理、进度监听等功能:

javascript
// utils/imagePreload.js
class ImagePreloadManager {
  constructor() {
    // 已加载的图片缓存
    this.cache = new Map();
    // 加载中的图片
    this.loadingImages = new Set();
    // 加载队列
    this.loadQueue = [];
    // 是否正在处理队列
    this.isProcessing = false;
  }

  /**
   * 预加载指定资源组的图片
   * @param {string|string[]} groups - 资源组名称或数组
   * @param {Object} options - 配置选项
   * @param {Function} options.onProgress - 进度回调
   * @param {Function} options.onComplete - 完成回调
   * @param {Function} options.onError - 错误回调
   */
  preload(groups, options = {}) {
    const { onProgress, onComplete, onError } = options;
    const imageUrls = this._getImageUrls(groups);
    
    if (!imageUrls.length) {
      onComplete && onComplete();
      return;
    }

    let loadedCount = 0;
    const totalCount = imageUrls.length;

    imageUrls.forEach(url => {
      this._loadImage(url, {
        onLoad: () => {
          loadedCount++;
          onProgress && onProgress({
            loaded: loadedCount,
            total: totalCount,
            progress: Math.round((loadedCount / totalCount) * 100)
          });
          
          if (loadedCount === totalCount) {
            onComplete && onComplete();
          }
        },
        onError: (err) => {
          console.error('Image preload error:', err);
          onError && onError(err);
          
          loadedCount++;
          onProgress && onProgress({
            loaded: loadedCount,
            total: totalCount,
            progress: Math.round((loadedCount / totalCount) * 100)
          });
          
          if (loadedCount === totalCount) {
            onComplete && onComplete();
          }
        }
      });
    });
  }

  /**
   * 加载单个图片
   * @param {string} url - 图片URL
   * @param {Object} options - 加载选项
   */
  _loadImage(url, options = {}) {
    const { onLoad, onError } = options;

    // 如果已经加载过,直接回调成功
    if (this.cache.has(url)) {
      onLoad && onLoad();
      return;
    }

    // 如果正在加载中,加入队列
    if (this.loadingImages.has(url)) {
      this.loadQueue.push({ url, options });
      return;
    }

    this.loadingImages.add(url);

    const image = wx.createImage();
    image.src = url;

    image.onload = () => {
      this.cache.set(url, true);
      this.loadingImages.delete(url);
      onLoad && onLoad();
      this._processQueue();
    };

    image.onerror = (err) => {
      this.loadingImages.delete(url);
      onError && onError(err);
      this._processQueue();
    };
  }

  /**
   * 处理加载队列
   */
  _processQueue() {
    if (this.isProcessing || this.loadQueue.length === 0) {
      return;
    }

    this.isProcessing = true;
    const item = this.loadQueue.shift();
    this._loadImage(item.url, item.options);
    this.isProcessing = false;
  }

  /**
   * 获取指定资源组的图片URLs
   * @param {string|string[]} groups - 资源组名称或数组
   * @returns {string[]} 图片URL数组
   */
  _getImageUrls(groups) {
    const groupsArray = Array.isArray(groups) ? groups : [groups];
    let urls = [];

    groupsArray.forEach(group => {
      if (IMAGE_RESOURCES[group]) {
        urls = [...urls, ...IMAGE_RESOURCES[group]];
      }
    });

    // 去重
    return [...new Set(urls)];
  }

  /**
   * 检查图片是否已加载
   * @param {string} url - 图片URL
   * @returns {boolean} 是否已加载
   */
  isLoaded(url) {
    return this.cache.has(url);
  }

  /**
   * 清除指定图片缓存
   * @param {string} url - 图片URL
   */
  clearCache(url) {
    if (url) {
      this.cache.delete(url);
    } else {
      // 清除所有缓存
      this.cache.clear();
    }
  }

  /**
   * 获取缓存大小
   * @returns {number} 缓存的图片数量
   */
  getCacheSize() {
    return this.cache.size;
  }
}

// 导出单例
const imagePreloadManager = new ImagePreloadManager();

module.exports = {
  IMAGE_RESOURCES,
  imagePreloadManager
};

2.3 预加载触发时机控制

2.3.1 启动时预加载

在小程序启动时预加载核心图片资源:

javascript
// app.js
const { imagePreloadManager } = require('./utils/imagePreload');

App({
  onLaunch() {
    // 启动时预加载启动页和首页图片
    imagePreloadManager.preload(['splash', 'home'], {
      onProgress: (progress) => {
        console.log('Preload progress:', progress);
        // 可以更新启动页的进度条
      },
      onComplete: () => {
        console.log('Preload complete');
        // 预加载完成后可以进行页面跳转等操作
      },
      onError: (err) => {
        console.error('Preload error:', err);
      }
    });
  },
  // 其他生命周期...
});

2.3.2 页面进入时预加载

在页面进入时预加载该页面所需的图片资源:

javascript
// pages/product/index.js
const { imagePreloadManager } = require('../../utils/imagePreload');

Page({
  onLoad() {
    // 页面加载时预加载商品页相关图片
    imagePreloadManager.preload('product', {
      onProgress: (progress) => {
        console.log('Product images preload progress:', progress);
      },
      onComplete: () => {
        console.log('Product images preload complete');
        // 预加载完成后可以显示页面内容
        this.setData({ isLoaded: true });
      }
    });
  },
  // 其他页面方法...
});

2.4 加载进度监听

通过回调函数实时获取加载进度:

javascript
imagePreloadManager.preload('home', {
  onProgress: (progress) => {
    // progress 包含 loaded, total, progress 三个字段
    console.log(`加载进度: ${progress.progress}%`);
    // 可以更新UI显示进度
    this.setData({ preloadProgress: progress.progress });
  }
});

2.5 加载失败处理

通过错误回调处理加载失败的情况:

javascript
imagePreloadManager.preload('home', {
  onError: (err) => {
    console.error('图片加载失败:', err);
    // 可以显示默认图片或其他错误处理
  }
});

2.6 已加载资源缓存管理

2.6.1 检查图片是否已加载

javascript
const { imagePreloadManager } = require('../../utils/imagePreload');

// 检查图片是否已加载
const isLoaded = imagePreloadManager.isLoaded('https://example.com/banner1.jpg');
console.log('图片是否已加载:', isLoaded);

2.6.2 清除缓存

javascript
// 清除指定图片缓存
imagePreloadManager.clearCache('https://example.com/banner1.jpg');

// 清除所有缓存
imagePreloadManager.clearCache();

2.6.3 获取缓存大小

javascript
const cacheSize = imagePreloadManager.getCacheSize();
console.log('缓存的图片数量:', cacheSize);

3. 性能优化

3.1 避免阻塞主线程

  • 使用异步加载方式,不阻塞小程序主线程
  • 采用队列机制,控制同时加载的图片数量
  • 对图片进行优先级排序,优先加载关键图片

3.2 内存管理

  • 定期清理不常用的图片缓存
  • 根据小程序内存限制,合理控制缓存大小
  • 在页面卸载时清理相关图片缓存

3.3 网络优化

  • 使用CDN加速图片加载
  • 对图片进行适当压缩
  • 考虑使用WebP等现代图片格式

4. 使用示例

4.1 完整使用示例

javascript
// 1. 配置图片资源
// utils/imagePreload.js 中定义 IMAGE_RESOURCES

// 2. 在 app.js 中启动预加载
const { imagePreloadManager } = require('./utils/imagePreload');

App({
  onLaunch() {
    // 启动时预加载核心资源
    imagePreloadManager.preload(['splash', 'home'], {
      onProgress: (progress) => {
        console.log('启动预加载进度:', progress.progress);
      },
      onComplete: () => {
        console.log('启动预加载完成');
      }
    });
  }
});

// 3. 在页面中使用
// pages/home/index.js
const { imagePreloadManager } = require('../../utils/imagePreload');

Page({
  data: {
    isLoaded: false,
    preloadProgress: 0
  },
  
  onLoad() {
    // 再次预加载(如果之前未完成)
    imagePreloadManager.preload('home', {
      onProgress: (progress) => {
        this.setData({ preloadProgress: progress.progress });
      },
      onComplete: () => {
        this.setData({ isLoaded: true });
      }
    });
  },
  
  // 检查图片是否已加载
  checkImageLoaded() {
    const url = 'https://example.com/banner1.jpg';
    const isLoaded = imagePreloadManager.isLoaded(url);
    console.log(`${url} 是否已加载:`, isLoaded);
  }
});

4.2 组件中使用

javascript
// components/custom-image/index.js
const { imagePreloadManager } = require('../../utils/imagePreload');

Component({
  properties: {
    src: {
      type: String,
      value: ''
    },
    placeholder: {
      type: String,
      value: ''
    }
  },
  
  data: {
    isLoaded: false
  },
  
  lifetimes: {
    attached() {
      this.preloadImage();
    }
  },
  
  methods: {
    preloadImage() {
      const { src } = this.properties;
      if (!src) return;
      
      // 检查是否已加载
      if (imagePreloadManager.isLoaded(src)) {
        this.setData({ isLoaded: true });
        return;
      }
      
      // 未加载则加载
      imagePreloadManager._loadImage(src, {
        onLoad: () => {
          this.setData({ isLoaded: true });
        },
        onError: () => {
          console.error('图片加载失败:', src);
        }
      });
    }
  }
});

5. 注意事项

  1. 图片资源管理:合理配置图片资源,避免预加载过多图片导致内存占用过高
  2. 网络环境:在弱网络环境下,预加载可能会影响用户体验,可考虑根据网络状况调整预加载策略
  3. 缓存清理:定期清理不常用的图片缓存,避免内存泄漏
  4. 错误处理:对加载失败的图片提供合理的错误处理机制
  5. 性能监控:监控预加载过程对小程序性能的影响,及时优化

6. 扩展功能

6.1 图片预加载优先级

javascript
// 扩展 ImagePreloadManager 类,添加优先级支持
class ImagePreloadManager {
  // ... 现有代码 ...
  
  /**
   * 预加载图片(带优先级)
   * @param {string|string[]} groups - 资源组名称或数组
   * @param {Object} options - 配置选项
   * @param {number} options.priority - 优先级,数字越大优先级越高
   */
  preload(groups, options = {}) {
    const { priority = 0 } = options;
    // 实现优先级逻辑...
  }
}

6.2 图片预加载状态持久化

javascript
// 扩展 ImagePreloadManager 类,添加持久化支持
class ImagePreloadManager {
  // ... 现有代码 ...
  
  /**
   * 保存预加载状态到本地存储
   */
  saveState() {
    const cacheKeys = Array.from(this.cache.keys());
    wx.setStorageSync('imagePreloadCache', cacheKeys);
  }
  
  /**
   * 从本地存储恢复预加载状态
   */
  restoreState() {
    const cacheKeys = wx.getStorageSync('imagePreloadCache') || [];
    cacheKeys.forEach(key => {
      this.cache.set(key, true);
    });
  }
}

6.3 批量预加载控制

javascript
// 扩展 ImagePreloadManager 类,添加批量控制
class ImagePreloadManager {
  constructor() {
    // ... 现有代码 ...
    // 最大同时加载数
    this.maxConcurrentLoads = 3;
    // 当前正在加载的数量
    this.currentLoads = 0;
  }
  
  // ... 现有代码 ...
  
  /**
   * 加载单个图片(带并发控制)
   */
  _loadImage(url, options = {}) {
    // ... 现有代码 ...
    
    // 控制并发加载
    if (this.currentLoads >= this.maxConcurrentLoads) {
      this.loadQueue.push({ url, options });
      return;
    }
    
    this.currentLoads++;
    
    // ... 加载逻辑 ...
    
    image.onload = () => {
      // ... 现有代码 ...
      this.currentLoads--;
      this._processQueue();
    };
    
    image.onerror = (err) => {
      // ... 现有代码 ...
      this.currentLoads--;
      this._processQueue();
    };
  }
}

7. 总结

本方案通过集中管理图片资源、控制预加载时机、监听加载进度、处理加载失败、管理缓存等功能,实现了微信小程序的图片预加载功能。该方案能够有效提升用户体验,避免图片加载导致的界面卡顿或空白现象,同时通过合理的性能优化,确保预加载过程不会对小程序性能造成负面影响。

通过本文提供的代码示例和使用方法,开发者可以快速实现微信小程序的图片预加载功能,为用户提供更加流畅的视觉体验。