<template>
  <div ref="wrap">
    <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" :class="{'scrollY':!autoPlay}" :style="pos" @mouseenter="enter" @mouseleave="leave" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd">
      <div ref="slotList" :style="float" @click="Change" class="vue-seamless-scroll" style="width:100%;">
        <slot></slot>
      </div>
      <div v-html="copyHtml" :style="float" @click="Change"></div>
    </div>
  </div>
</template>

<script>
import { animationFrame, arrayEqual } from './util'
export default {
  name: 'vue-seamless-scroll',
  data () {
    return {
      xPos: 0,
      yPos: 0,
      delay: 0,
      copyHtml: '',
      height: 0,
      width: 0, // 外容器宽度
      realBoxWidth: 0, // 内容实际宽度
    }
  },
  onMounted () {
    animationFrame()
  },
  props: {
    data: {
      type: Array,
      default: () => {
        return []
      }
    },
    classOption: {
      type: Object,
      default: () => {
        return {}
      }
    }
  },
  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 () {
      let style ={
        width:'100%',
        transform: `translate(${this.xPos}px,${this.yPos}px)`,
        transition: `all ${this.ease} ${this.delay}ms`,
      }
      if(this.options.autoPlay){
        style.overflow ='hidden'
      } 
      return  style
    },
    // 默认配置
    defaultOption () {
      return {
        step: 1, //步长
        stop: false, // 是否开启滚动
        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 () {
      return Object.assign({}, this.defaultOption, this.classOption)
    },
    // 左右方向的滚动是否显示控制器按钮，true 的时候 autoPlay 自动变为 false。
    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
    },
    // 移动端开启 touch 滑动
    canTouchScroll () {
      return this.options.openTouch
    },
    // 方向: 0 往下 1 往上 2 向左 3 向右。
    isHorizontal () {
      return this.options.direction > 1
    },
    // 和步长有关 rem计算
    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
    }
  },
  methods: {
    // 重置
    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 () {
      if (this.hoverStopSwitch) this._startMove()
    },
    // 滚动
    _move () {
      // 鼠标移入时拦截_move()
      if (this.isHover) return
      if (this.options.stop) return
      this._cancle() //进入move立即先清除动画 防止频繁touchMove导致多动画同时进行
      this.reqFrame = requestAnimationFrame(
        () => {
          const h = this.realBoxHeight / 2  //实际高度
          const w = this.realBoxWidth / 2 //宽度
          let { direction, waitTime } = this.options
          let { step } = this
          if (direction === 1) { // 上
            // console.log('1',this.yPos)
            if (Math.abs(this.yPos) >= h) {
              this.$emit('ScrollEnd')
              this.yPos = 0
            }
            this.yPos -= step
            // console.log('2',this.yPos)
          } 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.SingScrollEnd()
                this._move()
              }, waitTime)
            } else {
              this._move()
            }
          } else if (!!this.realSingleStopWidth) {
            if (Math.abs(this.xPos) % this.realSingleStopWidth < step) { // 符合条件暂停waitTime
              this.singleWaitTime = setTimeout(() => {
                this.SingScrollEnd()
                this._move()
              }, waitTime)
            } else {
              this._move()
            }
          } else {
            this._move()
          }
        }
      )
    },
    // 初始化
    _initMove () {
      this.$nextTick(() => {
        const { switchDelay, stop } = this.options
        // console.log(1)
        // 如果设置停止滚动 就不执行
        if (stop) return;
        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
            // console.log(this.$refs.realBox)
            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()
    },
    // 点击事件
    Change (e) {
      let ele
      function find (ele) {
        if (Object.keys(ele.dataset).length > 1) {
          return ele
        }
        if (!ele.parentElement || ele.parentElement.className === 'vue-seamless-scroll') {
          return
        }
        return find(ele.parentElement)
      }
      ele = find(e.target)
      if (ele) {
        this.$emit('onChange', ele.dataset)
      }
    },
    // 单步暂停的回调 返回已滚动的距离
    SingScrollEnd () {
      let { direction } = this.options
      if (direction === 1 || direction === 0) {
        // 上下
        this.$emit('SingScrollEnd', this.yPos)
      } else {
        this.$emit('SingScrollEnd', this.xPos)
      }
    }
  },
  mounted () {
    this._initMove()
  },
  watch: {
    // 监听数据变化
    data (newData, oldData) {
      this._dataWarm(newData)
      //监听data是否有变更
      if (!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)
  }
}
</script>
<style lang="less" scoped>
.scrollY {
    overflow-y: scroll;
    overflow-x: hidden;
    -ms-overflow-style: none;
    overflow: -moz-scrollbars-none;
    height: 100%;
    &::-webkit-scrollbar {
      width: 0 !important;
    }
  }
</style>
