<template>
  <div ref="wrapbox" class="wrapbox" @scroll.passive="handleScrolls" @mousewheel="wrapboxWheel">
    <div ref="wrap" class="wrapinner">
      <div :style="leftSwitch" v-if="navigation" :class="leftSwitchClass" @click="leftSwitchClick">
        <slot name="left-switch"></slot>
      </div>
      <div
        :style="rightSwitch"
        v-if="navigation"
        :class="rightSwitchClass"
        @click="rightSwitchClick"
      >
        <slot name="right-switch"></slot>
      </div>
      <div
        ref="realBox"
        :style="pos"
        @mouseenter="enter"
        @mouseleave="leave"
        @touchstart="touchStart"
        @touchmove="touchMove"
        @touchend="touchEnd"
      >
        <div ref="slotList" :style="float">
          <slot></slot>
        </div>
        <!-- <div v-html="copyHtml" :style="float"></div> -->
      </div>
    </div>
  </div>
</template>

<script>
// require('comutils/animationFrame')()
// const arrayEqual = require('comutils/arrayEqual')
// const copyObj = require('comutils/copyObj')
export default {
  name: "vue-seamless-scroll",
  data() {
    return {
      xPos: 0,
      yPos: 0,
      delay: 0,
      copyHtml: "",
      height: 0,
      width: 0, // 外容器宽度
      realBoxWidth: 0, // 内容实际宽度
      isMousewheel: false //是否在执行滑动才做。加这个是为了阻止 滑动到底部的时候直接回到顶部
    };
  },
  props: {
    data: {
      type: Array,
      default: () => {
        return [];
      }
    },
    limitMoveNum: {
      type: Number,
      default: 5
    },
    classOption: {
      type: Object,
      default: () => {
        return {
          step: 0.2, // 数值越大速度滚动越快
          // limitMoveNum: 5, // 开始无缝滚动的数据量 this.dataList.length
          hoverStop: true, // 是否开启鼠标悬停stop
          direction: 1, // 0向下 1向上 2向左 3向右
          openWatch: true, // 开启数据实时监控刷新dom
          singleHeight: 0, // 单步运动停止的高度(默认值0是无缝不停止的滚动) direction => 0/1
          singleWidth: 0, // 单步运动停止的宽度(默认值0是无缝不停止的滚动) direction => 2/3
          waitTime: 1000 // 单步运动停止的时间(默认值1000ms)
        };
      }
    }
  },
  computed: {
    leftSwitchState() {
      return this.xPos < 0;
    },
    rightSwitchState() {
      return Math.abs(this.xPos) < this.realBoxWidth - this.width;
    },
    leftSwitchClass() {
      return this.leftSwitchState ? "" : this.options.switchDisabledClass;
    },
    rightSwitchClass() {
      return this.rightSwitchState ? "" : this.options.switchDisabledClass;
    },
    leftSwitch() {
      return {
        position: "absolute",
        margin: `${this.height / 2}px 0 0 -${this.options.switchOffset}px`,
        transform: "translate(-100%,-50%)"
      };
    },
    rightSwitch() {
      return {
        position: "absolute",
        margin: `${this.height / 2}px 0 0 ${this.width +
          this.options.switchOffset}px`,
        transform: "translateY(-50%)"
      };
    },
    float() {
      return this.isHorizontal
        ? { float: "left", overflow: "hidden" }
        : { overflow: "hidden" };
    },
    pos() {
      return {
        transform: `translate(${this.xPos}px,${this.yPos}px)`,
        transition: `all ${this.ease} ${this.delay}ms`,
        overflow: "hidden"
      };
    },
    defaultOption() {
      return {
        step: 1, //步长
        limitMoveNum: 5, //启动无缝滚动最小数据数
        hoverStop: true, //是否启用鼠标hover控制
        direction: 1, // 0 往下 1 往上 2向左 3向右
        openTouch: true, //开启移动端touch
        singleHeight: 0, //单条数据高度有值hoverStop关闭
        singleWidth: 0, //单条数据宽度有值hoverStop关闭
        waitTime: 1000, //单步停止等待时间
        switchOffset: 30,
        autoPlay: true,
        navigation: false,
        switchSingleStep: 134,
        switchDelay: 400,
        switchDisabledClass: "disabled",
        isSingleRemUnit: false // singleWidth/singleHeight 是否开启rem度量
      };
    },
    options() {
      this.defaultOption.limitMoveNum = this.limitMoveNum;
      return Object.assign({}, this.defaultOption, this.classOption);
      // return copyObj({}, this.defaultOption, this.classOption)
    },
    navigation() {
      return this.options.navigation;
    },
    autoPlay() {
      if (this.navigation) return false;
      return this.options.autoPlay;
    },
    scrollSwitch() {
      return this.data.length >= this.options.limitMoveNum;
    },
    hoverStopSwitch() {
      return this.options.hoverStop && this.autoPlay && this.scrollSwitch;
    },
    canTouchScroll() {
      return this.options.openTouch;
    },
    isHorizontal() {
      return this.options.direction > 1;
    },
    baseFontSize() {
      return this.options.isSingleRemUnit
        ? parseInt(
            window.getComputedStyle(document.documentElement, null).fontSize
          )
        : 1;
    },
    realSingleStopWidth() {
      return this.options.singleWidth * this.baseFontSize;
    },
    realSingleStopHeight() {
      return this.options.singleHeight * this.baseFontSize;
    },
    step() {
      let singleStep;
      let step = this.options.step;
      if (this.isHorizontal) {
        singleStep = this.realSingleStopWidth;
      } else {
        singleStep = this.realSingleStopHeight;
      }
      if (singleStep > 0 && singleStep % step > 0) {
        console.error(
          "如果设置了单步滚动,step需是单步大小的约数,否则无法保证单步滚动结束的位置是否准确。~~~~~"
        );
      }
      return step;
    }
  },
  mounted() {
    this._initMove();
  },
  methods: {
    handleScrolls(e) {
      // let scrollTop = window.pageYOffset || document.documentElement.scrollTop;
      // console.log(this.$refs.wrapbox.scrollTop);
      // console.log("高度" + e.target.scrollHeight);
      // console.log("距离上面" + e.target.scrollTop);
      // console.log("元素高度" + e.target.clientHeight);
      // 距离底部 let scrollBottom = e.target.scrollHeight - e.target.scrollTop - e.target.clientHeight;
      this.yPos = -e.target.scrollTop;
      // this.$refs.wrapbox.scrollTop = e.target.scrollTop;
    },
    wrapboxWheel(e) {
      const scrollTop = this.$refs.wrapbox.scrollTop;
      if (scrollTop < 1 && e.wheelDeltaY > 0) {
        this.$refs.wrapbox.scrollTop = Math.abs(this.yPos);
      }
    },
    arrayEqual(arr1, arr2) {
      if (arr1 === arr2) return true;
      if (arr1.length !== arr2.length) return false;
      for (let i = 0; i < arr1.length; ++i) {
        if (arr1[i] !== arr2[i]) return false;
      }
      return true;
    },
    reset() {
      this._cancle();
      this._initMove();
    },
    leftSwitchClick() {
      if (!this.leftSwitchState) return;
      // 小于单步距离
      if (Math.abs(this.xPos) < this.options.switchSingleStep) {
        this.xPos = 0;
        return;
      }
      this.xPos += this.options.switchSingleStep;
    },
    rightSwitchClick() {
      if (!this.rightSwitchState) return;
      // 小于单步距离
      if (
        this.realBoxWidth - this.width + this.xPos <
        this.options.switchSingleStep
      ) {
        this.xPos = this.width - this.realBoxWidth;
        return;
      }
      this.xPos -= this.options.switchSingleStep;
    },
    _cancle() {
      cancelAnimationFrame(this.reqFrame || "");
    },
    touchStart(e) {
      if (!this.canTouchScroll) return;
      let timer;
      const touch = e.targetTouches[0]; //touches数组对象获得屏幕上所有的touch，取第一个touch
      const { waitTime, singleHeight, singleWidth } = this.options;
      this.startPos = {
        x: touch.pageX,
        y: touch.pageY
      }; //取第一个touch的坐标值
      this.startPosY = this.yPos; //记录touchStart时候的posY
      this.startPosX = this.xPos; //记录touchStart时候的posX
      if (!!singleHeight && !!singleWidth) {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
          this._cancle();
        }, waitTime + 20);
      } else {
        this._cancle();
      }
    },
    touchMove(e) {
      //当屏幕有多个touch或者页面被缩放过，就不执行move操作
      if (
        !this.canTouchScroll ||
        e.targetTouches.length > 1 ||
        (e.scale && e.scale !== 1)
      )
        return;
      const touch = e.targetTouches[0];
      const { direction } = this.options;
      this.endPos = {
        x: touch.pageX - this.startPos.x,
        y: touch.pageY - this.startPos.y
      };
      event.preventDefault(); //阻止触摸事件的默认行为，即阻止滚屏
      const dir = Math.abs(this.endPos.x) < Math.abs(this.endPos.y) ? 1 : 0; //dir，1表示纵向滑动，0为横向滑动
      if (dir === 1 && direction < 2) {
        // 表示纵向滑动 && 运动方向为上下
        this.yPos = this.startPosY + this.endPos.y;
      } else if (dir === 0 && direction > 1) {
        // 为横向滑动 && 运动方向为左右
        this.xPos = this.startPosX + this.endPos.x;
      }
    },
    touchEnd() {
      if (!this.canTouchScroll) return;
      let timer;
      const direction = this.options.direction;
      this.delay = 50;
      if (direction === 1) {
        if (this.yPos > 0) this.yPos = 0;
      } else if (direction === 0) {
        let h = (this.realBoxHeight / 2) * -1;
        if (this.yPos < h) this.yPos = h;
      } else if (direction === 2) {
        if (this.xPos > 0) this.xPos = 0;
      } else if (direction === 3) {
        let w = this.realBoxWidth * -1;
        if (this.xPos < w) this.xPos = w;
      }
      if (timer) clearTimeout(timer);
      timer = setTimeout(() => {
        this.delay = 0;
        this._move();
      }, this.delay);
    },
    enter() {
      if (this.hoverStopSwitch) this._stopMove();
    },
    leave() {
      this.isMousewheel = false;
      if (this.hoverStopSwitch) this._startMove();
    },
    wheel(envent) {
      if (this.data.length < this.options.limitMoveNum) {
        return false;
      }
      this.isMousewheel = true;
      const h = this.realBoxHeight / 2;
      const tranh = h * -1 - 120;
      if (envent.wheelDeltaY > 0) {
        this.yPos += 120;
        //向上滑动 数值应该更大
        const ys = this.yPos + 120;
        if (ys > 20) {
          this.yPos = 20;
        } else {
          this.yPos += 120;
        }
      } else {
        //向下滑动  数值应该更小
        const ys = this.yPos - 120;
        if (Math.abs(ys) < h) {
          this.yPos -= 120;
        }
      }
    },
    _move() {
      // 鼠标移入时拦截_move()
      if (this.isHover) return;
      if (this.isMousewheel) {
        this._cancle();
        return;
      }
      this._cancle(); //进入move立即先清除动画 防止频繁touchMove导致多动画同时进行
      this.reqFrame = requestAnimationFrame(
        function() {
          const h = this.realBoxHeight / 2; //实际高度
          const w = this.realBoxWidth / 2; //宽度
          let { direction, waitTime } = this.options;
          let { step } = this;
          // console.log('还是水水水水')
          // console.log(this.yPos)
          // console.log(h)
          if (direction === 1) {
            // 上
            if (Math.abs(this.yPos) >= h) {
              this.$emit("ScrollEnd");
              this.$refs.wrapbox && (this.$refs.wrapbox.scrollTop = 0);
              this.yPos = 0;
            }
            this.yPos -= step;
          } else if (direction === 0) {
            // 下
            if (this.yPos >= 0) {
              this.$emit("ScrollEnd");
              this.yPos = h * -1;
            }
            this.yPos += step;
          } else if (direction === 2) {
            // 左
            if (Math.abs(this.xPos) >= w) {
              this.$emit("ScrollEnd");
              this.xPos = 0;
            }
            this.xPos -= step;
          } else if (direction === 3) {
            // 右
            if (this.xPos >= 0) {
              this.$emit("ScrollEnd");
              this.xPos = w * -1;
            }
            this.xPos += step;
          }
          if (this.singleWaitTime) clearTimeout(this.singleWaitTime);
          if (!!this.realSingleStopHeight) {
            //是否启动了单行暂停配置
            if (Math.abs(this.yPos) % this.realSingleStopHeight < step) {
              // 符合条件暂停waitTime
              this.singleWaitTime = setTimeout(() => {
                this._move();
              }, waitTime);
            } else {
              this._move();
            }
          } else if (!!this.realSingleStopWidth) {
            if (Math.abs(this.xPos) % this.realSingleStopWidth < step) {
              // 符合条件暂停waitTime
              this.singleWaitTime = setTimeout(() => {
                this._move();
              }, waitTime);
            } else {
              this._move();
            }
          } else {
            this._move();
          }
        }.bind(this)
      );
    },
    _initMove() {
      this.$nextTick(() => {
        const { switchDelay } = this.options;
        const { autoPlay, isHorizontal } = this;
        this._dataWarm(this.data);
        this.copyHtml = ""; //清空copy
        if (isHorizontal) {
          this.height = this.$refs.wrap.offsetHeight;
          this.width = this.$refs.wrap.offsetWidth;
          let slotListWidth = this.$refs.slotList.offsetWidth;
          // 水平滚动设置warp width
          if (autoPlay) {
            // 修正offsetWidth四舍五入
            slotListWidth = slotListWidth * 2 + 1;
          }
          this.$refs.realBox.style.width = slotListWidth + "px";
          this.realBoxWidth = slotListWidth;
        }

        if (autoPlay) {
          this.ease = "ease-in";
          this.delay = 0;
        } else {
          this.ease = "linear";
          this.delay = switchDelay;
          return;
        }

        // 是否可以滚动判断
        if (this.scrollSwitch) {
          let timer;
          if (timer) clearTimeout(timer);
          this.copyHtml = this.$refs.slotList.innerHTML;
          setTimeout(() => {
            this.realBoxHeight = this.$refs.realBox.offsetHeight;
            this._move();
          }, 0);
        } else {
          this._cancle();
          this.yPos = this.xPos = 0;
        }
      });
    },
    _dataWarm(data) {
      if (data.length > 100) {
        console.warn(
          `数据达到了${data.length}条有点多哦~,可能会造成部分老旧浏览器卡顿。`
        );
      }
    },
    _startMove() {
      this.isHover = false; //开启_move
      this._move();
    },
    _stopMove() {
      this.isHover = true; //关闭_move
      // 防止频频hover进出单步滚动,导致定时器乱掉
      if (this.singleWaitTime) clearTimeout(this.singleWaitTime);
      this._cancle();
    }
  },
  watch: {
    data(newData, oldData) {
      this._dataWarm(newData);
      //监听data是否有变更
      if (!this.arrayEqual(newData, oldData)) {
        this.reset();
      }
    },
    autoPlay(bol) {
      if (bol) {
        this.reset();
      } else {
        this._stopMove();
      }
    }
  },
  beforeCreate() {
    this.reqFrame = null; // move动画的animationFrame定时器
    this.singleWaitTime = null; // single 单步滚动的定时器
    this.isHover = false; // mouseenter mouseleave 控制this._move()的开关
    this.ease = "ease-in";
  },
  beforeDestroy() {
    this._cancle();
    clearTimeout(this.singleWaitTime);
    // window.addEventListener("scroll", this.handleScrolls, false);
  }
};
</script>
<style scoped>
.wrapbox {
  height: 100%;
  overflow: auto;
}
</style>
