自定义滚动条,兼容IE

62 阅读1分钟
<template>
  <div class="the-scrollbar" ref="el" @mouseenter="onEnter" @mouseleave="onLeave">
    <div ref="wrap" class="the-scrollbar-wrap" :style="wrapStyle">
      <slot></slot>
    </div>
    <!-- <transition name="fade"> -->
      <p
        class="the-scrollbar-thumb"
        ref="thumbY"
        :style="thumbStyle.y"
        v-show="showThumb1"
      ></p>
    <!-- </transition> -->
    <!-- <transition name="fade"> -->
      <p
        class="the-scrollbar-thumb"
        ref="thumbX"
        :style="thumbStyle.x"
        v-show="showThumb1"
      ></p>
    <!-- </transition> -->
  </div>
</template>
<script>
/** 滚动条的厚度 */
const scrollbarSize = (function() {
    const el = document.createElement("div");
    el.style.width = "100px";
    el.style.height = "100px";
    el.style.overflow = "scroll";
    document.body.appendChild(el);
    const width = el.offsetWidth - el.clientWidth;
    document.body.removeChild(el)
    return width;
})();

/**
 * 滚动条组件
 */
export default {
    name: "Scrollbar",
    props: {
        /** 滚动条颜色 */
        thumbColor: {
            type: String,
            default: "#ccc"
        },
        /** 滚动条厚度 */
        thumbSize: {
            type: Number,
            default: 8
        },
        /**
         * 内部有点击事件时,延时更新滚动条的时间,0为不执行,单位毫秒
         * - 使用场景:内部有子节点尺寸变动撑开包裹器的滚动尺寸时,并且带有动画的情况,这时设置的延迟就为动画持续时间
         */
        clickUpdateDelay: {
            type: Number,
            default: 0
        },
    },
    data() {
      return {
        /** 包围器节点样式 */
        wrapStyle: {
          height: "",
          width: ""
        },
        thumbStyle: {
            x: {
                width: "",
                height: "",
                left: "",
                bottom: "",
                transform: "",
                borderRadius: "",
                backgroundColor: this.thumbColor
            },
            y: {
                width: "",
                height: "",
                top: "",
                right: "",
                transform: "",
                borderRadius: "",
                backgroundColor: this.thumbColor
            }
        },
        showThumb1: false,

        /** 是否摁下开始拖拽 */
        isDrag: false,
        /** 是否垂直模式 */
        vertical: false,
        /** 摁下滚动条时的偏移量 */
        deviation: 0,
        /** 更新延时器 */
        timer: ''
      }
    },
    methods: {
      /**
         * 更新包裹容器样式
         * - !!!注意:如果是动态设置组件父容器的边框时,需要手动执行该方法,
         * 原因是父容器的边框会影响当前设置的包围盒宽度,导致滚动条的高度有所变化,也就是跟`css`中设置
         * `box-sizing: border-box;`的原理一样
         */
      updateWrapStyle() {
            const parent = this.$refs.el.parentNode
            parent.style.overflow = "hidden"; // 这里一定要将父元素设置超出隐藏,不然弹性盒子布局时会撑开宽高
            const css = getComputedStyle(parent);
            // console.log("父元素边框尺寸 >>", css.borderLeftWidth, css.borderRightWidth, css.borderTopWidth, css.borderBottomWidth);
            this.wrapStyle.width = `calc(100% + ${scrollbarSize}px + ${css.borderLeftWidth} + ${css.borderRightWidth})`;
            this.wrapStyle.height = `calc(100% + ${scrollbarSize}px + ${css.borderTopWidth} + ${css.borderBottomWidth})`;
        },
                /** 初始化滚动指示器样式 */
         initThumbStyle() {
            this.thumbStyle.y.right = this.thumbStyle.y.top = "0px";
            this.thumbStyle.y.width = this.thumbSize + "px";
           this.thumbStyle.x.bottom = this.thumbStyle.x.left = "0px";
           this.thumbStyle.x.height = this.thumbSize + "px";
           this.thumbStyle.x.borderRadius = this.thumbStyle.y.borderRadius = `${this.thumbSize / 2}px`;
        },

        /**
         * 更新滚动指示器样式
         * - 可以外部主动调用
         */
         updateThumbStyle() {
            const wrapEl = this.$refs.wrap;
            if (wrapEl) {
                let height = wrapEl.clientHeight / wrapEl.scrollHeight * 100;
                if (height >= 100) {
                    height = 0;
                }
                this.thumbStyle.y.height = height + "%";
                this.thumbStyle.y.transform = `translate3d(0, ${wrapEl.scrollTop / wrapEl.scrollHeight * wrapEl.clientHeight}px, 0)`;

                // console.log("scrollWidth >>", wrapEl.scrollWidth);
                // console.log("scrollLeft >>", wrapEl.scrollLeft);
                // console.log("clientWidth >>", wrapEl.clientWidth);
                // console.log("offsetWidth >>", wrapEl.offsetWidth);
                let width = (wrapEl.clientWidth / wrapEl.scrollWidth) * 100;
                if (width >= 100) {
                    width = 0;
                }
                this.thumbStyle.x.width = width + "%";
                this.thumbStyle.x.transform = `translate3d(${wrapEl.scrollLeft / wrapEl.scrollWidth * wrapEl.clientWidth}px, 0, 0)`;
                // console.log("------------------------------------");
            }
        },
        
         onDragStart(event) {
            // console.log("摁下 >>", event);
            const _thumbX = this.$refs.thumbX;
            const _thumbY = this.$refs.thumbY;
            const target = event.target;
            if (_thumbX.contains(target)) {
                this.isDrag = true;
                this.vertical = false;
                this.deviation = event.clientX - _thumbX.getBoundingClientRect().left;
            }
            if (_thumbY.contains(target)) {
                this.isDrag = true;
                this.vertical = true;
                this.deviation = event.clientY - _thumbY.getBoundingClientRect().top;
            }
        },

         onDragMove(event) {
            if (!this.isDrag) return;
            // console.log("拖拽移动 >>", event.offsetY, event.clientY, event);
            const wrapEl = this.$refs.wrap;
            if (this.vertical) {
                const wrapTop = wrapEl.getBoundingClientRect().top;
                const wrapHeight = wrapEl.clientHeight;
                let value = event.clientY - wrapTop;
                wrapEl.scrollTop = (value - this.deviation) / wrapHeight * wrapEl.scrollHeight;
            } else {
                const wrapLeft = wrapEl.getBoundingClientRect().left;
                const wrapWidth = wrapEl.clientWidth;
                let value = event.clientX - wrapLeft;
                wrapEl.scrollLeft = (value - this.deviation) / wrapWidth * wrapEl.scrollWidth;
            }
        },

       onDragEnd(event) {
            // console.log("抬起");
            this.isDrag = false;
            if (this.$refs.el.contains(event.target)) {
                if (this.clickUpdateDelay > 0) {
                    // console.log("执行");
                    this.timer && clearTimeout(this.timer);
                    this.timer = setTimeout(this.updateThumbStyle, this.clickUpdateDelay);
                }
            } else {
                this.showThumb = false;
            }
        },

         onEnter() {
            this.showThumb = true;
            this.updateThumbStyle();
        },

         onLeave() {
            if (!this.isDrag) {
                this.showThumb = false;
            }
        }
    },
    mounted () {
            // console.log("onMounted >>", el.value!.clientHeight);
            // console.log("scrollbarSize >>", scrollbarSize);
            this.updateWrapStyle();
            this.initThumbStyle();
            this.$refs.wrap && this.$refs.wrap.addEventListener("scroll", this.updateThumbStyle);
            document.addEventListener("mousedown", this.onDragStart);
            document.addEventListener("mousemove", this.onDragMove);
            document.addEventListener("mouseup", this.onDragEnd);

            this.showThumb1 = true
        },
        destroyed () {
          this.$refs.wrap && this.$refs.wrap.removeEventListener("scroll", this.updateThumbStyle);
            document.removeEventListener("mousedown", this.onDragStart);
            document.removeEventListener("mousemove", this.onDragMove);
            document.removeEventListener("mouseup", this.onDragEnd);
            this.timer && clearTimeout(this.timer);
        }
}
</script>
<style lang="less">
.the-scrollbar {
  width: 100%;
  height: 100%;
  overflow: hidden;
  position: relative;
  .the-scrollbar-wrap {
    overflow: scroll;
  }
  .the-scrollbar-thumb {
    position: absolute;
    z-index: 10;
    outline: none;
    border: none;
  }
}
</style>