美文网首页
vue 跑马灯无-缝连接循环滚动

vue 跑马灯无-缝连接循环滚动

作者: wxw_威 | 来源:发表于2021-12-01 17:35 被阅读0次

    跑马灯-无缝连接循环滚动

    1、效果:


    1638346922745.jpg

    2、组件地址:
    vue-seamless-scroll

    3、安装:

    直接引用

    import vueSeamlessScroll from './myClass.vue'
    

    npm 安装

    npm install vue-seamless-scroll --save
    

    4、代码

    引用组件

    <template>
      <vue-seamless-scroll :data="sendVal" :class-option="optionLeft" class="seamless-warp">
        <ul class="item" :style="{width: `${elementWidth}px`}">
          <li v-for="(item, index) in sendVal" v-text="item.actShowName" :key="index" @click="onItemClick(item)"></li>
        </ul>
      </vue-seamless-scroll>
    </template>
    
    <script>
    import vueSeamlessScroll from 'vue-seamless-scroll'
    export default {
      components: {vueSeamlessScroll},
      props: ['sendVal'],
      data() {
        return {
          elementWidth: 139 * this.sendVal.length,    // li 标签宽度() * 数组长度
        };
      },
      computed: {
        optionLeft () {
          return {
            direction: 2,
            limitMoveNum: 2,
            step: 1.5,
          }
        }
      },
    };
    </script>
    
    <style lang="less" scoped>
    .seamless-warp {
      overflow: hidden;
      width: 100vw;
      ul.item {
        li {
          float: left;
          margin-right: 10px;
          color: #ffffff;
          width: 125px;
          height: 30px;
          border-radius: 15px;
          text-align: center;
          background: linear-gradient(180deg, #FF9357, #EC5E22);
          display: inline-block;
          border: 2px solid #FFBB6E;
          line-height: 30px;
          font-size: 14px;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
        }
      }
    }
    </style>
    

    5、组件代码

    <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"
          :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>
    </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, // 内容实际宽度
          }
        },
        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 () {
            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 () {
            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
          }
        },
        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
            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
                if (direction === 1) { // 上
                  if (Math.abs(this.yPos) >= h) {
                    this.$emit('ScrollEnd')
                    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()
          },
        },
        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>
    
    

    相关文章

      网友评论

          本文标题:vue 跑马灯无-缝连接循环滚动

          本文链接:https://www.haomeiwen.com/subject/umisxrtx.html