mcwl-pc/app/components/ScrollListView.vue

207 lines
3.8 KiB
Vue

<script setup>
import { onMounted, ref } from 'vue';
const props = defineProps({
title: {
type: String,
default: 'Scroll List',
},
items: {
type: Array,
default: () => [1, 2, 3, 4, 5, 6, 7],
},
initialIndex: {
type: Number,
default: 4,
},
})
const emit = defineEmits(['change', 'item-click'])
const containerRef = ref(null)
const listRef = ref(null)
const itemRefs = ref([])
const currentIndex = ref(props.initialIndex)
// 居中滚动函数
function scrollToCenter(index) {
const container = containerRef.value
const item = itemRefs.value[index]
if (!container || !item)
return
const containerWidth = container.offsetWidth
const itemWidth = item.offsetWidth
const itemLeft = item.offsetLeft
const scrollLeft = itemLeft - (containerWidth / 2) + (itemWidth / 2)
listRef.value.scrollTo({
left: scrollLeft,
behavior: 'smooth',
})
}
function handleItemClick(index) {
currentIndex.value = index
scrollToCenter(index)
emit('item-click', {
index,
item: props.items[index],
})
emit('change', index)
}
function jumpUp() {
if (currentIndex.value > 0) {
currentIndex.value--
scrollToCenter(currentIndex.value)
emit('change', currentIndex.value)
}
}
function jumpDown() {
if (currentIndex.value < props.items.length - 1) {
currentIndex.value++
scrollToCenter(currentIndex.value)
emit('change', currentIndex.value)
}
}
onMounted(() => {
})
defineExpose({
jumpUp,
jumpDown,
currentIndex,
})
</script>
<template>
<div class="scroll-list-container">
<h2>{{ title }}</h2>
<div ref="containerRef" class="wrap">
<div ref="listRef" class="list">
<div
v-for="(item, index) in items"
:id="`item${index + 1}`"
:key="index" ref="itemRefs"
class="item"
:class="[{ active: currentIndex === index }]"
@click="handleItemClick(index)"
>
<slot name="item" :item="item" :index="index">
{{ item }}
</slot>
</div>
</div>
</div>
<div class="action">
<button @click="jumpUp">
</button>
<button @click="jumpDown">
</button>
</div>
</div>
</template>
<style scoped>
.scroll-list-container {
display: flex;
flex-direction: column;
align-items: center;
}
.wrap {
width: 400px;
outline: 4px solid #666;
position: relative;
flex-shrink: 0;
overflow: hidden;
}
.list {
display: flex;
overflow: auto;
gap: 10px;
padding: 10px;
scroll-behavior: smooth;
/* 隐藏滚动条但保持功能 */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
/* 为 Webkit 浏览器隐藏滚动条 */
.list::-webkit-scrollbar {
display: none;
}
.item {
display: flex;
align-items: center;
justify-content: center;
width: 100px;
height: 100px;
background: royalblue;
border-radius: 8px;
flex-shrink: 0;
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
}
.item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.active {
background-color: #f44336;
transform: scale(1.05);
}
.action {
margin: 20px;
}
button {
margin: 0 5px;
padding: 8px 16px;
border: none;
border-radius: 4px;
background: #4caf50;
color: white;
cursor: pointer;
transition: background 0.3s;
}
button:hover {
background: #45a049;
}
/* 添加两侧渐变遮罩效果 */
.wrap::before,
.wrap::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
width: 30px;
pointer-events: none;
z-index: 1;
}
/* .wrap::before {
left: 0;
background: linear-gradient(to right, rgba(255, 255, 255, 0.9), transparent);
}
.wrap::after {
right: 0;
background: linear-gradient(to left, rgba(255, 255, 255, 0.9), transparent);
} */
</style>