参数说明
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>