使用vue封装一个自定义样式的滚动条
作者:冂岛
众所周知,当容器高度固定而内容部分高度超出容器高度时,浏览器会渲染出一个可以滚动并用于显示剩余界面的条 -- 滚动条,它可以简单的样式修改,但是位置是固定的,无法移动,而我们需要改变位置的时候,它就不能满足我们的需求了,这时我们可以自己手写一个
话不多说,步入正题,先创建测试文件,测试手写滚动条是否可用
// test.vue <template> <div class="scrollLayout"> <!-- 内容 --> <div class="content" ref="content" @scroll="scroll"> <template v-for="i in 30"> <div style="width: 10rem; text-align: center">{{ i }}</div> </template> </div> <!-- 滚动条 --> <div class="scrollBar" ref="scrollBar"> <div class="slider" :style="sliderStyle"></div> </div> </div> </template> <script setup> import { ref, onMounted } from 'vue' const content = ref(null); // ref绑定的内容元素 const scrollBar = ref(null); // ref绑定的手写滚动条 const sliderHeight = ref(0); // 滑块高度 const position = ref(0); // 手写滚动条的位置 const sliderStyle = ref(`height:${sliderHeight}%;margin-top:${position}px;`); const contentCH = ref(0); // content盒子高度 const contentSH = ref(0); // content内容高度 const scrollBarCH = ref(0); // 滚动条高度 const activeScrollDistance = ref(0); // 内容滚动的距离 const contentScrollDistance = ref(0); // 内容滚动的距离 onMounted(() => { const { clientHeight, scrollHeight } = content.value; contentCH.value = clientHeight; contentSH.value = scrollHeight; scrollBarCH.value = scrollBar.value.clientHeight; sliderHeight.value = (clientHeight / scrollHeight) * 100; activeScrollDistance.value = scrollBarCH.value - scrollBarCH.value * (sliderHeight.value / 100); contentScrollDistance.value = contentSH.value - contentCH.value; }) // 内容滚动时 const scroll = () => { const { scrollTop } = content.value; position.value = (scrollTop * activeScrollDistance.value) / contentScrollDistance.value; // 滑块需要滑动的距离 }; </script> <style scoped> .scrollLayout { position: relative; padding: 1rem; font-size: 1rem; } .content { height: 20rem; overflow: auto; background: skyblue; } .scrollBar { position: absolute; top: 0; right: 1rem; width: 5px; /* 滚动条的宽度 */ height: 18rem; /* 滚动条的高度 */ background-color: pink; /* 滚动条的颜色 */ } .slider { width: 100%; background-color: palevioletred; /* 滑块的颜色 */ border-radius: 0.5rem; /* 滑块圆角 */ } </style>
获取元素的 clientHeight 和 scrollHeight 来计算滑块的高度以及可滚动距离,通过scrollTop获取滚动的距离通过 scroll 事件来监听内容的滚动,从而实现一个简单的手搓滚动条,下面开始封装。
封装前还要考虑到的问题:可否在同一页面多次复用;内容容器一般都是调接口数据进行遍历渲染,而v-for在渲染每个条目时是逐个插入到DOM中的,这说明vue会先创建一个空的父元素,并将每个条目插入到该父元素中,这意味着 通过用ref绑定父页面的内容容器provide给子组件,子组件inject到dom元素的scrollHeight 这种方法不可行。
我想了一个办法,父页面把内容通过slot给子组件把接口数据父传子,在子组件可以拿到接口数据和内容dom的scrollHeight,然后用watch监听props的接口数据,若发生变化,重新获取scrollHeight即可。
父页面使用setTimeout模拟接口:
// test.vue <template> <div class="scrollLayout"> <scroll :data="arrList"> <template v-for="i in arrList"> <div style="width: 10rem; text-align: center">num: {{ i.num }}</div> </template> </scroll> </div> </template> <script setup> import { ref } from 'vue'; import scroll from '../components/scroll.vue'; const arrList = ref([]); setTimeout(() => { for (let i = 1; i <= 30; i++) { const obj = { num: i < 10 ? '0' + i : i }; arrList.value.push(obj); } }, 3000); </script> <style lang="scss" scoped> .scrollLayout { height: 10rem; background: pink; } </style>
组件部分:
// scroll.vue <template> <div class="scrollable"> <div class="content" ref="content" @scroll="scroll"> <slot></slot> </div> <div class="scrollBar" ref="scrollBar" :style="scrollBarStyle"> <div class="slider" :style="sliderStyle"></div> </div> </div> </template> <script setup> import { ref, onMounted, computed, watch, nextTick } from 'vue'; const props = defineProps({ scrollColor: { type: String, default: '', }, sliderColor: { type: String, default: '#000', }, data: { type: Array, default: [], }, right: { type: String, default: '0', }, }); const content = ref(null); // ref内容 const scrollBar = ref(null); // ref滚动条 const contentCH = ref(0); // content盒子高度 const contentSH = ref(0); // content内容高度 const scrollBarCH = ref(0); // 滚动条高度 const activeScrollDistance = ref(0); // 内容滚动的距离 const contentScrollDistance = ref(0); // 内容滚动的距离 const sliderHeight = ref(0); // 滑块高度 const position = ref(0); // 滚动条滑动距离 const scrollBarStyle = computed(() => `right:${props.right}px;background:${props.scrollColor};`); const sliderStyle = computed(() => `height:${sliderHeight.value}%;margin-top:${position.value}px;background:${props.sliderColor};`); onMounted(() => { watch( () => props.data, () => { // nextTick确保在DOM更新完毕后再执行 nextTick(() => { const { clientHeight, scrollHeight } = content.value; console.log('容器的高度:', clientHeight, '内容高度:', scrollHeight); contentCH.value = clientHeight; contentSH.value = scrollHeight; scrollBarCH.value = scrollBar.value.clientHeight; sliderHeight.value = (clientHeight / scrollHeight) * 100; activeScrollDistance.value = scrollBarCH.value - scrollBarCH.value * (sliderHeight.value / 100); contentScrollDistance.value = contentSH.value - contentCH.value; }); }, { immediate: true, deep: true } ); }); // 监听滚动 const scroll = () => { const { scrollTop } = content.value; position.value = (scrollTop * activeScrollDistance.value) / contentScrollDistance.value; }; </script> <style lang="scss" scoped> .scrollable { position: relative; display: flex; height: 100%; overflow: hidden; } .content { height: 100%; overflow: auto; &::-webkit-scrollbar { display: none; } } .scrollBar { position: absolute; top: 0; width: 5px; height: 100%; border-radius: 5px; .slider { width: 100%; border-radius: 3px; } } </style>
这样就可以解决初始获取的scrollHeight是内容插入前的高度——即容器高度。
以上就是使用vue封装一个自定义样式的滚动条的详细内容,更多关于vue封装滚动条的资料请关注脚本之家其它相关文章!