Vue自定义滚动条组件

120 阅读1分钟

近期有个项目,由于自带的滚动条太大而且样式不好看,但是需求比较特殊,所以自定义了一个滚动条组件,用于代替浏览器自带滚动条。 需要的朋友可以参考一下,有问题欢迎大佬指出

<template>
  <div class="hd-scroll scrollbox" ref="box"
  @mousewheel.stop.prevent="handleMouseWheel"
  @DOMMouseScroll.stop.prevent="handleFirefoxMouseWheel"
  @mouseenter="handleMouseEnter"
  @mouseleave="handleMouseLeave">
    <transition name="fade">
        <div :class="['scrollbar', { force: force }]" ref="bar"
        v-show="show" :style="{ 'height': barHeight + 'px'}"
        @mousedown="handleMouseDown"></div>
    </transition>
    <div ref="content">
      <slot></slot>
    </div>
  </div>
</template>

<script>
/**
 * @author linhonejie
 * 共用上传文件组件
 * 引入  import { HdScroll } from "@/components";
 *
    <HdScroll>
      <div>content</div>
    </HdScroll>
**/
export default {
  name: "HdScroll",
  props: {
    showBar: {
      type: Boolean,
      default: false
    }
  },
  data () {
    return {
      box: undefined, // 自定义滚动条盒子
      bar: undefined, // 滚动条
      barHeight: 50, // 滚动条高度
      ratio: 1, // 滚动条偏移率
      force: false, // 滚动条是否被鼠标光标按住
      hover: false, // 鼠标光标是否悬停在盒子上
      show: false // 是否显示滚动条
    }
  },
  mounted () {
    if (this.showBar) {
      this.show = true;
    }
    this.box = this.$refs.box;
    this.bar = this.$refs.bar;
    this.content = this.$refs.content
    // 滚动条全局可拖动
    document.addEventListener("mouseup", this.handleMouseUp);
    document.addEventListener("mousemove", this.handleMouseMove);
  },
  methods: {
    /**
     * 鼠标滚轮事件
     * @param {object} e 事件
     */
    handleMouseWheel (e) {
      if (e.wheelDelta < 0) {
        if (this.box.offsetHeight + this.box.scrollTop > this.content.offsetHeight) {
          return
        }
        this.box.scrollTop -= e.wheelDelta / 4;
        this.bar.style.transform = "translateY(" + (this.box.scrollTop + this.box.scrollTop / this.ratio) + "px)"
      } else {
        this.box.scrollTop -= e.wheelDelta / 4;
        this.bar.style.transform = "translateY(" + (this.box.scrollTop + this.box.scrollTop / this.ratio) + "px)"
      }
    },
    handleFirefoxMouseWheel (e) {
      if (e.wheelDelta < 0) {
        if (this.box.offsetHeight + this.box.scrollTop > this.content.offsetHeight) {
          return
        }
        this.box.scrollTop -= -(e.detail * 8);
        this.bar.style.transform = "translateY(" + (this.box.scrollTop + this.box.scrollTop / this.ratio) + "px)"
      } else {
        this.box.scrollTop -= -(e.detail * 8);
        this.bar.style.transform = "translateY(" + (this.box.scrollTop + this.box.scrollTop / this.ratio) + "px)"
      }
    },
    /**
     * 鼠标按下
     * @param {object} e 事件
     */
    handleMouseDown (e) {
      if (e.target === this.bar) {
        this.box.prevY = e.pageY;
        this.force = true;
      }
    },
    /**
     * 鼠标按键释放
     */
    handleMouseUp () {
      this.force = false;
      this.box.prevY = null;
      if (!this.hover) {
        if (!this.showBar) {
          this.show = false;
        }
      }
    },
    /**
     * 鼠标移动
     * @param {object} e 事件
     */
    handleMouseMove (e) {
      if (this.force) {
        // 阻止默认选中事件(IE下无效)
        e.preventDefault();
        if (e.pageY - this.box.prevY > 0) {
          if (this.box.offsetHeight + this.box.scrollTop > this.content.offsetHeight) {
            return
          }
          this.box.scrollTop += (e.pageY - this.box.prevY) * this.ratio;
          this.bar.style.transform = "translateY(" + (this.box.scrollTop + this.box.scrollTop / this.ratio) + "px)";
          this.box.prevY = e.pageY;
        } else {
          this.box.scrollTop += (e.pageY - this.box.prevY) * this.ratio;
          this.bar.style.transform = "translateY(" + (this.box.scrollTop + this.box.scrollTop / this.ratio) + "px)";
          this.box.prevY = e.pageY;
        }
      }
    },
    /**
     * 鼠标光标进入盒子范围
     */
    handleMouseEnter () {
      this.hover = true
      if (this.box.scrollHeight > this.box.offsetHeight) {
        // 修正进度条高度和位置(建议通过事件触发)
        this.barHeight = this.box.offsetHeight ** 2 / this.box.scrollHeight;
        this.ratio = (this.box.scrollHeight - this.box.offsetHeight) / (this.box.offsetHeight - this.barHeight);
        this.bar.style.transform = "translateY(" + (this.box.scrollTop + this.box.scrollTop / this.ratio) + "px)";
        // 显示滚动条
        this.$nextTick(() => {
          this.show = true;
        })
      }
    },
    /**
     * 鼠标光标离开盒子范围
     */
    handleMouseLeave () {
      this.hover = false;
      if (!this.force) {
        if (!this.showBar) {
          this.show = false;
        }
      }
    },

    /**
     * 组件外触发修改scrolltop
     */
    setBoxScrollTop (scrollTop) {
      this.box.scrollTop = scrollTop;
    }
  }
}
</script>

<style lang="scss" scoped>
  // 滚动条宽度
  $scrollbar-width: 6px;

  .scrollbox {
    width: 100%;
    height: 100%;
    position: relative;
    // padding-right: $scrollbar-width;
    overflow-y: hidden;
  }
  .scrollbar {
    width: $scrollbar-width;
    // height: 100%;
    background-color: rgba($color: #000, $alpha: 0.3);
    position: absolute;
    right: 0;
    border-radius: $scrollbar-width / 2;
    &:hover {
      background-color: gray;
    }
    &.force {
      background-color: gray;
    }
    z-index: 2;
  }

  // Vue进入离开动画
  .fade-enter-active, .fade-leave-active {
    transition: opacity .5s;
  }
  .fade-enter, .fade-leave-to {
    opacity: 0;
  }
</style>