bar.ts 3.13 KB
import type { Ref } from 'vue'
import {
  computed,
  defineComponent,
  getCurrentInstance,
  h,
  inject,
  onUnmounted,
  ref,
} from 'vue'
import { BAR_MAP, renderThumbStyle } from './util'
import { off, on } from '@/utils/domUtils'

export default defineComponent({
  name: 'Bar',

  props: {
    vertical: Boolean,
    size: String,
    move: Number,
  },

  setup(props) {
    const instance = getCurrentInstance()
    const thumb = ref()
    const wrap = inject('scroll-bar-wrap', {} as Ref<Nullable<HTMLElement>>) as any
    const bar = computed(() => {
      return BAR_MAP[props.vertical ? 'vertical' : 'horizontal']
    })
    const barStore = ref<Recordable>({})
    const cursorDown = ref()

    const startDrag = (e: any) => {
      e.stopImmediatePropagation()
      cursorDown.value = true
      on(document, 'mousemove', mouseMoveDocumentHandler)
      on(document, 'mouseup', mouseUpDocumentHandler)
      document.onselectstart = () => false
    }

    const clickThumbHandler = (e: any) => {
      // prevent click event of right button
      if (e.ctrlKey || e.button === 2)
        return

      window.getSelection()?.removeAllRanges()
      startDrag(e)
      barStore.value[bar.value.axis]
        = e.currentTarget[bar.value.offset]
        - (e[bar.value.client] - e.currentTarget.getBoundingClientRect()[bar.value.direction])
    }

    const clickTrackHandler = (e: any) => {
      const offset = Math.abs(
        e.target.getBoundingClientRect()[bar.value.direction] - e[bar.value.client],
      )
      const thumbHalf = thumb.value[bar.value.offset] / 2
      const thumbPositionPercentage
        = ((offset - thumbHalf) * 100) / instance?.vnode.el?.[bar.value.offset]

      wrap.value[bar.value.scroll]
        = (thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100
    }

    function mouseMoveDocumentHandler(e: any) {
      if (cursorDown.value === false) return
      const prevPage = barStore.value[bar.value.axis]

      if (!prevPage) return

      const offset
        = (instance?.vnode.el?.getBoundingClientRect()[bar.value.direction] - e[bar.value.client])
        * -1
      const thumbClickPosition = thumb.value[bar.value.offset] - prevPage
      const thumbPositionPercentage
        = ((offset - thumbClickPosition) * 100) / instance?.vnode.el?.[bar.value.offset]
      wrap.value[bar.value.scroll]
        = (thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100
    }

    function mouseUpDocumentHandler() {
      cursorDown.value = false
      barStore.value[bar.value.axis] = 0
      off(document, 'mousemove', mouseMoveDocumentHandler)
      document.onselectstart = null
    }

    onUnmounted(() => {
      off(document, 'mouseup', mouseUpDocumentHandler)
    })

    return () =>
      h(
        'div',
        {
          class: ['scrollbar__bar', `is-${bar.value.key}`],
          onMousedown: clickTrackHandler,
        },
        h('div', {
          ref: thumb,
          class: 'scrollbar__thumb',
          onMousedown: clickThumbHandler,
          style: renderThumbStyle({
            size: props.size,
            move: props.move,
            bar: bar.value,
          }),
        }),
      )
  },
})