更新记录

1.0.0(2023-02-17)

1、首次提交代码


平台兼容性

Vue2 Vue3
×
App 快应用 微信小程序 支付宝小程序 百度小程序 字节小程序 QQ小程序
HBuilderX 3.1.0 app-vue × × × × ×
钉钉小程序 快手小程序 飞书小程序 京东小程序
× × × ×
H5-Safari Android Browser 微信浏览器(Android) QQ浏览器(Android) Chrome IE Edge Firefox PC-Safari
× × × × × × × × ×

image-cache

UniApp图片本地缓存及性能优化

说明

image-cache组件是对image组件封装,在第一次加载图片后缓存图片到本地,下次加载时如果存在缓存则读取缓存图片,否则加载网络图片,目前支持微信小程序和APP,其它平台自行测试。

快速上手

1、从插件市场导入image-cache插件,或下载插件压缩包

2、在需要使用image-cache的页面注册组件

使用方法

image-cache

参数名 类型 默认值 说明
src String "" 图片资源地址
mode String "aspectFill" 图片裁剪、缩放的模式
lazyLoad Boolean false 图片懒加载。只针对page与scroll-view下的image有效
fadeShow Boolean true 图片显示动画效果
webp Boolean false 在系统不支持webp的情况下是否单独启用webp。默认false,只支持网络资源。webp支持详见下面说明
showMenuByLongpress Boolean false 开启长按图片显示识别小程序码菜单
draggable Boolean true 是否能拖动图片
@load HandleEvent 当图片载入完毕时,发布到 AppService 的事件名,事件对象event.detail = {height:'图片高度px', width:'图片宽度px'}
@error HandleEvent 当错误发生时,发布到 AppService 的事件名,事件对象event.detail = {errMsg: 'something wrong'}
@click HandleEvent
<template>
    <!-- image-cache组件的props可参考uniapp的image组件 -->
    <image-cache src="图片网络地址"></image-cache>
</template>

代码编写

1、包装image标签

uniapp使用image标签来显示和渲染图片,以下代码对image标签进行包装,使用image-cache组件代替image组件。

image-cache.vue

<template>
    <image :src="cacheImgUrl" :mode="mode" :lazy-load="lazyLoad" :fade-show="fadeShow"
        :show-menu-by-longpress="showMenuByLongpress" :webp="webp" :draggable="draggable"
        @load="sendEvent('load', $event)" @error="sendEvent('error', $event)" @click="sendEvent('click', $event)">
    </image>
</template>

<script>
    import {
        ImgCache
    } from './imgCache.js';
    export default {
        name: "ImgCache",
        props: {
            // 图片资源地址
            src: {
                type: String,
                default: ""
            },
            // 图片裁剪、缩放的模式
            mode: {
                type: String,
                default: 'aspectFill'
            },
            // 图片懒加载。只针对page与scroll-view下的image有效
            lazyLoad: {
                type: Boolean,
                default: false
            },
            // 图片显示动画效果
            fadeShow: {
                type: Boolean,
                default: true
            },
            // 在系统不支持webp的情况下是否单独启用webp。默认false,只支持网络资源。webp支持详见下面说明
            webp: {
                type: Boolean,
                default: false
            },
            // 开启长按图片显示识别小程序码菜单
            showMenuByLongpress: {
                type: Boolean,
                default: false
            },
            // 是否能拖动图片
            draggable: {
                type: Boolean,
                default: true
            }
        },
        watch: {
            // 监听URL变化
            src: {
                handler(src) {
                    this.handleCache(src);
                },
                immediate: true
            }
        },
        data() {
            return {
                // 缓存图片地址
                cacheImgUrl: ''
            };
        },
        methods: {
            /**
             * 处理缓存
             * @param {String} imgUrl 网络图片地址
             */
            async handleCache(imgUrl) {
                // #ifdef H5
                this.cacheImgUrl = imgUrl;
                return;
                // #endif
                // 判断图片是否存在缓存
                let localUrl = await ImgCache.getCache(imgUrl);
                if (localUrl) {
                    // 存在缓存,直接用本地图片地址
                    this.cacheImgUrl = localUrl;
                } else {
                    // 不存在缓存,用网络地址
                    this.cacheImgUrl = imgUrl;
                    // 并且设置缓存
                    ImgCache.setCache(imgUrl);
                }
            },
            /**
             * 向父级发送事件
             * @param {String} emit 事件名称
             * @param {Object} event 事件回调
             */
            sendEvent(emit, event) {
                this.$emit(emit, event);
            }
        }
    }
</script>

2、缓存逻辑

以下代码,如果直接用图片的网络地址链接来作为缓存文件命名的话,会显得图片名称很长,而且如果里面有中文,可能导致路径读取错误。所以在imgCache.js中引用md5.js,用md5加密算法来对图片链接进行加密,得到一个等长不重复的文本,以此来作为文件名。

imgCache.js

import md5 from "./md5.min.js";
import downLoadQueue from './download.js';

/**
 * 图片缓存操作类
 */
export class ImgCache {
    /**
     * 设置图片缓存
     * @param {String} imgUrl 网络图片地址
     */
    static async setCache(imgUrl) {
        // 创建图片下载任务
        let task = {
            // 图片路径
            filePath: ImgCache.createFilePath(imgUrl),
            url: imgUrl
        }
        // 添加图片下载任务
        downLoadQueue.add(task);
    }
    /**
     * 获取图片缓存
     * @param {String} imgUrl 网络图片地址
     */
    static getCache(imgUrl) {
        // 创建文件key值
        let filePath = ImgCache.createFilePath(imgUrl);
        return ImgCache.isExistFile(filePath);
    }
    /**
     * 拼接图片文件缓存路径
     * @param {String} imgUrl 网络图片地址
     */
    static createFilePath(imgUrl) {
        let fileName = md5(imgUrl);
        // #ifdef MP-WEIXIN
        return `${wx.env.USER_DATA_PATH}/${fileName}`;
        // #endif
        // #ifdef APP-PLUS
        return return `_doc/imgCache/${fileName}`;
        // #endif
    }
    /**
     * 根据文件路径判断文件是否存在
     * @param {String} filePath 文件路径
     */
    static isExistFile(filePath) {
        return new Promise(resolve => {
            // #ifdef APP-PLUS
            plus.io.resolveLocalFileSystemURL(filePath, () => resolve(filePath), () => resolve(null));
            // #endif
            // #ifdef MP-WEIXIN
            uni.getFileSystemManager().readFile({
                filePath: filePath,
                success: (res) => {
                    resolve(filePath)
                },
                fail: (res) => {
                    resolve(null)
                }
            })
            // #endif
        })
    }
}

3、图片下载逻辑

在图片下载的逻辑代码中,用到了队列,主要是为了解决下列 2 个问题。

如果一个页面中的图片很多,由于下载任务是异步的,会出现一次性下载太多图片,导致页面渲染会比较慢。

如果一个页面中的两个标签引用了同一个图片链接,图片缓存会执行两次,可能会导致这个链接重复下载,这样及其浪费手机资源。

由于队列结构是先进先出的,图片下载任务会一个接着一个按顺序执行,并且在其中加入了判断图片是否已经下载过的判断,避免图片重复下载。

download.js

import {
    ImgCache
} from './imgCache.js';

/**
 * 图片下载任务队列
 */
class DownLoadQueue {
    constructor() {
        // 初始化任务队列
        this.taskList = [];
    }
    /**
     * 添加图片下载任务
     * @param {Object} task 图片下载任务
     */
    add(task) {
        task.isDownload = false;
        this.taskList.push(task);
        this.consume();
    }
    /**
     * 任务消费方法
     */
    async consume() {
        if (this.taskList.length <= 0) {
            return;
        }
        // 从下载队列中获取第 1 条数据
        const task = this.taskList[0];
        // 判断是否正在下载
        if (task.isDownload) {
            return;
        }
        // 状态修改为正在下载
        task.isDownload = true;
        // 判断是否已经下载过该图片
        if (await ImgCache.isExistFile(task.filePath)) {
            // 已经存在,无需重复下载, 进行下一个任务下载
            this.taskList.shift();
            return this.consume();
        }
        // 创建文件下载任务
        // #ifdef MP-WEIXIN
        uni.downloadFile({
            url: task.url,
            success: (res) => {
                if (res.statusCode === 200) {
                    // 下载成功,保存文件
                    uni.getFileSystemManager().saveFile({
                        tempFilePath: res.tempFilePath,
                        filePath: task.filePath
                    });
                }
            },
            complete: () => {
                // 进行下一个任务下载
                this.taskList.shift();
                this.consume();
            }
        })
        // #endif
        // #ifdef APP-PLUS
        const downloadTask = plus.downloader.createDownload(task.url, {
            filename: task.filePath
        }, (download, status) => {
            // 进行下一个任务下载
            this.taskList.shift();
            this.consume();
        })
        // 开始执行下载任务
        downloadTask.start()
        // #endif
    }
}

// 创建图片缓存下载队列
let downLoadQueue = new DownLoadQueue();

export default downLoadQueue;

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

使用中有什么不明白的地方,就向插件作者提问吧~ 我要提问