ant-design-vue/components/vc-virtual-list/ScrollBar.tsx

266 lines
7.2 KiB
Vue
Raw Normal View History

2021-06-26 01:35:40 +00:00
import type { PropType } from 'vue';
import { defineComponent, reactive } from 'vue';
2020-09-28 11:14:00 +00:00
import classNames from '../_util/classNames';
import createRef from '../_util/createRef';
import raf from '../_util/raf';
2020-12-10 09:40:46 +00:00
import supportsPassive from '../_util/supportsPassive';
2020-09-28 11:14:00 +00:00
import PropTypes from '../_util/vue-types';
const MIN_SIZE = 20;
2020-10-01 09:20:10 +00:00
interface ScrollBarState {
dragging: boolean;
pageY: number | null;
startTop: number | null;
visible: boolean;
}
function getPageY(e: MouseEvent | TouchEvent) {
2020-09-28 11:14:00 +00:00
return 'touches' in e ? e.touches[0].pageY : e.pageY;
}
2020-10-01 09:20:10 +00:00
export default defineComponent({
2020-09-28 11:14:00 +00:00
name: 'ScrollBar',
inheritAttrs: false,
props: {
prefixCls: PropTypes.string,
scrollTop: PropTypes.number,
scrollHeight: PropTypes.number,
height: PropTypes.number,
count: PropTypes.number,
2020-10-01 09:20:10 +00:00
onScroll: {
type: Function as PropType<(scrollTop: number) => void>,
},
onStartMove: {
type: Function as PropType<() => void>,
},
onStopMove: {
type: Function as PropType<() => void>,
},
2020-09-28 11:14:00 +00:00
},
setup() {
return {
moveRaf: null,
scrollbarRef: createRef(),
thumbRef: createRef(),
visibleTimeout: null,
2020-10-01 09:20:10 +00:00
state: reactive<ScrollBarState>({
2020-09-28 11:14:00 +00:00
dragging: false,
pageY: null,
startTop: null,
visible: false,
}),
2020-09-28 11:14:00 +00:00
};
},
watch: {
scrollTop: {
handler() {
this.delayHidden();
},
flush: 'post',
},
},
mounted() {
2021-12-11 06:59:52 +00:00
this.scrollbarRef.current?.addEventListener(
2020-12-10 09:40:46 +00:00
'touchstart',
this.onScrollbarTouchStart,
2020-12-18 10:54:18 +00:00
supportsPassive ? ({ passive: false } as EventListenerOptions) : false,
2020-12-10 09:40:46 +00:00
);
2021-12-11 06:59:52 +00:00
this.thumbRef.current?.addEventListener(
2020-12-10 09:40:46 +00:00
'touchstart',
this.onMouseDown,
2020-12-18 10:54:18 +00:00
supportsPassive ? ({ passive: false } as EventListenerOptions) : false,
2020-12-10 09:40:46 +00:00
);
2020-09-28 11:14:00 +00:00
},
2020-09-28 13:21:43 +00:00
beforeUnmount() {
2020-09-28 11:14:00 +00:00
this.removeEvents();
clearTimeout(this.visibleTimeout);
},
methods: {
delayHidden() {
clearTimeout(this.visibleTimeout);
this.state.visible = true;
2020-09-29 07:16:56 +00:00
2020-09-28 11:14:00 +00:00
this.visibleTimeout = setTimeout(() => {
this.state.visible = false;
}, 2000);
},
2020-10-01 09:20:10 +00:00
onScrollbarTouchStart(e: TouchEvent) {
2020-09-28 11:14:00 +00:00
e.preventDefault();
},
2020-10-01 09:20:10 +00:00
onContainerMouseDown(e: MouseEvent) {
2020-09-28 11:14:00 +00:00
e.stopPropagation();
e.preventDefault();
},
// ======================= Clean =======================
patchEvents() {
window.addEventListener('mousemove', this.onMouseMove);
window.addEventListener('mouseup', this.onMouseUp);
2020-12-10 09:40:46 +00:00
this.thumbRef.current.addEventListener(
'touchmove',
this.onMouseMove,
2020-12-18 10:54:18 +00:00
supportsPassive ? ({ passive: false } as EventListenerOptions) : false,
2020-12-10 09:40:46 +00:00
);
2020-09-28 11:14:00 +00:00
this.thumbRef.current.addEventListener('touchend', this.onMouseUp);
},
removeEvents() {
window.removeEventListener('mousemove', this.onMouseMove);
window.removeEventListener('mouseup', this.onMouseUp);
2020-12-10 09:40:46 +00:00
this.scrollbarRef.current.removeEventListener(
'touchstart',
this.onScrollbarTouchStart,
2020-12-18 10:54:18 +00:00
supportsPassive ? ({ passive: false } as EventListenerOptions) : false,
2020-12-10 09:40:46 +00:00
);
this.thumbRef.current.removeEventListener(
'touchstart',
this.onMouseDown,
2020-12-18 10:54:18 +00:00
supportsPassive ? ({ passive: false } as EventListenerOptions) : false,
2020-12-10 09:40:46 +00:00
);
this.thumbRef.current.removeEventListener(
'touchmove',
this.onMouseMove,
2020-12-18 10:54:18 +00:00
supportsPassive ? ({ passive: false } as EventListenerOptions) : false,
2020-12-10 09:40:46 +00:00
);
2020-09-28 11:14:00 +00:00
this.thumbRef.current.removeEventListener('touchend', this.onMouseUp);
raf.cancel(this.moveRaf);
},
// ======================= Thumb =======================
2020-10-01 09:20:10 +00:00
onMouseDown(e: MouseEvent | TouchEvent) {
2020-09-28 11:14:00 +00:00
const { onStartMove } = this.$props;
Object.assign(this.state, {
dragging: true,
pageY: getPageY(e),
startTop: this.getTop(),
});
onStartMove();
this.patchEvents();
e.stopPropagation();
e.preventDefault();
},
2020-10-01 09:20:10 +00:00
onMouseMove(e: MouseEvent | TouchEvent) {
2020-09-28 11:14:00 +00:00
const { dragging, pageY, startTop } = this.state;
const { onScroll } = this.$props;
raf.cancel(this.moveRaf);
if (dragging) {
const offsetY = getPageY(e) - pageY;
const newTop = startTop + offsetY;
const enableScrollRange = this.getEnableScrollRange();
const enableHeightRange = this.getEnableHeightRange();
2020-11-04 10:59:08 +00:00
const ptg = enableHeightRange ? newTop / enableHeightRange : 0;
2020-09-28 11:14:00 +00:00
const newScrollTop = Math.ceil(ptg * enableScrollRange);
this.moveRaf = raf(() => {
onScroll(newScrollTop);
});
}
},
onMouseUp() {
const { onStopMove } = this.$props;
this.state.dragging = false;
onStopMove();
this.removeEvents();
},
// ===================== Calculate =====================
getSpinHeight() {
const { height, count } = this.$props;
let baseHeight = (height / count) * 10;
baseHeight = Math.max(baseHeight, MIN_SIZE);
baseHeight = Math.min(baseHeight, height / 2);
return Math.floor(baseHeight);
},
getEnableScrollRange() {
const { scrollHeight, height } = this.$props;
2020-11-04 10:59:08 +00:00
return scrollHeight - height || 0;
2020-09-28 11:14:00 +00:00
},
getEnableHeightRange() {
const { height } = this.$props;
const spinHeight = this.getSpinHeight();
2020-11-04 10:59:08 +00:00
return height - spinHeight || 0;
2020-09-28 11:14:00 +00:00
},
getTop() {
const { scrollTop } = this.$props;
const enableScrollRange = this.getEnableScrollRange();
const enableHeightRange = this.getEnableHeightRange();
2020-11-04 10:59:08 +00:00
if (scrollTop === 0 || enableScrollRange === 0) {
return 0;
}
2020-09-28 11:14:00 +00:00
const ptg = scrollTop / enableScrollRange;
return ptg * enableHeightRange;
},
2021-06-22 07:33:11 +00:00
// Not show scrollbar when height is large than scrollHeight
showScroll() {
2020-11-04 10:59:08 +00:00
const { height, scrollHeight } = this.$props;
2021-06-22 07:33:11 +00:00
return scrollHeight > height;
2020-11-04 10:59:08 +00:00
},
2020-09-28 11:14:00 +00:00
},
render() {
// eslint-disable-next-line no-unused-vars
2021-06-22 07:33:11 +00:00
const { dragging, visible } = this.state;
2020-09-28 11:14:00 +00:00
const { prefixCls } = this.$props;
const spinHeight = this.getSpinHeight() + 'px';
const top = this.getTop() + 'px';
2021-06-22 07:33:11 +00:00
const canScroll = this.showScroll();
const mergedVisible = canScroll && visible;
2020-09-28 11:14:00 +00:00
return (
<div
ref={this.scrollbarRef}
2021-06-22 07:33:11 +00:00
class={classNames(`${prefixCls}-scrollbar`, {
[`${prefixCls}-scrollbar-show`]: canScroll,
})}
2020-09-28 11:14:00 +00:00
style={{
width: '8px',
top: 0,
bottom: 0,
right: 0,
position: 'absolute',
2021-06-22 07:33:11 +00:00
display: mergedVisible ? undefined : 'none',
2020-09-28 11:14:00 +00:00
}}
onMousedown={this.onContainerMouseDown}
onMousemove={this.delayHidden}
>
<div
ref={this.thumbRef}
class={classNames(`${prefixCls}-scrollbar-thumb`, {
[`${prefixCls}-scrollbar-thumb-moving`]: dragging,
})}
style={{
width: '100%',
height: spinHeight,
top,
left: 0,
position: 'absolute',
background: 'rgba(0, 0, 0, 0.5)',
borderRadius: '99px',
cursor: 'pointer',
userSelect: 'none',
}}
onMousedown={this.onMouseDown}
/>
</div>
);
},
2020-10-01 09:20:10 +00:00
});