ScrollX
TIP
使用鼠标滚轮或者左右按钮进行左右滑动
ScrollX.vue
<script setup lang="ts">
import { useDebounceFn } from '@vueuse/core'
interface Props {
showArrow?: boolean
}
withDefaults(defineProps<Props>(), {
showArrow: true,
})
const translateX = ref(0)
const content = ref<HTMLElement | null>(null)
const wrapper = ref<HTMLElement | null>(null)
const isOverflow = ref(false)
const resetTranslateX = useDebounceFn((wrapperWidth, contentWidth) => {
if (!isOverflow.value) translateX.value = 0
else if (-translateX.value > contentWidth - wrapperWidth) translateX.value = wrapperWidth - contentWidth
else if (translateX.value > 0) translateX.value = 0
}, 200)
const refreshIsOverflow = useDebounceFn(() => {
const wrapperWidth = wrapper.value?.offsetWidth || 0
const contentWidth = content.value?.offsetWidth || 0
isOverflow.value = contentWidth > wrapperWidth
resetTranslateX(wrapperWidth, contentWidth)
}, 200)
function handleMouseWheel(e: { wheelDelta: number }) {
const { wheelDelta } = e
const wrapperWidth = wrapper.value?.offsetWidth || 0
const contentWidth = content.value?.offsetWidth || 0
/**
* @wheelDelta 平行滚动的值 >0: 右移 <0: 左移
* @translateX 内容translateX的值
* @wrapperWidth 容器的宽度
* @contentWidth 内容的宽度
*/
if (wheelDelta < 0) {
if (wrapperWidth > contentWidth && translateX.value < -10) return
if (wrapperWidth <= contentWidth && contentWidth + translateX.value - wrapperWidth < -10) return
}
if (wheelDelta > 0 && translateX.value > 10) return
translateX.value += wheelDelta
resetTranslateX(wrapperWidth, contentWidth)
}
const observer = new MutationObserver(refreshIsOverflow)
onMounted(() => {
refreshIsOverflow()
window.addEventListener('resize', refreshIsOverflow)
// 监听内容宽度刷新是否超出
observer.observe(content.value!, { childList: true })
})
onBeforeUnmount(() => {
window.removeEventListener('resize', refreshIsOverflow)
observer.disconnect()
})
</script>
<template>
<div ref="wrapper" class="wrapper" @mousewheel.prevent="handleMouseWheel">
<template v-if="showArrow && isOverflow">
<div class="left" @click="handleMouseWheel({ wheelDelta: 120 })">
<i i-ic:baseline-keyboard-arrow-left />
</div>
<div class="right" @click="handleMouseWheel({ wheelDelta: -120 })">
<i i-ic:baseline-keyboard-arrow-right />
</div>
</template>
<div
ref="content"
class="content"
:class="{ overflow: isOverflow && showArrow }"
:style="{
transform: `translateX(${translateX}px)`,
}"
>
<slot />
</div>
</div>
</template>
<style lang="scss" scoped>
.wrapper {
display: flex;
z-index: 9;
overflow: hidden;
position: relative;
.content {
padding: 0 10px;
display: flex;
align-items: center;
flex-wrap: nowrap;
transition: transform 0.5s;
&.overflow {
padding-left: 30px;
padding-right: 30px;
}
}
.left,
.right {
background-color: #fff;
position: absolute;
top: 0;
bottom: 0;
margin: auto;
width: 20px;
height: 35px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
border: 1px solid #e0e0e6;
border-radius: 2px;
z-index: 2;
cursor: pointer;
}
.left {
left: 0;
}
.right {
right: 0;
}
}
</style>
index.vue
<script setup lang="ts">
import ScrollX from './ScrollX.vue'
const num = ref(10)
</script>
<template>
<div>
<n-space>
<n-button @click="num++" round type="primary">添加</n-button>
<n-button @click="num && num--" round type="error">减少</n-button>
</n-space>
<ScrollX h-60>
<n-tag v-for="i in num" :key="i" type="primary" px-15 mx-5 cursor-pointer>tag{{ i }}</n-tag>
</ScrollX>
</div>
</template>