mirror of
https://gitee.com/element-plus/element-plus.git
synced 2024-12-14 17:31:02 +08:00
199 lines
5.5 KiB
Vue
199 lines
5.5 KiB
Vue
<template>
|
|
<transition :name="ns.b('fade')">
|
|
<div
|
|
v-show="always || visible"
|
|
ref="instance"
|
|
:class="[ns.e('bar'), ns.is(bar.key)]"
|
|
@mousedown="clickTrackHandler"
|
|
>
|
|
<div
|
|
ref="thumb"
|
|
:class="ns.e('thumb')"
|
|
:style="thumbStyle"
|
|
@mousedown="clickThumbHandler"
|
|
/>
|
|
</div>
|
|
</transition>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import {
|
|
computed,
|
|
defineComponent,
|
|
inject,
|
|
onBeforeUnmount,
|
|
ref,
|
|
toRef,
|
|
} from 'vue'
|
|
import { isClient, useEventListener } from '@vueuse/core'
|
|
import { scrollbarContextKey } from '@element-plus/tokens'
|
|
import { throwError } from '@element-plus/utils'
|
|
import { useNamespace } from '@element-plus/hooks'
|
|
import { BAR_MAP, renderThumbStyle } from './util'
|
|
|
|
import { thumbProps } from './thumb'
|
|
|
|
const COMPONENT_NAME = 'Thumb'
|
|
export default defineComponent({
|
|
name: COMPONENT_NAME,
|
|
props: thumbProps,
|
|
|
|
setup(props) {
|
|
const scrollbar = inject(scrollbarContextKey)
|
|
const ns = useNamespace('scrollbar')
|
|
|
|
if (!scrollbar)
|
|
throwError(COMPONENT_NAME, 'can not inject scrollbar context')
|
|
|
|
const instance = ref<HTMLDivElement>()
|
|
const thumb = ref<HTMLDivElement>()
|
|
|
|
const thumbState = ref({})
|
|
const visible = ref(false)
|
|
|
|
let cursorDown = false
|
|
let cursorLeave = false
|
|
let originalOnSelectStart:
|
|
| ((this: GlobalEventHandlers, ev: Event) => any)
|
|
| null = isClient ? document.onselectstart : null
|
|
|
|
const bar = computed(
|
|
() => BAR_MAP[props.vertical ? 'vertical' : 'horizontal']
|
|
)
|
|
|
|
const thumbStyle = computed(() =>
|
|
renderThumbStyle({
|
|
size: props.size,
|
|
move: props.move,
|
|
bar: bar.value,
|
|
})
|
|
)
|
|
|
|
const offsetRatio = computed(
|
|
() =>
|
|
// offsetRatioX = original width of thumb / current width of thumb / ratioX
|
|
// offsetRatioY = original height of thumb / current height of thumb / ratioY
|
|
// instance height = wrap height - GAP
|
|
instance.value![bar.value.offset] ** 2 /
|
|
scrollbar.wrapElement![bar.value.scrollSize] /
|
|
props.ratio /
|
|
thumb.value![bar.value.offset]
|
|
)
|
|
|
|
const clickThumbHandler = (e: MouseEvent) => {
|
|
// prevent click event of middle and right button
|
|
e.stopPropagation()
|
|
if (e.ctrlKey || [1, 2].includes(e.button)) return
|
|
|
|
window.getSelection()?.removeAllRanges()
|
|
startDrag(e)
|
|
|
|
const el = e.currentTarget as HTMLDivElement
|
|
if (!el) return
|
|
thumbState.value[bar.value.axis] =
|
|
el[bar.value.offset] -
|
|
(e[bar.value.client] - el.getBoundingClientRect()[bar.value.direction])
|
|
}
|
|
|
|
const clickTrackHandler = (e: MouseEvent) => {
|
|
if (!thumb.value || !instance.value || !scrollbar.wrapElement) return
|
|
|
|
const offset = Math.abs(
|
|
(e.target as HTMLElement).getBoundingClientRect()[bar.value.direction] -
|
|
e[bar.value.client]
|
|
)
|
|
const thumbHalf = thumb.value[bar.value.offset] / 2
|
|
const thumbPositionPercentage =
|
|
((offset - thumbHalf) * 100 * offsetRatio.value) /
|
|
instance.value[bar.value.offset]
|
|
|
|
scrollbar.wrapElement[bar.value.scroll] =
|
|
(thumbPositionPercentage *
|
|
scrollbar.wrapElement[bar.value.scrollSize]) /
|
|
100
|
|
}
|
|
|
|
const startDrag = (e: MouseEvent) => {
|
|
e.stopImmediatePropagation()
|
|
cursorDown = true
|
|
document.addEventListener('mousemove', mouseMoveDocumentHandler)
|
|
document.addEventListener('mouseup', mouseUpDocumentHandler)
|
|
originalOnSelectStart = document.onselectstart
|
|
document.onselectstart = () => false
|
|
}
|
|
|
|
const mouseMoveDocumentHandler = (e: MouseEvent) => {
|
|
if (!instance.value || !thumb.value) return
|
|
if (cursorDown === false) return
|
|
|
|
const prevPage = thumbState.value[bar.value.axis]
|
|
if (!prevPage) return
|
|
|
|
const offset =
|
|
(instance.value.getBoundingClientRect()[bar.value.direction] -
|
|
e[bar.value.client]) *
|
|
-1
|
|
const thumbClickPosition = thumb.value[bar.value.offset] - prevPage
|
|
const thumbPositionPercentage =
|
|
((offset - thumbClickPosition) * 100 * offsetRatio.value) /
|
|
instance.value[bar.value.offset]
|
|
scrollbar.wrapElement[bar.value.scroll] =
|
|
(thumbPositionPercentage *
|
|
scrollbar.wrapElement[bar.value.scrollSize]) /
|
|
100
|
|
}
|
|
|
|
const mouseUpDocumentHandler = () => {
|
|
cursorDown = false
|
|
thumbState.value[bar.value.axis] = 0
|
|
document.removeEventListener('mousemove', mouseMoveDocumentHandler)
|
|
document.removeEventListener('mouseup', mouseUpDocumentHandler)
|
|
restoreOnselectstart()
|
|
if (cursorLeave) visible.value = false
|
|
}
|
|
|
|
const mouseMoveScrollbarHandler = () => {
|
|
cursorLeave = false
|
|
visible.value = !!props.size
|
|
}
|
|
|
|
const mouseLeaveScrollbarHandler = () => {
|
|
cursorLeave = true
|
|
visible.value = cursorDown
|
|
}
|
|
|
|
onBeforeUnmount(() => {
|
|
restoreOnselectstart()
|
|
document.removeEventListener('mouseup', mouseUpDocumentHandler)
|
|
})
|
|
|
|
const restoreOnselectstart = () => {
|
|
if (document.onselectstart !== originalOnSelectStart)
|
|
document.onselectstart = originalOnSelectStart
|
|
}
|
|
|
|
useEventListener(
|
|
toRef(scrollbar, 'scrollbarElement'),
|
|
'mousemove',
|
|
mouseMoveScrollbarHandler
|
|
)
|
|
useEventListener(
|
|
toRef(scrollbar, 'scrollbarElement'),
|
|
'mouseleave',
|
|
mouseLeaveScrollbarHandler
|
|
)
|
|
|
|
return {
|
|
ns,
|
|
instance,
|
|
thumb,
|
|
bar,
|
|
thumbStyle,
|
|
visible,
|
|
clickTrackHandler,
|
|
clickThumbHandler,
|
|
}
|
|
},
|
|
})
|
|
</script>
|