列表自动滚动
This commit is contained in:
125
src/views/gisHome/component/autoScroller.vue
Normal file
125
src/views/gisHome/component/autoScroller.vue
Normal file
@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div class="auto-scroll-container" @mouseenter="pauseScroll" @mouseleave="resumeScroll" ref="container">
|
||||
<div class="auto-scroll-content" ref="content">
|
||||
<div class="auto-scroll-item" v-for="(item, index) in duplicatedList" :key="index">
|
||||
<slot :item="item">{{ item }}</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
items: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
speed: {
|
||||
type: Number,
|
||||
default: 0.5 // px/frame
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 100 // px
|
||||
},
|
||||
minItems: {
|
||||
type: Number,
|
||||
default: 2 // 小于这个数量不滚动
|
||||
},
|
||||
autoScroll: {
|
||||
type: Boolean,
|
||||
default: true // 控制是否自动滚动
|
||||
}
|
||||
});
|
||||
|
||||
// Refs and Computed
|
||||
const container = ref(null);
|
||||
const content = ref(null);
|
||||
const duplicatedList = computed(() => [...props.items, ...props.items]);
|
||||
const shouldScroll = computed(() => props.items.length >= props.minItems);
|
||||
|
||||
let scrollY = 0;
|
||||
let animationFrameId = null;
|
||||
let manualPaused = false; // 记录是否因为滚轮手动停止
|
||||
|
||||
// 滚动核心逻辑
|
||||
function normalizeScrollY(contentHeight) {
|
||||
if (scrollY <= -contentHeight) scrollY += contentHeight;
|
||||
if (scrollY >= 0) scrollY -= contentHeight;
|
||||
}
|
||||
|
||||
function step() {
|
||||
const contentHeight = content.value.offsetHeight / 2;
|
||||
scrollY -= props.speed;
|
||||
normalizeScrollY(contentHeight);
|
||||
content.value.style.transform = `translateY(${Math.round(scrollY)}px)`;
|
||||
|
||||
animationFrameId = requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
function startScroll() {
|
||||
if (!animationFrameId) {
|
||||
animationFrameId = requestAnimationFrame(step);
|
||||
}
|
||||
}
|
||||
|
||||
function pauseScroll() {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
animationFrameId = null;
|
||||
}
|
||||
|
||||
// 鼠标滚轮事件:手动滚动并停止自动滚动
|
||||
function onWheel(e) {
|
||||
pauseScroll();
|
||||
manualPaused = true; // 标记为手动停止
|
||||
const contentHeight = content.value.offsetHeight / 2;
|
||||
scrollY -= e.deltaY;
|
||||
normalizeScrollY(contentHeight);
|
||||
content.value.style.transform = `translateY(${Math.round(scrollY)}px)`;
|
||||
}
|
||||
|
||||
// 鼠标移出时恢复自动滚动(如果不是手动暂停)
|
||||
function resumeScroll() {
|
||||
if (props.autoScroll && shouldScroll.value) {
|
||||
manualPaused = false; // 重置手动暂停标志
|
||||
startScroll();
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
if (shouldScroll.value && props.autoScroll) {
|
||||
startScroll();
|
||||
}
|
||||
container.value.addEventListener('wheel', onWheel);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
pauseScroll();
|
||||
container.value.removeEventListener('wheel', onWheel);
|
||||
});
|
||||
|
||||
// 响应 items 数量变化
|
||||
watch(shouldScroll, (newVal) => {
|
||||
if (!newVal) {
|
||||
pauseScroll();
|
||||
} else if (props.autoScroll && !manualPaused) {
|
||||
startScroll();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.auto-scroll-container {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.auto-scroll-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
will-change: transform;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user