<template>
	<view class="zs-lazy-image" :style="[imgViewStyle]"
		:class="['zs-lazy-item-' + elIndex,loadStatus=='loaded'?'':'will-change']">
		<view :class="'zs-lazy-item-' + elIndex" class="zs-lazy-item-goods" style="font-size: 0;">
			
			<image :style="[imgStyle]" v-if="!isError" class="zs-lazy-item" :src="isShow ? image : loadingImg"
				:mode="imgMode" @load="imgLoaded" @error="loadError" @tap="clickImg">
			</image>
			<image :style="[imgStyle]" class="zs-lazy-item error" v-else :src="loadingImg" :mode="imgMode"
				@load="errorImgLoaded" @tap="clickImg"></image>

		</view>
	</view>
	
</template>

<script>
	import guid from '@/utils/guid.js'
	export default {
		name: 'ZsLazyLoad',
		props: {
			// 当前item的索引
			index: {
				type: [Number, String]
			},
			// 要显示的图片
			image: {
				type: String,
				default: ''
			},
			// 图片裁剪模式
			imgMode: {
				type: String,
				default: 'aspectFill'
			},
			// 占位图片路径
			loadingImg: {
				type: String,
				default: '/static/logo.png'
			},
			// 加载失败的错误占位图
			errorImg: {
				type: String,
				default: 'data:image/png;base64, '
			},
			// 图片进入可见区域前多少像素时，单位rpx，开始加载图片
			// 负数为图片超出屏幕底部多少距离后触发懒加载，正数为图片顶部距离屏幕底部多少距离时触发(图片还没出现在屏幕上)
			threshold: {
				type: [Number, String],
				default: 30
			},
			// 淡入淡出动画的过渡时间
			duration: {
				type: [Number, String],
				default: 200
			},
			// 渡效果的速度曲线，各个之间差别不大，因为这是淡入淡出，且时间很短，不是那些变形或者移动的情况，会明显
			// 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: false
			},

			// 圆角值
			borderRadius: {
				type: [Number, String],
				default: 20
			},
			// 图片高度，单位rpx
			height: {
				type: [Number, String],
				default: '450'
			},
			width:{
				type:[Number,String],
				default:'auto'
			},
			// full 全部圆角 default 上右圆角
			type: {
				type: String,
				default: 'full'
			},

		},
		data() {
			return {
				isShow: false,
				opacity: 1,
				time: this.duration,
				isError: false, // 图片加载失败
				elIndex: guid()
			}
		},
		computed: {
			imageCopy() {
				let _img = this.image.replace('_640', '_320')
				return _img
			},
			// 将threshold从rpx转为px
			getThreshold() {
				// 先取绝对值，因为threshold可能是负数，最后根据this.threshold是正数或者负数，重新还原
				let thresholdPx = uni.upx2px(Math.abs(this.threshold));
				return this.threshold < 0 ? -thresholdPx : thresholdPx;
			},
			imgStyle() {
				return {
					borderRadius: this.type == 'full' ? this.borderRadius + 'rpx' :
						`${this.borderRadius}rpx ${this.borderRadius}rpx 0 0`,
					height: this.height == 'auto' ? 'auto' : String(this.height).indexOf('%') != -1 ? this.height : this
						.height + 'rpx',
					width:this.width == 'auto' ? 'auto' : String(this.width).indexOf('%') != -1 ? this.width : this
						.width + 'rpx',
				}
			},
			imgViewStyle() {
				return {
					opacity: Number(this.opacity),
					borderRadius: this.type == 'full' ? this.borderRadius + 'rpx' :
						`${this.borderRadius}rpx ${this.borderRadius}rpx 0 0`,
					transition: `opacity ${this.time / 1000}s ease-in-out`
				}
			}
		},
		watch: {
			isShow(nVal) {
				// 如果是不开启过渡效果，直接返回
				if (!this.isEffect) return;
				this.time = 0;
				// 原来opacity为1(不透明，是为了显示占位图)，改成0(透明，意味着该元素显示的是背景颜色，默认的白色)，再改成1，是为了获得过渡效果
				this.opacity = 0.7;
				// 延时30ms，否则在浏览器H5，过渡效果无效
				setTimeout(() => {
					this.time = this.duration;
					this.opacity = 1;
				}, 30)
			}
		},
		mounted() {
			// 此hdOnReachBottom事件由mixin.js发出，目的是让页面到底时，保证所有图片都进行加载，做到绝对稳定且可靠
			this.$nextTick(() => {
				uni.$once('hdOnReachBottom', () => {
					if (!this.isShow) this.isShow = true;
				});
			})
			// mounted的时候，不一定挂载了这个元素，延时30ms，否则会报错或者不报错，但是也没有效果
			setTimeout(() => {
				// 这里是组件内获取布局状态，不能用uni.createIntersectionObserver，而必须用this.createIntersectionObserver
				this.disconnectObserver('contentObserver');
				const contentObserver = uni.createIntersectionObserver(this);
				// 要理解这里怎么计算的，请看这个：
				// https://blog.csdn.net/qq_25324335/article/details/83687695
				contentObserver.relativeToViewport({
					bottom: this.getThreshold,
				}).observe('.zs-lazy-item-' + this.elIndex, (res) => {
					if (res.intersectionRatio > 0) {
						// 懒加载状态改变
						this.isShow = true;
						// 如果图片已经加载，去掉监听，减少性能的消耗
						this.disconnectObserver('contentObserver');
					}
				})
				this.contentObserver = contentObserver;
			}, 30)
		},
		methods: {
			// 点击图片触发的事件,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';
				// 只通知当前图片的index
				this.$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();
			},
		},
		beforeDestroy() {
			this.disconnectObserver('contentObserver');
		}
	}
</script>

<style lang="scss" scoped>
	.zs-lazy-image {
		background-color: #f5f5f6;
		overflow: hidden;
		-webkit-transform: rotate(0deg);
	}
	.zs-lazy-item-goods{
		position: relative;
	}
	.tui_lable{
		width: 120rpx;
		height: 32rpx;
		padding-right: 6rpx;
		box-sizing: border-box;
		display: flex;
		align-items: center;
		justify-content: center;
		position: absolute;
		left: -16px;
		top: 5px;
		transform: rotate(-45deg);
		font-size: 24rpx;
		color: #fff;
	}

	// 通过class控制，image loaded后移除，释放内存，
	// 避免大量will-change挂载
	.will-change {
		transform: transition3d(0, 0, 0);
		will-change: transform, opacity;
	}

	.zs-lazy-item {
		width: 100%;
		// 骗系统开启硬件加速， hack写法，交由will-change处理
		// transform: transition3d(0, 0, 0);
		// 防止图片加载“闪一下”
		// will-change: transform;
		display: block;
		overflow: hidden;
	}
</style>
