vue3-scroll-more:横向滚动组件处理元素或页签过多滚动显示
滚动条在日常开发中是绕不开的 UI 组件,本质上,“滚动本身就是一种非常常见的交互模式。最常见的莫过于纵向滚动——毕竟需要用有限空间承载更多内容的需求实在太普遍纵向滚动的场景最为典型——在有限的空间里承载更多内容,这是最日常的需求。 但横向滚动也并不少见。通过按住鼠标滚轮左右拖拽,或者按住键盘 Sh
滚动条在日常开发中是绕不开的 UI 组件,本质上,“滚动本身就是一种非常常见的交互模式。最常见的莫过于纵向滚动——毕竟需要用有限空间承载更多内容的需求实在太普遍
纵向滚动的场景最为典型——在有限的空间里承载更多内容,这是最日常的需求。但横向滚动也并不少见。通过按住鼠标滚轮左右拖拽,或者按住键盘 Shift 键同时滚动滚轮,都可以触发横向滚动。许多场景下,例如页面上横向排列的页签,当页签数量过多时,必然需要一种处理方式:既要保留之前的页签,又要能快速访问所有页签,交互还得友好直观。
就像顶部那张图所展示的,这正是本文介绍的组件要解决的问题。
使用方式
安装
根据所用的包管理器,选择一种方式即可:
--npm
npm install vue3-scroll-more--pnpm
pnpm install vue3-scroll-more--yarn
yarn add vue3-scroll-more--bun
bun install vue3-scroll-more
引入
全局引入:
import Vue3ScrollMore from 'vue3-scroll-more'
const app = createApp(App)
app.use(vue3TagInput)
局部引入:
import { Vue3ScrollMore } from 'vue3-scroll-more'
使用
在任意组件中使用 vue3-scroll-more 组件,默认插槽即你要显示的元素,可以完全根据具体需求自由定制。它类似于列表,需要传入一个 data 数据——一个数组,用于循环遍历渲染默认插槽的内容。注意这是一个作用域插槽,可接收两个参数:数组的每一项 item 和索引 index,然后基于这两个参数进行各种自定义配置与操作。
其他属性虽然很多,但 data 是唯一必填的。
<template>
<div class="tags-view-container">
<vue3-scroll-more :data="list" :side-change="sideChange">
<template v-slot="{ item, index }">
<span class="tags-view-item" :class="currentIndex === index 'active' : ''" @click="setCurrentIndex(index)">
{{ item }}
<el-icon v-if="index !== 0" class="el-icon-close" title="关闭">
<Close />
el-icon>
span>
template>
vue3-scroll-more>
div>
template>
功能
滚动
如果内容宽度未占满容器,则正常展示,不会触发滚动。

一旦内容过长,滚动机制会自动生效,且不会显示滚动条——你正常滚动鼠标滚轮即可,与纵向滚动体验一致。
按钮
除了使用鼠标滚轮,还可以通过展示左右两侧的按钮,单击即可滚动。
左右两侧各有一个图标按钮,指示当前可滚动的方向。当某侧滚动到头时,对应图标颜色变为灰色;反之,仍可继续滚动的一侧图标颜色加深,鼠标悬停时颜色更深——交互反馈直观友好。
如果按住鼠标不放,可连续滚动,适合需要快速滚动的场景,且速度可调。
数量
既然存在滚动,必然有一部分内容处于隐藏状态。该组件可以显示隐藏元素的个数,计算规则:只要某个元素的露出部分小于三分之二(即隐藏部分大于三分之一),就视为隐藏。

显示数量时可设置最大值——例如超过 99 条时,最多显示“99”,避免数字过大显得不雅。
隐藏内容
如需查看隐藏的内容,也不一定非要滚动。配置对应属性后,鼠标悬停在按钮上时,被隐藏的内容即可展示出来。

这种展示方式很直观——隐藏部分以垂直方向展示,可设置最大高度,超过高度后转为你熟悉的纵向滚动。
拖拽
元素支持拖拽重排顺序。不仅横向的元素可在左右方向拖拽调整位置,隐藏内容区域内的元素也可上下拖拽重新排布。更有趣的是,左右两侧的元素还能互相拖拽到对方区域放置。
拖拽规则清晰:被拖拽的元素若落在悬浮元素的前半部分,则插入到其前面;落在后半部分,则插入到其后面。同理,上下拖动的元素根据悬浮元素的上半部分和下半部分判断。
中间部分拖拽:
两边部分拖拽:
两边往中间拖拽:
自动跳转
中间部分点击某个元素可高亮显示,视觉上容易识别。而点击两侧隐藏内容中的元素时,可自定义其行为——例如让被点击的元素自动滚动到视口中,处于最右边或最中间。
共有五种模式可选:
- 点击两边内容中的元素,尽量显示在最左边(除非无法滚动)。
- 点击两边内容中的元素,尽量显示在最右边(除非无法滚动)。
- 点击两边内容中的元素,尽量显示在最中间(除非无法滚动)。
- 点击左边内容中的元素尽量显示在最左边,点击右边内容中的元素尽量显示在最右边(除非无法滚动)。
- 不产生滚动变化,不自动出现在视口中。
简单演示两种:
组件配置
属性
// 组件数据, 基于数组渲染默认插槽
data: {
type: Array,
default: () => ([])
},
// 唯一标识, 用于给v-for绑定key
keySign: {
type: String
},
// 两边列表的高度
sideHeight: {
type: Number,
default: 300
},
// 当滚动条出现或者消失的时候触发, 接收一个参数对象, options.type的show表示滚动显示或者hide表示滚动消失
sideChange: {
type: Function,
default: (options) => ({})
},
// 是否显示数量
showNum: {
type: Boolean,
default: true
},
// 显示数量的最大值
maxNum: {
type: Number,
default: 999
},
// 是否展示两边的按钮
showButton: {
type: Boolean,
default: true
},
// 是否展示隐藏的内容
showHidden: {
type: Boolean,
default: true
},
// 点击元素的时候自动跳转到视口区域
// left: 总是展示在最左边
// right: 总是展示在最右边
// center: 总是展示在最中间
// auto: 总是按照默认行为展示
// none: 什么都不做
autoJump: {
type: String as PropType<'left' | 'right' | 'center' | 'auto' | 'none'>,
default: 'none'
},
// 当鼠标长按两个按钮的时候滚动的速度
autoScrollRate: {
type: Number,
default: 10
},
// 元素是否可以拖动以进行重新排序
draggable: {
type: Boolean,
default: false
}
插槽
通常只需传入默认插槽,两边的内容会自动根据默认插槽渲染。当然,你也可以手动传入自定义插槽内容。
规则很简单:两边插槽都不传,则都用默认插槽;只传了一个,另一个用传的那个;都传了,则各用各的。
default(props: { item: any, index: number })
start(props: { item: any, index: number })
end(props: { item: any, index: number })
示例
<script setup lang="ts">
import { ref } from 'vue';
import { Vue3ScrollMore } from 'vue3-scroll-more'const list = ref(new Array(20).fill(0).map((_, ind) => ("菜单" + ind)))function sideChange(option: any) {
console.log(option)
}
const currentIndex = ref(0)
function setCurrentIndex(ind: number) {
currentIndex.value = ind
}
script><template>
<div class="tags-view-container">
<vue3-scroll-more :data="list" :side-change="sideChange">
<template v-slot="{ item, index }">
<span class="tags-view-item" :class="currentIndex === index 'active' : ''" @click="setCurrentIndex(index)">
{{ item }}
<el-icon v-if="index !== 0" class="el-icon-close" title="关闭">
<Close />
el-icon>
span>
template>
vue3-scroll-more>
div>
template><style lang="scss" scoped>
.tags-view-container {
padding-bottom: 4px;
border-bottom: 1px solid #d8dce5;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04); .tags-view-item {
display: inline-block;
position: relative;
cursor: pointer;
height: 26px;
line-height: 26px;
border: 1px solid #d8dce5;
color: #495060;
background: #fff;
padding: 0 8px;
font-size: 12px;
margin-left: 5px;
margin-top: 4px;
white-space: nowrap; &.active {
background-color: #42b983;
color: #fff;
border-color: #42b983; &::before {
content: "";
background: #fff;
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
position: relative;
margin-right: 2px;
}
} .el-icon-close {
width: 16px;
height: 16px;
vertical-align: -2px;
margin-left: 5px;
margin-right: -5px;
}
}
}
style>
通过这个组件,横向滚动场景的交互变得更加丰富,可发挥的空间也更大。
希望能帮到有类似需求的朋友。
