Vue | 带有左右点击按钮可控制滑动的列表

  • Post author:
  • Post category:vue




效果

请添加图片描述



功能点

大概有如下两个功能点:

  • 列表横向排列
  • 点击左右两个按钮可以左右移动每个item(附加动画)



思路

列表横向排列可以使用flex布局解决,主要逻辑在左右滑动的控制上面。

在样式上来看,左右滑动其实可以看成:

改变position为relative时属性left值。

具体分析如下:

请添加图片描述

如上图,蓝色为外层div,红色为内层div。

内层div装有列表的各项item,所以它的宽度可能会大于外层div的宽度。外层div采用定宽,同时设置overflow: hidden,使内层div突出的部分隐藏起来。然后通过设置内层div的position: relative,调整left属性的值来达到左右滑动的效果。

请添加图片描述

如上图,把外层div的宽度称为boxLength,内层div的宽度称为allLength,属性值left的绝对值(因为这里left的值应该为负数)称为leftMove。

基于上面的分析,

如果左右两个按钮要控制滑动,实际上就是控制left的属性值。

先来分析右边的按钮:

假设一个列表项目item的宽度为120px,如果要向右移动3个项目item单位,实际上是将left属性值-360。

而且从上图容易得到,当leftMove+boxLength ≥ allLength的时候,向右滑动就应该暂停。

但这里有个小细节,如果内层div(红)右边隐藏部分的width < 360px,则应该只移动那小于360px的一部分宽度,而不应该直接向左移动360px,不然外层div的右侧就会有一段空白。

基于上面这段逻辑,有:

scrollRight() {
  const allLength = this.monitorList.length * 120 // monitorList是项目列表
  const boxLength = document.getElementById('list-box').clientWidth // 用clientWidth获取外层div的宽度
  if (allLength < boxLength) return // 不需要滑动
  const listEl = document.getElementById('list')
  const leftMove = Math.abs(parseInt(window.getComputedStyle(listEl, null).left))
  if (leftMove + boxLength + 360 > allLength) {
    // 到底的时候
    listEl.style.left = '-' + (allLength - boxLength) + 'px'
  } else {
    listEl.style.left = '-' + (leftMove + 360) + 'px'
  }
}

这里有个小tip,获取left属性的时候,不能用

document.getElementById('xxx').style.left

这样得到的值将会是undefined,可以用window.getComputedStyle(listEl, null).left来获取。


如何使用JavaScript获取CSS的left属性值?


Window.getComputedStyle() – Web API 接口参考 | MDN

同理,向左滑动,就是将left的属性值+360px;当leftMove + boxLength – 360 < boxLength的时候,就应该是滚动到最前面了,此时left的属性值就为0。

scrollLeft() {
  const allLength = this.monitorList.length * 120
  const boxLength = document.getElementById('list-box').clientWidth
  if (allLength < boxLength) return
  const listEl = document.getElementById('list')
  const leftMove = Math.abs(parseInt(window.getComputedStyle(listEl, null)?.left))
  if (leftMove + boxLength - 360 < boxLength) {
    // 滚到
    listEl.style.left = '0px'
  } else {
    listEl.style.left = '-' + (leftMove - 360) + 'px'
  }
}

最后,要像滑动有动画效果,可以使用transition: left 1s;来实现。



demo

<template>
  <div>
    <el-card>
      <div class="monitor-list">
        <!-- 左边按钮 -->
        <div class="btn" @click="scrollLeft">
          <i class="el-icon el-icon-caret-left" />
        </div>
        <!-- 中间列表 -->
        <div id="list-box" class="list-box">
          <div id="list" class="list">
            <div v-for="item in monitorList" :key="item.id" class="list-item">
              <img v-if="item.status" width="60" height="60" :src="imgList.alive" alt="">
              <img v-else width="60" height="60" :src="imgList.down" alt="">
              {{ item.name }}
            </div>
          </div>
        </div>
        <!-- 右边按钮 -->
        <div class="btn" @click="scrollRight">
          <i class="el-icon el-icon-caret-right" />
        </div>
      </div>
    </el-card>
  </div>
</template>

<script>
export default {
  data() {
    return {
      monitorList: [],
      imgList: {
        alive: require('@/assets/images/icon/device-alive.png'),
        down: require('@/assets/images/icon/device-down.png')
      }
    }
  },
  created() {
    this.initMonitorList()
  },
  methods: {
    initMonitorList() {
      for (let i = 1; i < 21; i++) {
        this.monitorList.push({
          id: i,
          name: `item${i + 1}`,
          status: 0
        })
      }
    },
		// 左滑动逻辑
    scrollLeft() {
      const allLength = this.monitorList.length * 120
      const boxLength = document.getElementById('list-box').clientWidth
      if (allLength < boxLength) return
      const listEl = document.getElementById('list')
      const leftMove = Math.abs(parseInt(window.getComputedStyle(listEl, null)?.left))
      if (leftMove + boxLength - 360 < boxLength) {
        // 到最开始的时候
        listEl.style.left = '0px'
      } else {
        listEl.style.left = '-' + (leftMove - 360) + 'px'
      }
    },
		// 右滑动逻辑
    scrollRight() {
      const allLength = this.monitorList.length * 120
      const boxLength = document.getElementById('list-box').clientWidth
      if (allLength < boxLength) return
      const listEl = document.getElementById('list')
      const leftMove = Math.abs(parseInt(window.getComputedStyle(listEl, null)?.left))
      if (leftMove + boxLength + 360 > allLength) {
        listEl.style.left = '-' + (allLength - boxLength) + 'px'
      } else {
        listEl.style.left = '-' + (leftMove + 360) + 'px'
      }
    }
  }
}
</script>
<style lang="scss" scoped>
  .monitor-list {
    display: flex;
    justify-content: space-between;
    height: 95px;

    .btn {
      border: 1px solid #b3d8ff;
      width: 50px;
      height: 100px;
      line-height: 100px;
      text-align: center;
      cursor: pointer;
      background-color: #ecf5ff;
      // icon
      font-size: 24px;
      color: #409eff;

      &:hover {
        background-color: #409eff;
        color: white;
      }
    }
    .list-box {
      width: calc(100vw - 100px);
      overflow: hidden;

      .list {
        width: calc(100vw - 100px);
        display: flex;
        transform: all 2s;
        .list-item {
          width: 100px;
          height: 95px;
          text-align: center;
          padding: 10px;
          cursor: pointer;
          margin-left: 40px;
        }
        position: relative;
        left: 0;
        transition: left 1s;
      }
    }
  }
</style>



版权声明:本文为weixin_44303404原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。