Vue 实现单行向上滚动

这个单行轮播是在之前多行轮播的基础上迭代的,本质上二者的思路都相同的,解决了核心重复轮播的算法后,剩下的就是样式的调整。

以前会用重复 dom 的方式来实现轮播,后来受到一个轮播插件的方案的启发,首先我们补齐 dom 的方式本质上还是在补齐需要重复的数据,所以在 Vue 中我们可以先根据你的轮播方式,

  1. 计算出需要追加的重复元素
  2. 计算出重复元素开始在可视区域后(即第一轮的数据展示的位置和当前重复元素的展示位置一致时)通过 css 将 dom 的位置「闪现」复位

在没有过渡动画的加持下,元素的位移肉眼无法察觉,所以我们又可以开始新一轮的轮播。

<template>
  <div
    class="scroll-container"
    :style="{height: `${height/100}rem`}"
  >
    <ul
      v-for="(item, index) in list"
      :key="index"
      class="scroll-ul"
      :style="{transform: `translate3d(0, ${y/100}rem, 0)`, transition: `${transition}`}"
    >
      <li
        class="c-item"
      >
        <img
          v-if="item.content"
          class="a-item"
          :src="item.avatar"
        >
        <div
          v-if="item.content"
          class="c-content"
          v-html="item.content"
        />
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'broad-cast',
  props: {
    list: {
      type: Array,
      default: function () {
        return []
      }
    }
  },
  data () {
    return {
      height: 0, // 单项高度
      y: 0, // 每次移动距离
      originLength: 0, // 原始数组长度
      transition: 'ease all .4s',
      number: 1, // 默认一次滚动一条
      interval: null
    }
  },
  mounted () {
    this.init()
    this.scroll()
  },
  methods: {
    init () {
      if (!this.list.length) return
      this.originLength = this.list.length
      const mod = this.originLength % this.number
      let need = this.originLength < this.number ? (this.number - this.originLength + this.number) : mod === 0 ? (this.number + 1) : (this.number - mod + this.number)
      // 按次序从头开始补齐
      let index = 0
      for (let i = 0; i < need; i++) {
        if (this.list[i]) {
          index = i
        } else {
          index = 0
        }
        const tmp = JSON.parse(JSON.stringify(this.list[index]))
        index++
        this.list.push(tmp)
      }
    },
    scroll () {
      if (!this.list.length) return
      // 计算可视区域高度
      this.height = 64 * this.number + (12 * (this.number - 1))
      let count = 0
      this.y = 0
      clearInterval(this.interval)
      this.interval = setInterval(() => {
        count++
        this.y -= (64 + 12)
        this.transition = '.4s ease all'
        setTimeout(() => {
          if (count === (this.originLength)) {
            count = 0
            this.transition = ''
            this.y = 0
          }
        }, 800)
      }, 2000)
    }
  }
}
</script>

<style scoped lang="scss">
  .scroll-container {
    overflow: hidden;
  }
  .c-item {
    display: flex;
    align-items: center;
    height: .64rem;
    box-sizing: border-box;
    margin-bottom: .12rem;
    color: #223875;
  }
  .c-content {
    max-width: 4.4rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .a-item {
    border-radius: 50%;
    overflow: hidden;
    width: .42rem;
    height: .42rem;
    margin-right: .14rem;
  }
</style>