自定义滚动条组件(VUE)

自定义滚动条组件(VUE)

前言

实际开发的业务有这样的场景:需要对浏览器滚动条进行定制化的更改,比如滚动条的颜色、样式等。
谷歌浏览器中提供::-webkit-scrollbar属性可对滚动条样式进行简单的更改。如果需要进行更复杂的,例如滚动条宽度等的更改,就需要模拟浏览器的滚动条自己封装定制组件。

废话不多说直接上代码

HTML部分

<!-- showScroll 控制滚动条显示与否 -->
 <div class="scrollBox-work" v-if="showScroll">
	<!-- scrollBox-track-work 定义轨道 -->
   <div class="scrollBox-track-work">
	 <!-- scrollBox-thumb-work 定义滑轮 -->
     <div class="scrollBox-thumb-work"></div>
   </div>
 </div>

js部分

export default {
  name: "ScrollBar",
  props: {
    targetEle: {
      type: HTMLDivElement,
    },
  },
  data() {
    return {
      // 注销事件对象
      removeEvent: null,
      showScroll: false,
      // ref方式调用时保存的滚动元素dom
      refTargetEle: null
    };
  },
  mounted() {
    // 页面尺寸改变时,需要重新获取滚动
    window.addEventListener("resize", this.resizeFun);
  },
  beforeDestroy() {
    // 组件注销时,移除监听事件
    this.removeEvent ? this.removeEvent() : null;
    window.removeEventListener("resize", this.resizeFun);
  },
  methods: {
    // 主逻辑部分
    addScrollEvent(targetEle = this.targetEle) {
      if (!this.targetEle) this.refTargetEle = targetEle 
      if (targetEle) {
        const targetEleWidth = targetEle.clientWidth;
        const scrollWidth = targetEle.scrollWidth;
        this.showScroll = targetEleWidth >= scrollWidth ? false : true;
        this.$nextTick(function () {
          if (this.showScroll) {
            const scrollEle = document.getElementsByClassName("scrollBox-thumb-work")[0];
            const scrollTrackEle = document.getElementsByClassName(
              "scrollBox-track-work"
            )[0];
            // 轨道宽度
            const trackWidth = scrollTrackEle.clientWidth;
            // 滑轨宽度
            const thumbWidth = scrollEle.clientWidth;
            const defaultScrollLeft = scrollWidth - targetEleWidth;
            // 鼠标拖拽移动
            // 初始页面位置
            let startX = null;
            // 滑轨初始位置
            let startLeft = null;
            // start
            scrollEle.addEventListener("mousedown", startEvent);
            targetEle.addEventListener("scroll", scrollEvent);
            // event
            function startEvent(e) {
              e.preventDefault();
              startX = e.pageX;
              startLeft = scrollEle.offsetLeft;
              document.addEventListener("mousemove", moveEvent);
              document.addEventListener("mouseup", stopEvent);
            }
            function moveEvent(e) {
              // 鼠标移动距离
              const deltaY = e.pageX - startX;
              // 滑轨新的位置
              const newLeft = Math.max(
                0,
                Math.min(trackWidth - thumbWidth, startLeft + deltaY)
              );
              // 滚动元素位置
              const scrollLeft =
                (newLeft / (trackWidth - thumbWidth)) * (scrollWidth - targetEleWidth);

              targetEle.scrollLeft = scrollLeft;
              scrollEle.style.left = newLeft + "px";
            }
            function stopEvent(e) {
              document.removeEventListener("mousemove", moveEvent);
              document.removeEventListener("mouseup", stopEvent);
            }
            function scrollEvent(e) {
              const { scrollLeft, clientWidth, scrollWidth } = targetEle;
              const thumbLeft =
                (scrollLeft / (scrollWidth - clientWidth)) * (trackWidth - thumbWidth);
              scrollEle.style.left = thumbLeft + "px";
            }

            // 鼠标滚轮
            // 注册滚动事件处理程序
            if ("onmousewheel" in targetEle) {
              targetEle.onmousewheel = handleMouseWheel; // Chrome/Safari/Opera等非IE浏览器
            } else if ("DOMMouseScroll" in targetEle) {
              targetEle.addEventListerner("DOMMouseScroll", handleMouseWheel, false); // Firefox浏览器
            } else {
              targetEle.addEventListener("MozMousePixelScroll", handleMouseWheel, false); // IE浏览器
            }
            function handleMouseWheel(e) {
              if (e.deltaX !== 0) {
                e.preventDefault();
                e = e || window.event; // 兼容不同浏览器
                let deltaX = Math.max(-1, Math.min(1, e.wheelDeltaX || -e.deltaX)); // 计算滚动量
                let startLeft = scrollEle.offsetLeft;
                let speed = (defaultScrollLeft > 200 ? defaultScrollLeft : 200) / 200;
                const newLeft = Math.max(
                  0,
                  Math.min(trackWidth - thumbWidth, startLeft - speed * deltaX)
                );
                // 滚动元素位置
                const scrollLeft =
                  (newLeft / (trackWidth - thumbWidth)) * defaultScrollLeft;

                targetEle.scrollLeft = scrollLeft;
              }
            }

            // 注销事件
            function removeEvent() {
              document.removeEventListener("mousemove", moveEvent);
              document.removeEventListener("mouseup", stopEvent);
              scrollEle.removeEventListener("mousedown", startEvent);
              targetEle.removeEventListener("scroll", scrollEvent);
              if ("onmousewheel" in targetEle) {
                targetEle.onmousewheel = null; // Chrome/Safari/Opera等非IE浏览器
              } else if ("DOMMouseScroll" in targetEle) {
                targetEle.removeEventListener("DOMMouseScroll", handleMouseWheel); // Firefox浏览器
              } else {
                targetEle.removeEventListener("MozMousePixelScroll", handleMouseWheel); // IE浏览器
              }
            }
            this.removeEvent = removeEvent;
          }
        });
      }
    },
    resizeFun(e) {
      this.targetEle ? this.addScrollEvent() : this.addScrollEvent(this.refTargetEle);
    },
  },
};

css部分

.scrollBox-work {
  width: 100%;
  justify-content: center;
  align-items: center;
  display: flex;
  transition: 0.1s all;
  margin-top: 20px;
  .scrollBox-track-work {
    position: relative;
    width: 76px;
    height: 6px;
    background-color: #f2f3f5;
    border-radius: 3px;
    .scrollBox-thumb-work {
      width: 47px;
      height: 6px;
      background-color: #3270ff;
      border-radius: 3px;
      position: absolute;
      left: 0;
    }
  }
}

注意事项

  • targetEle 为滚动条关联的滚动元素,采用props传入方式,或者refs调用滚动条组件addScrollEvent方法建立联系。
  • (newLeft / (trackWidth - thumbWidth)) * (scrollWidth - targetEleWidth); 等比例获取滚动元素滚动距离。

实战演示

以一个步骤条组件为例,与滚动条组件实现关联

<div class="c-steps">
	...代码省略
</div>
<ScrollBar ref="scrollBar" />
import ScrollBar from '../ScrollBar/ScrollBar.vue'
export default {
  components: { ScrollBar },
  mounted() {
    this.$refs.scrollBar?.addScrollEvent(this.getTargetEle())
    window.addEventListener('resize', this.resizeFun)
  },
  methods: {
    getTargetEle() {
      return document.getElementsByClassName("c-steps")[0]
    },
    resizeFun() {
      this.$refs.scrollBar?.addScrollEvent(this.getTargetEle())
    },
  },
};

注:ref调用优势为,滚动元素数据为异步加载形式,可以在异步回调函数中,进行滚动的关联绑定。(后续优化方案:使用MutationObserver 构造函数,监听滚动元素尺寸改变,重新调用addScrollEvent方法。

最后附上实现效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值