简易的文字跑马灯 Vue

2,107 阅读1分钟
前言: 最近项目上用到了文字跑马灯功能。本着能抄就抄的精神,在网上找了好几个已经实现该功能且功能比较多的组件。
可惜的是作者将其挂在了npm上,要通过导包的方式使用。要是给组长发现连个简单的功能都要导别人的包来使用的话估计会给喷死。
无奈之下只能自己写一个了(基于Vue)。

实现原理: 定时器 + translateX 效果

GIF.gif

使用方式

import TextLoop from '@/components/common/TextLoop' //这里换上自己存放文件的路径
<text-loop :stringArray="[
      '第一段文本',
      '第二段长的文本。。。。。。。',
      '第三'
  ]" gap="20px" :waitTime="1000" />

注意事项

    要给父元素一个确定的宽度 
    不要使用flex: 1

附上代码

<template>
  <div class="outBox" ref="outBox" v-if="!reFresh">
    <div class="textBox" ref="box" @mouseenter="mouseenter" @mouseleave="mouseleave">
      <span
        v-for="(item,index) in stringArray"
        :key="index"
        :style="{
          paddingRight: gap
        }"
      >{{ item }}</span>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    // 传递过来的字符串数组
    stringArray: {
      type: Array,
      default: []
    },
    //播放速度
    interval: {
      type: Number,
      default: 30
    },
    // 每次移动的像素 太大会导致 "很卡" 的视觉效果
    movePx: {
      type: Number,
      default: 2
    },
    // 两个文本之间的间距
    gap: {
      type: String,
      default: '10px'
    },
    // 每次元素停留显示的时间
    waitTime: {
      type: Number,
      default: 0
    },
    // 是否鼠标放上去后停止移动
    isHoverStop: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      timer: null,
      x: 0,
      isScrolling: true,
      index: 0,
      childWidth: 0,
      reFresh: false,
    }
  },
  beforeDestroy() {
  // 组件销毁时 清除定时器
    clearInterval(this.timer)
  },
  mounted() {
    this.$nextTick(() => {
      this.renderDom()
    })
  },
  methods: {
    renderDom() {
      // 获取外面盒子的Dom元素
      const outBox = this.$refs.outBox;
      // 获取里面面盒子的Dom元素
      const box = this.$refs.box
      const outWidth = outBox.offsetWidth;
      // 设置起始播放的位置 父盒子的最右侧出现
      const startX = outWidth
      box.style.transform = `translateX(${startX}px)`
      this.x = startX
      // 获取第一个子元素的宽度
      this.childWidth = [...box.childNodes][this.index].offsetWidth
      // 获取子元素的总宽度
      this.timer = setInterval(() => {
        if (!this.isScrolling) return
        const box = this.$refs.box
        if (!box) {
          return
        }
        const childNodes = [...box.childNodes]
        const totalChildWidth = childNodes.reduce((pre, cur) => pre + cur.offsetWidth, 0)
        box.style.transform = `translateX(${this.x}px)`
        this.x -= this.movePx
        if (-this.x >= totalChildWidth) {
          // 播放完之后就重新开始播放
          this.x = startX
          this.index = 0
          this.childWidth = childNodes[this.index].offsetWidth
          return
        }
        if ((-this.x >= this.childWidth) && this.waitTime) {
          // 如果过了第index个元素 开始等待 
          this.index++
          this.childWidth += childNodes[this.index].offsetWidth
          this.isScrolling = false
          setTimeout(() => {
            this.isScrolling = true
          }, this.waitTime);
        }
      }, this.interval)
    },
    /**
     * @descript 鼠标放上去触发
     */
    mouseenter(e) {
      if (!this.isHoverStop) return
      this.isScrolling = false;
    },
    /**
     * @descript 鼠标离开触发
     */
    mouseleave() {
      this.isScrolling = true;
    }
  }
}
</script>
<style lang="scss" scoped>
.outBox {
  width: 100%;
  overflow: hidden;
  .textBox {
    white-space: nowrap;
  }
}
</style>