vue2,vue3项目无限滚动组件,支持滑动

参数说明

items:项目数组

stepTime:每一步滚动的时间间隔(毫秒)

stepHeight:垂直滚动时每一步的高度

stepWidth:水平滚动时每一步的宽度

threshold:判断是否需要滚动的项目数量阈值

containerHeight:容器的高度

containerWidth:容器的宽度

horizontal:是否水平滚动

fadeInOut:是否启用淡入淡出效果

 

 vue2使用方法 (item为当前数据)

把item结构出来然后在设计样式,使用参考类似饿了吗的table 

        <auto-scroll
          :items="info"
          :step-time="3000"
          :step-height="220"
          :threshold="1"
          :container-height="220"
        >
          <template v-slot:default="{ item, index }">
            <div class="navItem fbc" @click="view(item)">
               item
            </div>
          </template>
        </auto-scroll>

vue3使用方法 (同vue2但是结构方法有区别)

<template #default="{ item, index }">
  <div class="item">
    {{ index }} - {{ item }}
  </div>
</template>

vue2源码 

<template>
  <div
    ref="scrollContainer"
    class="scroll-container"
    :style="containerStyle"
    @mouseover="handleMouseOver"
    @mouseleave="handleMouseLeave"
    @wheel="handleMouseWheel"
    @touchstart="handleTouchStart"
    @touchmove="handleTouchMove"
    @touchend="handleTouchEnd"
  >
    <ul ref="scrollList" class="scroll-list" :style="scrollListStyle">
      <li
        v-for="(item, index) in displayItems"
        :key="index"
        :style="itemStyle"
      >
        <slot :item="item" :index="index">
          {{ item }}
        </slot>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "AutoScroll",
  props: {
    // 接受的项目数组
    items: {
      type: Array,
      required: true,
    },
    // 每一步滚动的时间间隔(毫秒)
    stepTime: {
      type: Number,
      default: 1000,
    },
    // 垂直滚动时每一步的高度
    stepHeight: {
      type: Number,
      default: 220,
    },
    // 水平滚动时每一步的宽度
    stepWidth: {
      type: Number,
      default: 220,
    },
    // 判断是否需要滚动的项目数量阈值
    threshold: {
      type: Number,
      default: 1,
    },
    // 容器的高度
    containerHeight: {
      type: Number,
      default: 220,
    },
    // 容器的宽度
    containerWidth: {
      type: Number,
      default: 220,
    },
    // 是否水平滚动
    horizontal: {
      type: Boolean,
      default: false,
    },
    // 是否启用淡入淡出效果
    fadeInOut: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      intervalId: null, // 定时器ID
      isHover: false, // 鼠标是否悬停在容器上
      currentPosition: 0, // 当前滚动的位置
      animationDuration: 1000, // 动画持续时间
      alignTimeout: null, // 对齐步高的延时器
      touchStartPosition: 0, // 触摸开始时的位置
    };
  },
  computed: {
    // 判断是否需要滚动,如果项目数量大于阈值,则需要滚动
    shouldScroll() {
      return this.items.length > this.threshold;
    },
    // 生成一个显示项目的数组,包含原数组和它的副本,以实现无缝滚动
    displayItems() {
      return [...this.items, ...this.items];
    },
    // 设置滚动列表的样式,根据是否悬停来决定是否启用过渡动画
    scrollListStyle() {
      return {
        transition: this.isHover ? 'none' : `transform ${this.animationDuration}ms linear`,
        display: this.fadeInOut ? 'flex' : 'block',
        opacity: this.fadeInOut ? (this.isHover ? 1 : 0.5) : 1,
        flexDirection: this.horizontal ? 'row' : 'column',
      };
    },
    // 设置容器样式,确定容器的宽度和高度
    containerStyle() {
      return {
        height: this.horizontal ? 'auto' : `${this.containerHeight}px`,
        width: this.horizontal ? `${this.containerWidth}px` : 'auto',
        overflow: 'hidden',
        position: 'relative',
      };
    },
    // 设置每个项目的样式,确定项目的宽度和高度
    itemStyle() {
      return {
        height: this.horizontal ? 'auto' : `${this.stepHeight}px`,
        width: this.horizontal ? `${this.stepWidth}px` : 'auto',
      };
    },
  },
  mounted() {
    // 组件挂载时初始化滚动设置
    this.initScrolling();
  },
  beforeDestroy() {
    // 组件销毁前清理滚动设置
    this.cleanUpScrolling();
  },
  watch: {
    // 监听items属性变化,重新初始化滚动
    items() {
      this.initScrolling();
    },
  },
  methods: {
    // 初始化滚动设置
    initScrolling() {
      this.cleanUpScrolling();
      if (this.shouldScroll && !this.isHover) {
        this.startScrolling();
      }
    },
    // 开始自动滚动
    startScrolling() {
      if (!this.intervalId) {
        this.intervalId = setInterval(this.scrollStep, this.stepTime);
      }
    },
    // 停止自动滚动
    stopScrolling() {
      if (this.intervalId) {
        clearInterval(this.intervalId);
        this.intervalId = null;
      }
    },
    // 每步滚动逻辑
    scrollStep() {
      const list = this.$refs.scrollList;
      const maxScrollPosition = this.horizontal
        ? list.scrollWidth / 2
        : list.scrollHeight / 2;

      // 如果当前滚动位置超过最大滚动位置,则重置
      if (this.currentPosition >= maxScrollPosition) {
        this.currentPosition = 0;
        list.style.transition = 'none'; // 禁用过渡效果以立即重置位置
        if (this.horizontal) {
          list.style.transform = `translateX(0)`;
        } else {
          list.style.transform = `translateY(0)`;
        }
        setTimeout(() => {
          list.style.transition = `transform ${this.animationDuration}ms linear`; // 恢复过渡效果
          this.currentPosition += this.horizontal ? this.stepWidth : this.stepHeight;
          if (this.horizontal) {
            list.style.transform = `translateX(-${this.currentPosition}px)`;
          } else {
            list.style.transform = `translateY(-${this.currentPosition}px)`;
          }
        }, 50);
      } else {
        this.currentPosition += this.horizontal ? this.stepWidth : this.stepHeight;
        if (this.horizontal) {
          list.style.transform = `translateX(-${this.currentPosition}px)`;
        } else {
          list.style.transform = `translateY(-${this.currentPosition}px)`;
        }
      }
    },
    // 鼠标悬停时停止滚动
    handleMouseOver() {
      this.isHover = true;
      this.stopScrolling();
    },
    // 鼠标离开时恢复滚动
    handleMouseLeave() {
      this.isHover = false;
      if (this.shouldScroll) {
        this.alignToStep(); // 对齐到最近的步高或步宽
        this.startScrolling();
      }
    },
    // 处理鼠标滚轮事件
    handleMouseWheel(event) {
      const list = this.$refs.scrollList;
      const delta = event.deltaY || event.deltaX;
      this.currentPosition += delta;

      if (this.currentPosition < 0) {
        this.currentPosition = 0;
      } else if (this.currentPosition >= (this.horizontal ? list.scrollWidth : list.scrollHeight) / 2) {
        this.currentPosition = (this.horizontal ? list.scrollWidth : list.scrollHeight) / 2 - 1;
      }

      list.style.transition = 'none';
      if (this.horizontal) {
        list.style.transform = `translateX(-${this.currentPosition}px)`;
      } else {
        list.style.transform = `translateY(-${this.currentPosition}px)`;
      }

      clearTimeout(this.alignTimeout);
      this.alignTimeout = setTimeout(this.alignToStep, 100);
    },
    // 处理触摸开始事件
    handleTouchStart(event) {
      this.touchStartPosition = this.horizontal ? event.touches[0].clientX : event.touches[0].clientY;
      this.stopScrolling();
    },
    // 处理触摸移动事件
    handleTouchMove(event) {
      const touchCurrentPosition = this.horizontal ? event.touches[0].clientX : event.touches[0].clientY;
      const delta = this.touchStartPosition - touchCurrentPosition;
      this.handleMouseWheel({ deltaY: delta, deltaX: delta });
      this.touchStartPosition = touchCurrentPosition;
    },
    // 处理触摸结束事件
    handleTouchEnd() {
      if (this.shouldScroll) {
        this.alignToStep();
        this.startScrolling();
      }
    },
    // 对齐到最近的步高或步宽
    alignToStep() {
      const list = this.$refs.scrollList;
      this.currentPosition = Math.round(this.currentPosition / (this.horizontal ? this.stepWidth : this.stepHeight)) * (this.horizontal ? this.stepWidth : this.stepHeight);
      list.style.transition = `transform ${this.animationDuration}ms linear`;
      if (this.horizontal) {
        list.style.transform = `translateX(-${this.currentPosition}px)`;
      } else {
        list.style.transform = `translateY(-${this.currentPosition}px)`;
      }
    },
    // 清理滚动设置
    cleanUpScrolling() {
      this.stopScrolling();
      clearTimeout(this.alignTimeout);
    },
  },
};
</script>

<style scoped>
.scroll-container {
  overflow: hidden;
  position: relative;
}
.scroll-list {
  list-style-type: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
</style>

vue3源码 

 

<template>
  <div
    ref="scrollContainer"
    class="scroll-container"
    :style="containerStyle"
    @mouseover="handleMouseOver"
    @mouseleave="handleMouseLeave"
    @wheel="handleMouseWheel"
    @touchstart="handleTouchStart"
    @touchmove="handleTouchMove"
    @touchend="handleTouchEnd"
  >
    <ul ref="scrollList" class="scroll-list" :style="scrollListStyle">
      <li
        v-for="(item, index) in displayItems"
        :key="index"
        :style="itemStyle"
      >
        <slot :item="item" :index="index">
          {{ item }}
        </slot>
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount, computed, watch } from 'vue';

// Props定义
const props = defineProps({
  items: {
    type: Array,
    required: true,
  },
  stepTime: {
    type: Number,
    default: 1000,
  },
  stepHeight: {
    type: Number,
    default: 220,
  },
  stepWidth: {
    type: Number,
    default: 220,
  },
  threshold: {
    type: Number,
    default: 1,
  },
  containerHeight: {
    type: Number,
    default: 220,
  },
  containerWidth: {
    type: Number,
    default: 220,
  },
  horizontal: {
    type: Boolean,
    default: false,
  },
  fadeInOut: {
    type: Boolean,
    default: false,
  },
});

// 引用DOM元素
const scrollContainer = ref(null);
const scrollList = ref(null);

// 定义状态
const intervalId = ref(null);
const isHover = ref(false);
const currentPosition = ref(0);
const animationDuration = ref(1000);
const alignTimeout = ref(null);
const touchStartPosition = ref(0);

// 判断是否需要滚动,如果项目数量大于阈值,则需要滚动
const shouldScroll = computed(() => {
  return props.items.length > props.threshold;
});

// 生成一个显示项目的数组,包含原数组和它的副本,以实现无缝滚动
const displayItems = computed(() => {
  return [...props.items, ...props.items];
});

// 设置滚动列表的样式,根据是否悬停来决定是否启用过渡动画
const scrollListStyle = computed(() => {
  return {
    transition: isHover.value ? 'none' : `transform ${animationDuration.value}ms linear`,
    display: props.fadeInOut ? 'flex' : 'block',
    opacity: props.fadeInOut ? (isHover.value ? 1 : 0.5) : 1,
    flexDirection: props.horizontal ? 'row' : 'column',
  };
});

// 设置容器样式,确定容器的宽度和高度
const containerStyle = computed(() => {
  return {
    height: props.horizontal ? 'auto' : `${props.containerHeight}px`,
    width: props.horizontal ? `${props.containerWidth}px` : 'auto',
    overflow: 'hidden',
    position: 'relative',
  };
});

// 设置每个项目的样式,确定项目的宽度和高度
const itemStyle = computed(() => {
  return {
    height: props.horizontal ? 'auto' : `${props.stepHeight}px`,
    width: props.horizontal ? `${props.stepWidth}px` : 'auto',
  };
});

// 初始化滚动设置
const initScrolling = () => {
  cleanUpScrolling();
  if (shouldScroll.value && !isHover.value) {
    startScrolling();
  }
};

// 开始自动滚动
const startScrolling = () => {
  if (!intervalId.value) {
    intervalId.value = setInterval(scrollStep, props.stepTime);
  }
};

// 停止自动滚动
const stopScrolling = () => {
  if (intervalId.value) {
    clearInterval(intervalId.value);
    intervalId.value = null;
  }
};

// 每步滚动逻辑
const scrollStep = () => {
  const list = scrollList.value;
  const maxScrollPosition = props.horizontal
    ? list.scrollWidth / 2
    : list.scrollHeight / 2;

  // 如果当前滚动位置超过最大滚动位置,则重置
  if (currentPosition.value >= maxScrollPosition) {
    currentPosition.value = 0;
    list.style.transition = 'none'; // 禁用过渡效果以立即重置位置
    if (props.horizontal) {
      list.style.transform = `translateX(0)`;
    } else {
      list.style.transform = `translateY(0)`;
    }
    setTimeout(() => {
      list.style.transition = `transform ${animationDuration.value}ms linear`; // 恢复过渡效果
      currentPosition.value += props.horizontal ? props.stepWidth : props.stepHeight;
      if (props.horizontal) {
        list.style.transform = `translateX(-${currentPosition.value}px)`;
      } else {
        list.style.transform = `translateY(-${currentPosition.value}px)`;
      }
    }, 50);
  } else {
    currentPosition.value += props.horizontal ? props.stepWidth : props.stepHeight;
    if (props.horizontal) {
      list.style.transform = `translateX(-${currentPosition.value}px)`;
    } else {
      list.style.transform = `translateY(-${currentPosition.value}px)`;
    }
  }
};

// 鼠标悬停时停止滚动
const handleMouseOver = () => {
  isHover.value = true;
  stopScrolling();
};

// 鼠标离开时恢复滚动
const handleMouseLeave = () => {
  isHover.value = false;
  if (shouldScroll.value) {
    alignToStep();
    startScrolling();
  }
};

// 处理鼠标滚轮事件
const handleMouseWheel = (event) => {
  const list = scrollList.value;
  const delta = event.deltaY || event.deltaX;
  currentPosition.value += delta;

  if (currentPosition.value < 0) {
    currentPosition.value = 0;
  } else if (currentPosition.value >= (props.horizontal ? list.scrollWidth : list.scrollHeight) / 2) {
    currentPosition.value = (props.horizontal ? list.scrollWidth : list.scrollHeight) / 2 - 1;
  }

  list.style.transition = 'none';
  if (props.horizontal) {
    list.style.transform = `translateX(-${currentPosition.value}px)`;
  } else {
    list.style.transform = `translateY(-${currentPosition.value}px)`;
  }

  clearTimeout(alignTimeout.value);
  alignTimeout.value = setTimeout(alignToStep, 100);
};

// 处理触摸开始事件
const handleTouchStart = (event) => {
  touchStartPosition.value = props.horizontal ? event.touches[0].clientX : event.touches[0].clientY;
  stopScrolling();
};

// 处理触摸移动事件
const handleTouchMove = (event) => {
  const touchCurrentPosition = props.horizontal ? event.touches[0].clientX : event.touches[0].clientY;
  const delta = touchStartPosition.value - touchCurrentPosition;
  handleMouseWheel({ deltaY: delta, deltaX: delta });
  touchStartPosition.value = touchCurrentPosition;
};

// 处理触摸结束事件
const handleTouchEnd = () => {
  if (shouldScroll.value) {
    alignToStep();
    startScrolling();
  }
};

// 对齐到最近的步高或步宽
const alignToStep = () => {
  const list = scrollList.value;
  currentPosition.value = Math.round(currentPosition.value / (props.horizontal ? props.stepWidth : props.stepHeight)) * (props.horizontal ? props.stepWidth : props.stepHeight);
  list.style.transition = `transform ${animationDuration.value}ms linear`;
  if (props.horizontal) {
    list.style.transform = `translateX(-${currentPosition.value}px)`;
  } else {
    list.style.transform = `translateY(-${currentPosition.value}px)`;
  }
};

// 清理滚动设置
const cleanUpScrolling = () => {
  stopScrolling();
  clearTimeout(alignTimeout.value);
};

// 组件挂载时初始化滚动设置
onMounted(() => {
  initScrolling();
});

// 组件销毁前清理滚动设置
onBeforeUnmount(() => {
  cleanUpScrolling();
});

// 监听items属性变化,重新初始化滚动
watch(() => props.items, () => {
  initScrolling();
});
</script>

<style scoped>
.scroll-container {
  overflow: hidden;
  position: relative;
}
.scroll-list {
  list-style-type: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
</style>

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农六六

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值