序言
在上篇文章中,我们完成了滚动条组件开发的前期准备工作,包括理论推导、布局规划和基础设置。现在,我们将把这些准备转化为实际代码,开启滚动条组件的具体开发之旅🌟。我们会详细阐述如何实现各项功能,解决开发中的技术挑战,为用户带来更好的滚动体验🌐。
滚动条
在设计滚动条组件时,我们需要考虑横向和纵向两个方向的滚动条。这两个方向的滚动条在功能实现和表现形式上有诸多相似之处,但也存在一些细微的差异。为了提高代码的可维护性、可复用性和可扩展性,我们决定将滚动条单独提取出来,作为一个独立的组件进行开发。基于此,我们在 packages/components/scrollbar/src 目录下创建了 bar.ts 和 bar.vue 文件,它们将作为实现滚动条功能的核心文件,为后续的开发工作提供强有力的支撑 🏗️。
bar.ts
import { ExtractPropTypes } from 'vue'
import { useDirectionProp } from '@nova-ui/hooks'
import type bar from './bar.vue'
export const barProps = {
direction: useDirectionProp(),
always: Boolean,
} as const
export type BarType = ExtractPropTypes<typeof barProps>
export type BarInstance = InstanceType<typeof bar>
在 bar.ts 文件中,barProps 包含 direction 和 always 两个属性。direction 用于确定滚动条的方向,并且可以在其他组件中复用 🔄。always 这个属性用于控制滚动条是否始终显示,以此来满足不同的使用场景 🔧。通过 InstanceType<typeof bar> 导出的 BarInstance 类型,有助于在操作滚动条组件实例时确保类型的准确性,避免出现类型错误,而且会在 scrollbar 组件中使用 🌟。
bar.vue
<template>
<div
v-show="visible"
ref="barRef"
:class="[
n.b(),
n.m(direction),
n.is('always', always)
]"
>
<div
ref="thumbRef"
:class="n.e('thumb')"
>
</div>
</div>
</template>
<script lang="ts" setup>
const n = useNamespace('bar')
defineOptions({
name: 'NBar',
})
const props = defineProps(barProps)
</script>
bar.vue 的基本 DOM 结构由两个 div 元素组成,一个作为轨道,另一个作为滑块。首先,我们会定义以下几个重要的变量:
size:用于存储滑块的长或宽,方便后续的样式调整 📏。ratio:表示视图区域与可视区域的比例,这是计算滑块大小的关键数据 🔢。visible:决定滚动条是否显示,会根据不同的情况进行调整 👁️。distance:表示滑块滚动的距离,它会根据用户的操作而变化呢 📏。
滑块大小计算
我们需要获取视图区域和可视区域的 DOM 元素。在 scrollbar 组件中通过 provide 传递,在 bar 组件中通过 inject 获取,以下是以纵向滚动条为例的代码,完整代码可查看相应的仓库 🔍。
const scrollbar = inject(scrollbarInjectionKey)
const visible = ref(false)
const size = ref(0)
const ratio = ref(1)
const updateHandler = () => {
const wrap = scrollbar?.wrapElement
if (!wrap) return
const { offsetHeight, scrollHeight } = wrap
const _ratio = offsetHeight / scrollHeight
const height = _ratio * offsetHeight
size.value = height
ratio.value = _ratio
visible.value = offsetHeight < scrollHeight
}
通过上述计算,我们可以得到所需的数值,进而为滑块添加合适的样式 🌟。
const thumbStyle = computed(() => {
return {
[props.direction === 'vertical' ? 'height' : 'width']: size.value ? addUnit(size.value) : undefined
}
})
滑块滚动距离
在完成滑块大小的计算和相关属性的获取之后,我们要计算滑块滚动的距离 🔢。
const scrollHandler = () => {
const wrap = scrollbar?.wrapElement
if (!wrap) return
distance.value = wrap.scrollTop * ratio.value
}
这个计算很简单,就是将视图区域滚动的距离与比例相乘。根据这个结果,我们将修改滑块的样式:
const thumbStyle = computed(() => {
return {
[props.direction === 'vertical' ? 'height' : 'width']: size.value ? addUnit(size.value) : undefined,
transform: `translate${ props.direction === 'vertical' ? 'Y' : 'X' }(${ distance.value }px)`
}
})
至此,基本功能已成型 👏。接下来,我们来看看如何触发这些方法。
scrollbar 更新 bar的 滑块
在 VNode 更新之后,我们需要调用相应的方法,这里使用 onUpdated 生命周期来完成。为了更好地兼容不同的场景,当视图区域大小发生变化时,我们会使用 useResizeObserver 来监听 DOM 变化并调用 updateHandler 。
const update = () => {
barUpdateHandler()
barScrollHandler()
}
let stopResizeObserver: (() => void) | undefined = undefined
watch(() => props.noresize, (noresize) => {
if (noresize) {
stopResizeObserver?.()
} else {
const { stop } = useResizeObserver(wrapRef as unknown as MaybeComputedElementRef, update)
stopResizeObserver = stop
}
}, { immediate: true })
onUpdated(() => {
update()
})
scrollbar 更新 bar的 滚动距离
在设置好滑块的大小之后,我们需要处理滚动距离。我们会对滚动区域进行监听,触发 bar 的相关函数。
const barScrollHandler = () => {
barVerticalRef.value?.scroll()
barHorizontalRef.value?.scroll()
}
const onScroll = () => {
barScrollHandler()
emits('scroll', {
scrollTop: wrapRef.value?.scrollTop || 0,
scrollLeft: wrapRef.value?.scrollLeft || 0,
})
}
🦀🦀感谢看官看到这里,如果觉得文章不错的话🙌,点个关注不迷路⭐。
诚邀您加入我的微信技术交流群🎉,群里都是志同道合的开发者👨💻,大家能一起交流分享摸鱼🐟。期待与您在群里相见🚀,咱们携手在开发路上共同进步✨ !👉点我
感谢各位大侠一路相伴,实在感激! 不瞒您说,在下还有几个开源项目 📦,它们就像精心培育的幼苗 🌱,急需您的浇灌。要是您瞧着还不错,麻烦动动手指,给它们点亮几颗 Star ⭐,您的支持就是它们成长的最大动力,在此谢过各位大侠啦!
Nova UI组件库:github.com/gmingchen/n…- 基于 Vue3 + Element-plus 管理后台基础功能框架
- 预览:admin.gumingchen.icu
- Github:github.com/gmingchen/a…
- Gitee:gitee.com/shychen/agi…
- 基础版后端:github.com/gmingchen/j…
- 文档:admin.gumingchen.icu/doc/
- 基于 Vue3 + Element-plus + websocket 即时聊天系统
- 预览:chatterbox.gumingchen.icu/
- Github:github.com/gmingchen/c…
- Gitee:gitee.com/shychen/cha…
- 基于 node 开发的后端服务:github.com/gmingchen/n…