预览图
组件【ImageLazyLoad】代码
<template><viewclass="image-lazy-load":style="{opacity: opacity,borderRadius: borderRadius + 'rpx',background: background,transition: `opacity ${time / 1000}s ease-in-out`,}":class="'image-lazy-load-item-' + elIndex"><view :class="'image-lazy-load-item-' + elIndex" style="height: 100%"><image:style="{ borderRadius: borderRadius + 'rpx', height: imgHeight, width: imgWidth }"v-if="!isError"class="image-lazy-load-item":src="isShow ? imageUrl : loadingImg":mode="imgMode"@load="imgLoaded"@error="loadError"@tap="clickImg"></image><!-- 1. 空白占位符-因为自己的项目不需要失败时的图片占位,所以这里就用一个div来占位2. 如果需要用图片来占位,请注释掉这里代码,把下面那段代码注释去掉就行--><viewv-elseclass="image-lazy-load-item":style="{ borderRadius: borderRadius + 'rpx', height: imgHeight, width: imgWidth, background: background }"></view><!-- <image:style="{ borderRadius: borderRadius + 'rpx', height: imgHeight, width: imgWidth }"class="image-lazy-load-item error"v-else:src="showErrorImg ? errorImg : ''":mode="imgMode"@load="errorImgLoaded"@tap="clickImg"></image> --></view></view>
</template><script>
/*** ImageLazyLoad 图片懒加载组件* @description 懒加载使用的场景为:页面有很多图片时,首次加载很慢,导致用户体验感不好.* @property {String Number} index 用户自定义值,在事件触发时回调,用以区分是哪个图片,一般用于图片预览* @property {String} image 图片路径* @property {String} loadingImg 预加载时的占位图* @property {String} errorImg 图片加载出错时的占位图* @property {String} threshold 触发加载时的位置,见上方说明,单位 rpx(默认300)* @property {String Number} duration 图片加载成功时,淡入淡出时间,单位ms(默认)* @property {String} effect 图片加载成功时,淡入淡出的css动画效果(默认ease-in-out)* @property {Boolean} isEffect 图片加载成功时,是否启用淡入淡出效果(默认true)* @property {String Number} borderRadius 图片圆角值,单位rpx(默认0)* @property {String Number} width 图片宽度,(默认100%,传值带上单位)* @property {String Number} height 图片高度,注意:实际高度可能受imgMode参数影响(默认450rpx 传值带上单位)* @property {String Number} imgMode 图片的裁剪模式,详见image组件裁剪模式(默认aspectFill)* @property {String} background 占位图片背景色* @property {Boolean} showErrorImg 显示加载失败图片(默认false)* @event {Function} click 点击图片时触发* @event {Function} load 图片加载成功时触发* @event {Function} error 图片加载失败时触发* @example <ImageLazyLoad :image="image"></ImageLazyLoad>*/
export default {name: "ImageLazyLoad",props: {index: {type: [Number, String],},// 要显示的图片image: {type: String,default: "",},// 图片裁剪模式imgMode: {type: String,default: "aspectFill",},// 占位图片路径loadingImg: {type: String,default: "",},// 加载失败的错误占位图errorImg: {type: String,default: "",},// 图片进入可见区域前多少像素时,单位rpx,开始加载图片// 负数为图片超出屏幕底部多少距离后触发懒加载,正数为图片顶部距离屏幕底部多少距离时触发(图片还没出现在屏幕上)threshold: {type: [Number, String],default: 300,},// 淡入淡出动画的过渡时间duration: {type: [Number, String],default: 300,},// 渡效果的速度曲线,各个之间差别不大,因为这是淡入淡出,且时间很短,不是那些变形或者移动的情况,会明显// linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n);effect: {type: String,default: "ease-in-out",},// 是否使用过渡效果isEffect: {type: Boolean,default: true,},// 圆角值borderRadius: {type: [Number, String],default: 0,},// 图片宽度,单位rpxwidth: {type: [Number, String],default: "100%",},// 图片高度,单位rpxheight: {type: [Number, String],default: "450rpx",},// 占位背景色background: {type: String,default: "",},// 显示错误图片showErrorImg: {type: Boolean,default: false,},},data() {return {imageUrl: this.image, // 正在要显示的图片路径isShow: false,opacity: 1,time: this.duration,loadStatus: "", // 默认是懒加载中的状态isError: false, // 图片加载失败elIndex: this.generateRandomString(32),isConnected: true, // 网络是否连接 默认连接};},computed: {// 将threshold从rpx转为pxgetThreshold() {// 先取绝对值,因为threshold可能是负数,最后根据this.threshold是正数或者负数,重新还原let thresholdPx = uni.upx2px(Math.abs(this.threshold));return this.threshold < 0 ? -thresholdPx : thresholdPx;},// 计算图片的高度,可能为auto,带%,或者直接数值imgHeight() {return this.height;},imgWidth() {return this.width;},},created() {// 由于一些特殊原因,不能将此变量放到data中定义this.observer = {};},watch: {isShow(nVal) {// 如果是不开启过渡效果,直接返回if (!this.isEffect) return;this.time = 0;// 原来opacity为1(不透明,是为了显示占位图),改成0(透明,意味着该元素显示的是背景颜色,默认的白色),再改成1,是为了获得过渡效果this.opacity = 0;// 延时30ms,否则在浏览器H5,过渡效果无效setTimeout(() => {this.time = this.duration;this.opacity = 1;}, 30);},// 图片路径发生变化时,需要重新标记一些变量,否则会一直卡在某一个状态,比如isErrorimage(n) {if (!n) {// 如果传入null或者'',或者undefined,标记为错误状态this.isError = true;} else {this.init();this.isError = false;}},// 监听网络变化, 防止网络断开重连的时候,图片一直加载不出来bugisConnected(newVal) {if (newVal) {this.init();this.isError = false;// 图片链接加个时间戳,防止加载失败后出现缓存,// 导致联网后,刷新页面加载失败的图片不能重新加载,而出现空白的bugthis.imageUrl = this.image + '?time=' + Date.now()}},},emits: ["click", "load"],methods: {// 用于重新初始化init() {this.isError = false;this.loadStatus = "";},// 点击图片触发的事件,loadlazy-还是懒加载中状态,loading-图片正在加载,loaded-图片加加载完成clickImg() {let whichImg = "";// 如果isShow为false,意味着图片还没开始加载,点击的只能是最开始的占位图if (this.isShow == false) whichImg = "lazyImg";// 如果isError为true,意味着图片加载失败,这是只剩下错误的占位图,所以点击的只能是错误占位图// 当然,也可以给错误的占位图元素绑定点击事件,看你喜欢~else if (this.isError == true) whichImg = "errorImg";// 总共三张图片,除了两个占位图,剩下的只能是正常的那张图片了else whichImg = "realImg";// 只通知当前图片的indexthis.$emit("click", this.index);},// 图片加载完成事件,可能是加载占位图时触发,也可能是加载真正的图片完成时触发,通过isShow区分imgLoaded() {// 占位图加载完成if (this.loadStatus == "") {this.loadStatus = "lazyed";}// 真正的图片加载完成else if (this.loadStatus == "lazyed") {this.loadStatus = "loaded";this.$emit("load", this.index);}},// 错误的图片加载完成errorImgLoaded() {this.$emit("error", this.index);},// 图片加载失败loadError() {this.isError = true;},disconnectObserver(observerName) {const observer = this[observerName];observer && observer.disconnect();},// 生成一个32位由字母组成的字符串generateRandomString(length) {let result = "";const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";for (let i = 0; i < length; i++) {result += characters.charAt(Math.floor(Math.random() * characters.length));}return result;},},beforeUnmount() {// 销毁页面时,可能还没触发某张很底部的懒加载图片,所以把这个事件给去掉//observer.disconnect();},mounted() {// 监听网络变化, 防止网络断开重连的时候,图片一直加载不出来buguni.onNetworkStatusChange((res) => {// console.log("网络变化:", res.isConnected);this.isConnected = res.isConnected;});// mounted的时候,不一定挂载了这个元素,延时30ms,否则会报错或者不报错,但是也没有效果setTimeout(() => {// 这里是组件内获取布局状态,不能用uni.createIntersectionObserver,而必须用this.createIntersectionObserver// this.disconnectObserver('contentObserver');const contentObserver = uni.createIntersectionObserver(this);// 要理解这里怎么计算的,请看这个:// https://blog.csdn.net/qq_25324335/article/details/83687695contentObserver.relativeToViewport({bottom: this.getThreshold,}).observe(".image-lazy-load-item-" + this.elIndex, (res) => {// console.log("relativeToViewport", res);if (res.intersectionRatio > 0) {// 懒加载状态改变this.isShow = true;// 如果图片已经加载,去掉监听,减少性能的消耗this.disconnectObserver("contentObserver");}});this.contentObserver = contentObserver;}, 30);},
};
</script><style scoped lang="scss">
.image-lazy-load {background-color: #fff;overflow: hidden;&-item {width: 100%;transform: transition3d(0, 0, 0);// 防止图片加载“闪一下”will-change: transform;/* #ifndef APP-NVUE */display: block;/* #endif */}
}
</style>
组件使用【组件引入这里就不介绍】
<ImageLazyLoadwidth="100%"height="calc((100vw - 24px - 8px) / 2)":image="item.url"threshold="300"></ImageLazyLoad>
说明:根据自己情况,设置对应的宽高就行