207 lines
3.8 KiB
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>
|