vuecssuiant-designantdreactantantd-vueenterprisefrontendui-designvue-antdvue-antd-uivue3vuecomponent
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
245 lines
6.8 KiB
245 lines
6.8 KiB
import type { Ref } from 'vue'; |
|
import { |
|
nextTick, |
|
onActivated, |
|
watchEffect, |
|
defineComponent, |
|
onBeforeUnmount, |
|
onMounted, |
|
ref, |
|
shallowRef, |
|
watch, |
|
} from 'vue'; |
|
import addEventListenerWrap from '../vc-util/Dom/addEventListener'; |
|
import { getOffset } from '../vc-util/Dom/css'; |
|
import classNames from '../_util/classNames'; |
|
import type { MouseEventHandler } from '../_util/EventInterface'; |
|
import getScrollBarSize from '../_util/getScrollBarSize'; |
|
import { useInjectTable } from './context/TableContext'; |
|
import { useLayoutState } from './hooks/useFrame'; |
|
|
|
interface StickyScrollBarProps { |
|
scrollBodyRef: Ref<HTMLElement>; |
|
onScroll: (params: { scrollLeft?: number }) => void; |
|
offsetScroll: number; |
|
container: HTMLElement | Window; |
|
scrollBodySizeInfo: { |
|
scrollWidth: number; |
|
clientWidth: number; |
|
}; |
|
} |
|
|
|
export default defineComponent<StickyScrollBarProps>({ |
|
name: 'StickyScrollBar', |
|
inheritAttrs: false, |
|
props: ['offsetScroll', 'container', 'scrollBodyRef', 'scrollBodySizeInfo'] as any, |
|
emits: ['scroll'], |
|
setup(props, { emit, expose }) { |
|
const tableContext = useInjectTable(); |
|
const bodyScrollWidth = shallowRef(0); |
|
const bodyWidth = shallowRef(0); |
|
const scrollBarWidth = shallowRef(0); |
|
watchEffect( |
|
() => { |
|
bodyScrollWidth.value = props.scrollBodySizeInfo.scrollWidth || 0; |
|
bodyWidth.value = props.scrollBodySizeInfo.clientWidth || 0; |
|
scrollBarWidth.value = |
|
bodyScrollWidth.value && bodyWidth.value * (bodyWidth.value / bodyScrollWidth.value); |
|
}, |
|
{ flush: 'post' }, |
|
); |
|
|
|
const scrollBarRef = shallowRef(); |
|
|
|
const [scrollState, setScrollState] = useLayoutState({ |
|
scrollLeft: 0, |
|
isHiddenScrollBar: true, |
|
}); |
|
|
|
const refState = ref({ |
|
delta: 0, |
|
x: 0, |
|
}); |
|
|
|
const isActive = shallowRef(false); |
|
|
|
const onMouseUp: MouseEventHandler = () => { |
|
isActive.value = false; |
|
}; |
|
|
|
const onMouseDown: MouseEventHandler = event => { |
|
refState.value = { delta: event.pageX - scrollState.value.scrollLeft, x: 0 }; |
|
isActive.value = true; |
|
event.preventDefault(); |
|
}; |
|
|
|
const onMouseMove: MouseEventHandler = event => { |
|
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons |
|
const { buttons } = event || (window?.event as any); |
|
if (!isActive.value || buttons === 0) { |
|
// If out body mouse up, we can set isActive false when mouse move |
|
if (isActive.value) { |
|
isActive.value = false; |
|
} |
|
return; |
|
} |
|
let left: number = refState.value.x + event.pageX - refState.value.x - refState.value.delta; |
|
|
|
if (left <= 0) { |
|
left = 0; |
|
} |
|
|
|
if (left + scrollBarWidth.value >= bodyWidth.value) { |
|
left = bodyWidth.value - scrollBarWidth.value; |
|
} |
|
emit('scroll', { |
|
scrollLeft: (left / bodyWidth.value) * (bodyScrollWidth.value + 2), |
|
}); |
|
|
|
refState.value.x = event.pageX; |
|
}; |
|
|
|
const onContainerScroll = () => { |
|
if (!props.scrollBodyRef.value) { |
|
return; |
|
} |
|
const tableOffsetTop = getOffset(props.scrollBodyRef.value).top; |
|
const tableBottomOffset = tableOffsetTop + props.scrollBodyRef.value.offsetHeight; |
|
const currentClientOffset = |
|
props.container === window |
|
? document.documentElement.scrollTop + window.innerHeight |
|
: getOffset(props.container).top + (props.container as HTMLElement).clientHeight; |
|
|
|
if ( |
|
tableBottomOffset - getScrollBarSize() <= currentClientOffset || |
|
tableOffsetTop >= currentClientOffset - props.offsetScroll |
|
) { |
|
setScrollState(state => ({ |
|
...state, |
|
isHiddenScrollBar: true, |
|
})); |
|
} else { |
|
setScrollState(state => ({ |
|
...state, |
|
isHiddenScrollBar: false, |
|
})); |
|
} |
|
}; |
|
|
|
const setScrollLeft = (left: number) => { |
|
setScrollState(state => { |
|
return { |
|
...state, |
|
scrollLeft: (left / bodyScrollWidth.value) * bodyWidth.value || 0, |
|
}; |
|
}); |
|
}; |
|
|
|
expose({ |
|
setScrollLeft, |
|
}); |
|
let onMouseUpListener = null; |
|
let onMouseMoveListener = null; |
|
let onResizeListener = null; |
|
let onScrollListener = null; |
|
onMounted(() => { |
|
onMouseUpListener = addEventListenerWrap(document.body, 'mouseup', onMouseUp, false); |
|
onMouseMoveListener = addEventListenerWrap(document.body, 'mousemove', onMouseMove, false); |
|
onResizeListener = addEventListenerWrap(window, 'resize', onContainerScroll, false); |
|
}); |
|
onActivated(() => { |
|
nextTick(() => { |
|
onContainerScroll(); |
|
}); |
|
}); |
|
|
|
onMounted(() => { |
|
setTimeout(() => { |
|
watch( |
|
[scrollBarWidth, isActive], |
|
() => { |
|
onContainerScroll(); |
|
}, |
|
{ immediate: true, flush: 'post' }, |
|
); |
|
}); |
|
}); |
|
|
|
watch( |
|
() => props.container, |
|
() => { |
|
onScrollListener?.remove(); |
|
onScrollListener = addEventListenerWrap( |
|
props.container, |
|
'scroll', |
|
onContainerScroll, |
|
false, |
|
); |
|
}, |
|
{ immediate: true, flush: 'post' }, |
|
); |
|
|
|
onBeforeUnmount(() => { |
|
onMouseUpListener?.remove(); |
|
onMouseMoveListener?.remove(); |
|
onScrollListener?.remove(); |
|
onResizeListener?.remove(); |
|
}); |
|
|
|
watch( |
|
() => ({ ...scrollState.value }), |
|
(newState, preState) => { |
|
if ( |
|
newState.isHiddenScrollBar !== preState?.isHiddenScrollBar && |
|
!newState.isHiddenScrollBar |
|
) { |
|
setScrollState(state => { |
|
const bodyNode = props.scrollBodyRef.value; |
|
if (!bodyNode) { |
|
return state; |
|
} |
|
return { |
|
...state, |
|
scrollLeft: (bodyNode.scrollLeft / bodyNode.scrollWidth) * bodyNode.clientWidth, |
|
}; |
|
}); |
|
} |
|
}, |
|
{ immediate: true }, |
|
); |
|
const scrollbarSize = getScrollBarSize(); |
|
|
|
return () => { |
|
if ( |
|
bodyScrollWidth.value <= bodyWidth.value || |
|
!scrollBarWidth.value || |
|
scrollState.value.isHiddenScrollBar |
|
) { |
|
return null; |
|
} |
|
const { prefixCls } = tableContext; |
|
return ( |
|
<div |
|
style={{ |
|
height: `${scrollbarSize}px`, |
|
width: `${bodyWidth.value}px`, |
|
bottom: `${props.offsetScroll}px`, |
|
}} |
|
class={`${prefixCls}-sticky-scroll`} |
|
> |
|
<div |
|
onMousedown={onMouseDown} |
|
ref={scrollBarRef} |
|
class={classNames(`${prefixCls}-sticky-scroll-bar`, { |
|
[`${prefixCls}-sticky-scroll-bar-active`]: isActive.value, |
|
})} |
|
style={{ |
|
width: `${scrollBarWidth.value}px`, |
|
transform: `translate3d(${scrollState.value.scrollLeft}px, 0, 0)`, |
|
}} |
|
/> |
|
</div> |
|
); |
|
}; |
|
}, |
|
});
|
|
|