<template>
  <view class="tn-lazy-load-class tn-lazy-load">
    <view
      class="tn-lazy-load__item"
      :class="[`tn-lazy-load__item--${elIndex}`]"
      :style="[lazyLoadItemStyle]"
    >
      <view class="tn-lazy-load__item__content">
        <image
          v-if="!error"
          class="tn-lazy-load__item__image"
          :style="[imageStyle]"
          :src="show ? image : loadingImg"
          :mode="imgMode"
          @load="handleImgLoaded"
          @error="handleImgError"
          @tap="handleImgClick"
        ></image>
        <image
          v-else
          class="tn-lazy-load__item__image tn-lazy-load__item__image--error"
          :style="[imageStyle]"
          :src="errorImg"
          :mode="imgMode"
          @load="handleErrorImgLoaded"
          @tap="handleImgClick"
        ></image>
      </view>
    </view>
  </view>
</template>

<script>
  export default {
    name: 'tn-lazy-load',
    props: {
      // 组件标识
      index: {
        type: [String, Number],
        default: ''
      },
      // 待显示的图片地址
      image: {
        type: String,
        default: ''
      },
      // 图片裁剪模式
      imgMode: {
        type: String,
        default: 'scaleToFill'
      },
      // 占位图片路径
      loadingImg: {
      	type: String,
      	default: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUM0QjNBQjkyQUQ2MTFFQTlCNUQ4RTIzNDE5RUIxNjciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUM0QjNBQkEyQUQ2MTFFQTlCNUQ4RTIzNDE5RUIxNjciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QzRCM0FCNzJBRDYxMUVBOUI1RDhFMjM0MTlFQjE2NyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QzRCM0FCODJBRDYxMUVBOUI1RDhFMjM0MTlFQjE2NyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtRHfPcAAAAzUExURZWVldfX18PDw62trZubm9zc3Li4uKGhoebm5tLS0uHh4c3Nzaenp729vcjIyLKysuvr6141L40AAAcXSURBVHja7NzZlqpGAEBR5lG0//9rIw7IJKJi4or7PGTdtN10wr5SVAEGf/qqArsAiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAg+nmQFMi5Jis+sIniED23jSzIgLTtg2D//iYme/8QBM/9lQ+CAEhbNLM3N9hEHAThX7GPCiBfAxK1b51kD+R7QMLjXg7iCsgWIPUh7pfVozG791oeBPngm48G583uW5GkBvI+SBaM2xXDn1oqum423bX/mgF5FySc2cv93Voug9TdZotsggnkBZB2NzbhrSY5HnoG07jei8dvzsJB/c3W60SALILE46+WCztsbhPR7R2VJq0ukEcT49nyy8QhaKcRa3fYHZD4+ufqOJAcgDz8/59vtw1I3QP5K6JsOG0vm3hce4I8LQp/BaRZGJC3AAn7IKOKXbC+7EdA5vdmmVwOLksgRThqOqiH4XEGsht+peoPUE8U/jJIO5OLH4GEwUslV5G0PTBG5Uiw/Y2jyigO3l9HAHKv9PYb82LloH74dZBoBUgar+l48NsNvtD0fkez9iwrAvIYZDRCl+Xs149Hm/KZmQ+QjUCiO1ei4ru7EsgnQYrkznlQb7thCuRfAzlOAPN72427P4VA/i2Q/DKT/Ls/VR8fvIBsDZIuz7TPF6TCbnk4GJkB2RokejTjuE7/unlgCuSTIO0Cy+Plp6vDfnQlBchy8QtjSHVd3EgmK1bHLm+H6+nXYbz2DuQRSPnqoL7vvq0u70on4zvxgCyWD3b9UyDVdW24PaWaiGTnFZJwPIQAebDpIKheBIm7n124ZthMJipAlkqHO+IZkP1tbfzOJark/A7MgKyvvl60fRqkvXfhuow+t9+q00+0/yyBrK8ZngOtBzldhw2X9tvpNGty0gvkmbPeJ0Cy/r09s/stbmfo0yMWkEdjevgKyOn2t2pxv7UXoibTdCDLje9/Ww1ymqzn87dbp92242ZmMRjI8hASvwKSLq4udqN6ksw8nxXN3tszD9L8Gkg+2mFrQYql5az4tvFj5xOx4VwnSdeBtGdyPwUytxK77pBVlNHdO7OK3rh/eTPUvdutT3fO52tuHMqD4N7llv8pyOQQ//w19YVDfX27+Sfuby9/6nau4pdA8vEdOZuChEH/quHt0Jg+IRJ/5+PrHwKZXfjbDiS73Zo7mu5UkzX7uTsXe0e/7nC3ePf1O69+BUg2XDfZCqSqOu7rGVf8cHBe8zhC2b61dtUHXv0OkGo6ZL4JkpbRYXdUaFevivx2M/1GIOctNh949TtAoumQ+TpIHMX54CJu+8BDd8FkE5BqcZh/59XvAClmTvKfB0nDqIlHo3T70SftyW1eX9dXtgQJqs1f/Q6QaOa/7wmQKtxH8eiGoCRuovODIO3VxOMmruZbHrLyD7z6DSDtGyT7ew1kf9hNn07c986JTovzzem0Id9wUG+Vk/IDr34DSNR7huZJkMFT6vEhqrPx/j5cnlZML8N6/PAzh9Y99Flm5Yde/c9BquDOkvkKkMP58dA4qi9vivE8JOvGz/j8FokfPpr288+pH2ZPOZrLmeGD+7KOh6dqYWJ48ki7yUg0tz0go/fv/LLddfV3sgOLJyaGPY/zrSlh1a36Arkzoue9CyG35ze6E6/dzO2Ga0EGHqdRJIkfn9/8OEjTW8Vq91ZWh39FeehWA7Nu9ft8CpUEk1WWOyDF0OPyEU2Pnzf/bZC0P6IPzmAvu7KauQBVrgKpJ0tG2arHzX8e5Pb3PezNs/PrX+3JMyCLn9XXf37tPFHvt09WfCDDjx+yyn1/p1V11j7GnB/q3leLuVva79S/tzed+db08YpF4uOZtmz/9oXWMq6BCAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiAALELvqt/BBgACqVeUBXxcCkAAAAASUVORK5CYII='
      },
      // 加载失败的错误占位图
      errorImg: {
      	type: String,
      	default: 'data:image/png;base64,[encoded data, 4508 characters]'
      },
      // 图片进入可见区域前多少像素前，单位rpx，开始加载图片
      // 负数为图片超出屏幕底部多少像素后触发懒加载，正数为图片顶部距离屏幕底部多少距离时触发（图片还没出现在屏幕上）
      threshold: {
        type: [Number, String],
        default: 100
      },
      // 是否开启过渡效果
      isEffect: {
        type: Boolean,
        default: true
      },
      // 动画过渡时间
      duration: {
        type: [String, Number],
        default: 500
      },
      // 渡效果的速度曲线，各个之间差别不大，因为这是淡入淡出，且时间很短，不是那些变形或者移动的情况，会明显
      // linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n);
      effect: {
      	type: String,
      	default: 'ease-in-out'
      },
      // 图片高度，单位rpx
      height: {
        type: [String, Number],
        default: 450
      },
      // 图片圆角
      borderRadius: {
        type: String,
        default: ''
      }
    },
    computed: {
      thresholdValue() {
        // 先取绝对值，因为threshold可能是负数，最后根据this.threshold是正数或者负数，重新还原
        let threshold = uni.upx2px(Math.abs(this.threshold))
        return this.threshold < 0 ? -threshold : threshold
      },
      lazyLoadItemStyle() {
        let style = {}
        style.opacity = Number(this.opacity)
        if (this.borderRadius) {
          style.borderRadius = this.borderRadius
        }
        // 因为time值需要改变,所以不直接用duration值(不能改变父组件prop传过来的值)
        style.transition = `opacity ${this.time / 1000}s ${this.effect}`
        style.height = this.$t.string.getLengthUnitValue(this.height)
        return style
      },
      imageStyle() {
        let style = {}
        if (typeof this.height === 'string' && this.height.indexOf('%') === -1) {
          style.height = this.$t.string.getLengthUnitValue(this.height)
        }
        return style
      }
    },
    watch: {
      show(val) {
        // 如果不开启过渡效果直接返回
        if (!this.effect) return
        this.time = 0
        // 原来opacity为1(不透明，是为了显示占位图)，改成0(透明，意味着该元素显示的是背景颜色，默认的白色)，再改成1，是为了获得过渡效果
        this.opacity = 0
        setTimeout(() => {
          this.time = this.duration
          this.opacity = 1
        }, 30)
      },
      image(val) {
        // 修改图片后重置部分变量
        if (!val) {
          // 如果传入null或者''，或者undefined，标记为错误状态
          this.error = true
        } else {
          this.init()
          this.error = false
        }
      }
    },
    data() {
      return {
        elIndex: this.$t.uuid(),
        // 显示图片
        show: false,
        // 图片透明度
        opacity: 1,
        // 动画时间
        time: this.duration,
        // 懒加载状态
        // loadlazy-懒加载中状态，loading-图片正在加载，loaded-图片加加载完成
        loadStatus: '',
        // 图片加载失败
        error: false
      }
    },
    created() {
      // 由于一些特殊原因，不能将此变量放到data中定义
      this.observer = {}
      this.observerName = 'lazyLoadContentObserver'
    },
    mounted() {
      // 在需要用到懒加载的页面，在触发底部的时候触发tOnLazyLoadReachBottom事件，保证所有图片进行加载
      this.$nextTick(() => {
        uni.$once('tOnLazyLoadReachBottom', () => {
          if (!this.show) this.show = true
        })
      })
      // mounted的时候，不一定挂载了这个元素，延时30ms，否则会报错或者不报错，但是也没有效果
      setTimeout(() => {
        this.disconnectObserver(this.observerName)
        const contentObserver = uni.createIntersectionObserver(this)
        contentObserver.relativeToViewport({
          bottom: this.thresholdValue
        }).observe(`.tn-lazy-load__item--${this.elIndex}`, (res) => {
          if (res.intersectionRatio > 0) {
            // 懒加载状态改变
            this.show = true
            // 如果图片已经加载，去掉监听，减少性能消耗
            this.disconnectObserver(this.observerName)
          }
        })
        this[this.observerName] = contentObserver
      }, 50)
    },
    methods: {
      // 初始化
      init() {
        this.error = false
        this.loadStatus = ''
      },
      // 处理图片点击事件
      handleImgClick() {
        let whichImg = ''
        // 如果show为false，则表示图片还没有开始加载，点击的是最开始占位图
        if (this.show === false) whichImg = 'lazyImg'
        // 如果error为true，则表示图片加载失败，点击的是错误占位图
        else if (this.error === true) whichImg = 'errorImg'
        // 点击了正常的图片
        else whichImg = 'realImg'
        
        this.$emit('click', {
          index: this.index,
          whichImg: whichImg
        })
      },
      // 处理图片加载完成事件，通过show来区分是占位图触发还是加载真正的图片触发
      handleImgLoaded() {
        if (this.loadStatus = '') {
          // 占位图加载完成
          this.loadStatus = 'lazyed'
        }
        else if (this.loadStatus == 'lazyed') {
          // 真正的图片加载完成
          this.loadStatus = 'loaded'
          this.$emit('loaded', this.index)
        }
      },
      // 处理错误图片加载完成
      handleErrorImgLoaded() {
        this.$emit('error', this.index)
      },
      // 处理图片加载失败
      handleImgError() {
		  // console.log('失败');
        this.error = true
      },
      disconnectObserver(observerName) {
        const observer = this[observerName]
        observer && observer.disconnect()
      }
    }
  }
</script>

<style lang="scss" scoped>
  .tn-lazy-load {
    &__item {
      background-color: $tn-bg-gray-color;
      overflow: hidden;
      
      &__image {
        // 解决父容器会多出3px的问题
        display: block;
        width: 100%;
        // 骗系统开启硬件加速
        transform: transition3d(0, 0, 0);
        // 防止图片加载“闪一下”
        will-change: transform;
		border-radius: 16rpx;
      }
    }
  }
</style>
