【Nova UI】十六、打造组件库之滚动条组件(中):探秘滑块的计算逻辑
序言
在上篇文章中,我们完成了滚动条组件开发的前期准备工作,包括理论推导、布局规划和基础设置。现在,我们将把这些准备转化为实际代码,开启滚动条组件的具体开发之旅🌟。我们会详细阐述如何实现各项功能,解决开发中的技术挑战,为用户带来更好的滚动体验🌐。
滚动条
在设计滚动条组件时,我们需要考虑横向和纵向两个方向的滚动条。这两个方向的滚动条在功能实现和表现形式上有诸多相似之处,但也存在一些细微的差异。为了提高代码的可维护性、可复用性和可扩展性,我们决定将滚动条单独提取出来,作为一个独立的组件进行开发。基于此,我们在 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 constexport 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><divv-show="visible"ref="barRef":class="[n.b(),n.m(direction),n.is('always', always)]"><divref="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?.wrapElementif (!wrap) returnconst { offsetHeight, scrollHeight } = wrapconst _ratio = offsetHeight / scrollHeightconst height = _ratio * offsetHeightsize.value = heightratio.value = _ratiovisible.value = offsetHeight < scrollHeight
}
通过上述计算,我们可以得到所需的数值,进而为滑块添加合适的样式 🌟。
const thumbStyle = computed(() => {return {[props.direction === 'vertical' ? 'height' : 'width']: size.value ? addUnit(size.value) : undefined}
})
滑块滚动距离
在完成滑块大小的计算和相关属性的获取之后,我们要计算滑块滚动的距离 🔢。
const scrollHandler = () => {const wrap = scrollbar?.wrapElementif (!wrap) returndistance.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
组件库:https://github.com/gmingchen/nova-ui- 基于 Vue3 + Element-plus 管理后台基础功能框架
- 预览:https://admin.gumingchen.icu
- Github:https://github.com/gmingchen/agile-admin
- Gitee:https://gitee.com/shychen/agile-admin
- 基础版后端:https://github.com/gmingchen/java-spring-boot-admin
- 文档:http://admin.gumingchen.icu/doc/
- 基于 Vue3 + Element-plus + websocket 即时聊天系统
- 预览:https://chatterbox.gumingchen.icu/
- Github:https://github.com/gmingchen/chatterbox
- Gitee:https://gitee.com/shychen/chatterbox
- 基于 node 开发的后端服务:https://github.com/gmingchen/node-server