用vue写轮子的一些心得(十三)——scroll滚动组件

 

需求分析

  • 可模拟原生滚动条。
  • 在滚动区域滚动滚轮内容可进行滚动。
  • 鼠标点击滚动条拖拽可滚动内容。
  • 内容区域滚动条关系为等比关系,内容滑动多少滚动条就滚动多少。

方法实现

一、思路:

1、先在scroll组件容器外层固定高宽并设置overflow:hidden;,这样在容器内的内容就不会跑出容器外,这是做滚动组件的基础。

2、监听wheel鼠标滚轮事件,判断当鼠标在容器中进行滚动时触发滚动事件,并对Y轴进行translateY位移(性能好)。判断最顶端与最低端的值,超过这个值则停止滚动抵达边界。

3、用css写一个模拟滚动条,模仿原生滚动条特性,监听鼠标点击事件、鼠标移动事件、鼠标松开事件,当点击滚动条时根据鼠标移动事件的位移进行内容区Y轴的移动,并滚动条等比例移动,送开鼠标则停止移动。

4、内容区与容器位置和滚动条与滚动条区域为等比关系,两者间保持联动。

 

二、方法实现

1、datePicker外部组件传参定义:

<template>
    <div class="box">
        <t-scroll style="width: 300px; height: 400px;">
            <p id="test2">1111122222222222333</p>
            <p>2</p>
            <p>3</p>
            <p>4</p>
            <p>5</p>
            <p>6</p>
            <p>7</p>
            <p>8</p>
            <p>9</p>
            <p>10</p>
        </t-scroll>
    </div>
</template>

2、datePicker内部组件实现

html

<template>
  <div ref="parent" class="t-scroll-content"
       @mouseenter="onMouseEnter"
       @mousemove="onMouseMove"
       @mouseleave="onMouseLeave"
       @wheel="onWheel">
    <div ref="child" class="t-scroll-content-box" :style="{transform: `translateY(${this.contentY}px)`}">
      <slot></slot>
    </div>
    <div class="t-scroll-content-track" v-show="scrollBarVisible">
      <div class="t-scroll-content-bar" ref="bar" @mousedown="onMouseDownScrollBar"
           @selectstart="onSelectStartScrollBar">
        <div class="t-scroll-content-inner"></div>
      </div>
    </div>
  </div>
</template>

js

<script>
  export default {
    name: 'TScroll',
    data() {
      return {
        scrollBarVisible: false,
        isScrolling: false,
        startPosition: undefined,
        endPosition: undefined,
        scrollBarY: 0,
        contentY: 0,
        barHeight: undefined,
        parentHeight: undefined,
        mouseIn: false
      }
    },

    computed: {
      maxScrollHeight() {
        return this.parentHeight - this.barHeight
      },
      childHeight() {
        return this.$refs.child.getBoundingClientRect().height
      }
    },

    mounted() {
      this.listenToDocument()
      this.parentHeight = this.$refs.parent.getBoundingClientRect().height
      this.updateScrollBar()
    },

    methods: {
      listenToDocument() {
        document.addEventListener('mousemove', e => this.onMouseMoveScrollBar(e))
        document.addEventListener('mouseup', e => this.onMouseUpScrollBar())
      },
      calculateContentYFromDeltaY(deltaY) {
        let contentY = this.contentY
        if (deltaY > 20) {
          contentY -= 20 * 3
        } else if (deltaY < -20) {
          contentY -= -20 * 3
        } else {
          contentY -= deltaY * 3
        }
        return contentY
      },
      onWheel(e) {
        this.updateContentY(e.deltaY, () => e.preventDefault())
        this.updateScrollBar()
      },
      updateContentY(deltaY, fn) {
        let maxHeight = this.calculateContentYMax()
        this.contentY = this.calculateContentYFromDeltaY(deltaY)
        if (this.contentY > 0) {
          this.contentY = 0
        } else if (this.contentY < -maxHeight) {
          this.contentY = -maxHeight
        } else {
          fn && fn()
        }
      },
      calculateContentYMax() {
        let {borderTopWidth, borderBottomWidth, paddingTop, paddingBottom} = window.getComputedStyle(this.$refs.parent)
        borderTopWidth = parseInt(borderTopWidth)
        borderBottomWidth = parseInt(borderBottomWidth)
        paddingTop = parseInt(paddingTop)
        paddingBottom = parseInt(paddingBottom)
        return this.childHeight - this.parentHeight + (borderTopWidth + borderTopWidth + paddingTop + paddingBottom)
      },
      updateScrollBar() {
        let parentHeight = this.parentHeight
        let childHeight = this.childHeight
        this.barHeight = parentHeight * parentHeight / childHeight
        this.$refs.bar.style.height = this.barHeight + 'px'
        this.scrollBarY = -parentHeight * this.contentY / childHeight
        this.$refs.bar.style.transform = `translateY(${this.scrollBarY}px)`
      },
      onMouseEnter() {
        this.scrollBarVisible = true
        this.mouseIn = true
      },
      onMouseLeave() {
        this.mouseIn = false
        if (!this.isScrolling) {
          this.scrollBarVisible = false
        }
      },
      onMouseMove() {
        this.mouseIn = true
      },
      onMouseDownScrollBar(e) {
        this.isScrolling = true
        let {screenX, screenY} = e
        this.startPosition = {x: screenX, y: screenY}
      },
      onMouseMoveScrollBar(e) {
        if (!this.isScrolling) {return}
        this.endPosition = {x: e.screenX, y: e.screenY}
        let delta = {x: this.endPosition.x - this.startPosition.x, y: this.endPosition.y - this.startPosition.y}
        this.scrollBarY = this.calculateScrollBarY(delta)
        this.contentY = -(this.childHeight * this.scrollBarY / this.parentHeight)
        this.startPosition = this.endPosition
        this.$refs.bar.style.transform = `translate(0px, ${this.scrollBarY}px)`
      },
      calculateScrollBarY(delta) {
        let newValue = parseInt(this.scrollBarY) + delta.y
        if (newValue < 0) {
          newValue = 0
        } else if (newValue > this.maxScrollHeight) {
          newValue = this.maxScrollHeight
        }
        return newValue
      },
      onMouseUpScrollBar(e) {
        this.isScrolling = false
        if (!this.mouseIn) {
          this.scrollBarVisible = false
        }
      },
      onSelectStartScrollBar(e) {
        e.preventDefault()
      }

    }
  }
</script>

完整代码可参考我的GitHub:https://github.com/A-Tione/tione/blob/master/src/scroll/scroll.vue

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值