本文主要通过下面的实例来总结滚动条的相关知识,效果如下:
需要实现的功能有:
- 基本的滚轮滑动功能
- 左右两侧有箭头,点击一次滑动一屏
- 默认锚定值为第一项
- 滑动到边界时,对应侧的箭头消失
一、组件结构
<CalendarScrollBar> 组件的结构如下:
<template>
<div class="scroll-bar">
<arrow-left />
<div class="scroll-bar__view"
<ul class="scroll-bar__items">
<li>
<scroll-bar-item />
</li>
</ul>
</div>
<arrow-right />
</div>
</template>
复制代码
二、组件功能
2.1 滚轮滑动
我们知道,滚动条的可见内容长度是小于实际的内容长度的。所以,当内容溢出边框时,需使用 overflow: hidden 对内容进行修剪、隐藏溢出。
.scroll-bar {
overflow: hidden;
.&__view {
overflow: hidden;
}
}
复制代码
效果如下:
可视元素 <div class="scroll-bar__view">
内容元素 <div class="scroll-bar__items">
2.2 点击箭头一次,切换一屏 tab
我们希望这个滚动条能实现:当点击左箭头时,滚动条会向左滑动,滑动的距离是一整个可视区的长度,如果此时到左边界的距离不满足一屏,那么滚动条会滑到左边界;同理,点击右箭头时,滚动条会向右滑动一整个可视区的长度,如果此时距离右边界不满足一屏,那么将滑到右边界。
思路:
这其实是一个平移计算问题。首先,我们使用 ref 和 offsetWidth 来获取到可见区域 view 的长度和内容区域 items 的长度,如下所示:
<div
class="scroll-bar"
ref="viewEl"
>
...
<ul =
class="scroll-bar__items"
ref="itemsEl"
>
...
</ul>
</div>
复制代码
const viewEl = ref<HTMLElement | null>()
const itemsEl = ref<HTMLElement | null>()
const viewWidth = computed(() => unref(viewEl)?.offsetWidth ?? 0)
const itemsWidth = computed(() => unref(itemsEl)?.offsetWidth ?? 0)
复制代码
然后,定义变量 distance 来表示滑动位移,并给两侧的箭头添加点击事件,通过点击事件来改变 distance。
<arrow-left @click="handleClick('left')" />
<arrow-right @click="handleClick('right')" />
复制代码
const distance = ref(0) // 滑动位移,初始默认 0 值
// 滑动位移的边界情况
const rangeDistance = (dis: number, offset: number) => {
const res = dis + offset // 滑动后的位移
const max = unref(itemsWidth) - unref(viewWidth)
// 边界处理
if (res < 0) return 0 // 位移最小值
if (res > max) return max // 位移最大值
return res
}
// 点击箭头,切换一屏
const handleClick = (arrow: 'left' | 'right') => {
arrow === 'left'
? distance.value = rangeDistance(unref(distance), -unref(viewWidth))
: distance.value = rangeDistance(unref(distance), unref(viewWidth))
}
复制代码
另外,由于可视区的宽度会随着浏览器窗口大小变化而变化,所以可以通过 watch 来监听resize。
watch(() => unref(viewEl), () => {
handleResize() // 记得作防抖处理
}, { immediate: true })
复制代码
2.3 滑动到边界时,对应侧箭头消失
最后,我们对边界情况进行处理,当滑动到边界时,对应侧的箭头将会消失。
很明显,箭头是否展示取决于 distance 的值。当值等于 0 时,说明此时滑动到了左边界,左箭头应不展示;当值等于 unref(itemsWidth) - unref(viewWidth) 时,说明此时滑动到了右边界,右箭头应不展示。
<arrow-left v-if="showLeft" ... />
<arrow-right v-if="showRight" ... />
复制代码
const showLeft = computed(() => !!unref(distance))
const showRight = computed(() => unref(distance) === (unref(itemsWidth) - unref(viewWidth))
复制代码
三、其他实践
3.1 点击滚动项,该项滑动到中间
首先,我们需要在 <CalendarScrollBar> 组件中添加一个 activeIndex 的 props,表示点击的是第几个滚动项。
<canlendar-scroll-bar
:active-index="activeIndex"
/>
复制代码
然后,借助 scrollIntoView 函数来实现点击后滑到中间的效果,该函数用法可参考 Element.scrollIntoView。
watch(() => unref(activeIndex), async (v) => {
await nextTick()
const target = unref(itemsEl)?.[v]
if (target) {
target.scrollIntoView({ behavior: 'smooth', inline: 'center' })
}
}, { immediate: true })