vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > vue封装滚动条

使用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>

获取元素的 clientHeightscrollHeight 来计算滑块的高度以及可滚动距离,通过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封装滚动条的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文