前端实现无缝冒泡滚动:js实现和纯css实现的两种方法,附代码

413 阅读2分钟

需求:

  1. 列表为动态,不确定数量;
  2. 每一个元素不互相遮挡;

结论:

js写又快又简单,但是兼容差,极大可能出现抖动和卡顿;推荐使用对浏览器更加友好的纯css的animation; 小伙伴们根据自己需求去选择哪一种;

方案1:

这也是大多数人使用的方法:在dom生成完毕之后,将父元素复制,插到自己后面;当复制的盒子滚动到容器顶部时,修改元素的scrollTop值;

  • 演示:

PixPin_2025-04-30_16-45-03.gif

    • 优点:1、实现简单,会自己动态计算高度;
    • 缺点:1、在给scrollTop赋值为0时,会有卡顿的情况, 当.item样式复杂的情况下更为明显;
  • 代码:
<template>
  <div class="app container" ref="container">
    <div class="box" ref="box1" @mouseover="stopRoll" @mouseleave="startRoll">
      <div v-for="(item, index) in list" class="item" :style="[{ transform: `translateX(${translateX[index]}px)` }]">{{ item }}</div>
    </div>
    <div class="box2" ref="box2" @mouseover="stopRoll" @mouseleave="startRoll">
      <div v-for="(item, index) in list" class="item" :style="[{ transform: `translateX(${translateX[index]}px)`}]">{{ item }}</div>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      list: [], //轮播的列表数据
      translateX: [], // x位移数组
      timer: null
    }
  },
  mounted() {
    this.getList()
  },
  beforeDestroy() {
    clearInterval(this.timer)
  },
  methods: {
    getList() {
      this.list = ['1王小明', '李晓红', '张小刚', '钱多多', '张大宝', '李华', '孙策', '赵明明', '钱多多', '0周武王'] // 获取列表
      for (let i = 0; i < this.list.length; i++) {
        this.translateX.push(this.getRandomInteger()) // 记录随机的偏移量
      }
      this.$nextTick(() => {
        this.startRoll()
      })
    },
    startRoll() {
      // this.$refs.box2.innerHtml = this.$refs.box1.innerHtml //重复写在dom里面了,不用再js复制了
      this.timer = setInterval(() => {
        this.rollFunction()
      }, 20)// 越小,滚动越快
    },
    stopRoll() {
      clearInterval(this.timer)
    },
    rollFunction() {
      if (this.$refs.container.scrollTop - this.$refs.box1.scrollHeight <= 0) {
        this.$refs.container.scrollTop++
      } else {
        this.$refs.container.scrollTop = 0
      }
    },
    getRandomInteger() {
      // 计算范围内的最小值和最大值
      const min = 20
      const max = 700
      // 生成随机数并四舍五入到最近的十位数
      const randomTen = Math.round((Math.random() * (max - min) + min) / 10) * 10
      return randomTen
    }
  }
}
</script>
<style lang="scss" scoped>
// 内容高度box1,box2必须大于容器container高度,否则滚动不起来!!!!!!!!!!!!!
.container {
  width: 800px;
  height: 300px;
  border: 1px solid #ccc;
  overflow: hidden;
  position: relative;
  .box1,.box2 { 
  }
  .item { 
    margin-bottom: 20px; 
  }
}
</style>

方案2

抛弃js判断高度,采用动画animation,动态设置不同的动画时长,在动画结束时重新开始动画;

演示:

22.gif

    • 优点:1、纯css写的,对浏览器非常友好,无卡顿;
    • 缺点:1、必须最后一个元素动画结束之后,第一个动画才开始,会导致容器有全部空白的时间(不超过1s ,这个是可以解决的,但是我懒,大佬们帮我优化下)
  • 代码:
<template>
  <div :class="['app', 'container', 'animation-my']" ref="container">
    <div class="box" ref="box1" @mouseover="stopRoll" @mouseleave="startRoll">
      <!-- 给最后一个item加class;
      动态设置动画延迟,实现先后滚动;根据自己项目实际情况设置值 -->
      <div v-for="(item, index) in list" :class="['item', { lastItem: index == list.length - 1 }]" :style="[{ left: `${translateX[index]}px` }, { animationDelay: `${index * 1.1}s` }]">
        {{ item }}
      </div>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      list: [], //轮播的列表数据
      translateX: [], // x位移数组
      lastDom: null //最后一个元素
    }
  },
  mounted() {
    this.getList()
    if (this.lastDom) {
      this.lastDom.removeEventListener('animationend', this.setClass)
    }
  },
  methods: {
    getList() {
      this.list = ['1王小明', '李晓红', '张小刚', '钱多多', '张大宝', '李华', '孙策', '赵明明', '钱多多', '0周武王'] // 获取列表
      for (let i = 0; i < this.list.length; i++) {
        this.translateX.push(this.getRandomInteger()) // 记录随机的偏移量
      }
      this.$nextTick(() => {
        this.lastDom = document.querySelector('.lastItem')
        this.lastDom.addEventListener('animationend', this.setClass)
      })
    },
    startRoll() {
      document.querySelector('.container').classList.remove('pause-animation')
    },
    stopRoll() {
      document.querySelector('.container').classList.add('pause-animation')
    },
    setClass() {
      document.querySelector('.container').classList.remove('animation-my')
      setTimeout(() => {
        document.querySelector('.container').classList.add('animation-my')
      }, 1) // 必须这样写,不然之间删除再加不生效
    },
    getRandomInteger() {
      // 计算范围内的最小值和最大值
      const min = 20
      const max = 700
      // 生成随机数并四舍五入到最近的十位数
      const randomTen = Math.round((Math.random() * (max - min) + min) / 10) * 10
      return randomTen
    }
  }
}
</script>
<style lang="scss" scoped>
// 内容高度box1,box2必须大于容器container高度,否则滚动不起来!!!!!!!!!!!!!
.container {
  width: 800px;
  height: 300px;
  border: 1px solid #ccc;
  overflow: hidden;
  position: relative;
  .box1,
  .box2 {
  }
  .item {
    position: absolute;
    bottom: -20px;
    cursor: pointer;
    animation-timing-function: linear;
    animation-duration: 10s;
  }
}

// 设置控制动画开始、暂停的外层大类
.animation-my {
  .item {
    animation-name: moveUp;
  }
}
.pause-animation {
  .item {
    animation-play-state: paused;
  }
}
@keyframes moveUp {
  0% {
    bottom: -20px;
  }
  100% {
    bottom: 300px;
  }
}
</style>