mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-27 19:29:04 +08:00
initial commit
This commit is contained in:
5
src/components/Scrollbar/index.ts
Normal file
5
src/components/Scrollbar/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* copy from element-ui
|
||||
*/
|
||||
|
||||
export { default as Scrollbar } from './src/Scrollbar';
|
106
src/components/Scrollbar/src/Bar.tsx
Normal file
106
src/components/Scrollbar/src/Bar.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
import { renderThumbStyle, BAR_MAP } from './util';
|
||||
import { defineComponent, computed, unref, inject, Ref, reactive, ref, onBeforeUnmount } from 'vue';
|
||||
import { on, off } from '/@/utils/domUtils';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Bar',
|
||||
props: {
|
||||
vertical: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
size: String as PropType<string>,
|
||||
move: Number as PropType<number>,
|
||||
},
|
||||
setup(props) {
|
||||
const thumbRef = ref<Nullable<HTMLDivElement>>(null);
|
||||
const elRef = ref<Nullable<HTMLDivElement>>(null);
|
||||
const commonState = reactive<KeyString>({});
|
||||
const getBarRef = computed(() => {
|
||||
return BAR_MAP[props.vertical ? 'vertical' : 'horizontal'];
|
||||
});
|
||||
const parentElRef = inject('scroll-bar-wrap') as Ref<Nullable<HTMLDivElement>>;
|
||||
|
||||
function clickThumbHandler(e: any) {
|
||||
const { ctrlKey, button, currentTarget } = e;
|
||||
// prevent click event of right button
|
||||
if (ctrlKey || button === 2 || !currentTarget) {
|
||||
return;
|
||||
}
|
||||
startDrag(e);
|
||||
const bar = unref(getBarRef);
|
||||
commonState[bar.axis] =
|
||||
currentTarget[bar.offset] -
|
||||
(e[bar.client as keyof typeof e] - currentTarget.getBoundingClientRect()[bar.direction]);
|
||||
}
|
||||
|
||||
function clickTrackHandler(e: any) {
|
||||
const bar = unref(getBarRef);
|
||||
const offset = Math.abs(e.target.getBoundingClientRect()[bar.direction] - e[bar.client]);
|
||||
const thumbEl = unref(thumbRef) as any;
|
||||
const parentEl = unref(parentElRef) as any;
|
||||
const el = unref(elRef) as any;
|
||||
if (!thumbEl || !el || !parentEl) return;
|
||||
const thumbHalf = thumbEl[bar.offset] / 2;
|
||||
const thumbPositionPercentage = ((offset - thumbHalf) * 100) / el[bar.offset];
|
||||
parentEl[bar.scroll] = (thumbPositionPercentage * parentEl[bar.scrollSize]) / 100;
|
||||
}
|
||||
|
||||
function startDrag(e: Event) {
|
||||
e.stopImmediatePropagation();
|
||||
commonState.cursorDown = true;
|
||||
|
||||
on(document, 'mousemove', mouseMoveDocumentHandler);
|
||||
on(document, 'mouseup', mouseUpDocumentHandler);
|
||||
document.onselectstart = () => false;
|
||||
}
|
||||
|
||||
function mouseMoveDocumentHandler(e: any) {
|
||||
if (commonState.cursorDown === false) return;
|
||||
const bar = unref(getBarRef);
|
||||
const prevPage = commonState[bar.axis];
|
||||
const el = unref(elRef) as any;
|
||||
const parentEl = unref(parentElRef) as any;
|
||||
const thumbEl = unref(thumbRef) as any;
|
||||
if (!prevPage || !el || !thumbEl || !parentEl) return;
|
||||
const rect = el.getBoundingClientRect() as any;
|
||||
const offset = (rect[bar.direction] - e[bar.client]) * -1;
|
||||
const thumbClickPosition = thumbEl[bar.offset] - prevPage;
|
||||
const thumbPositionPercentage = ((offset - thumbClickPosition) * 100) / el[bar.offset];
|
||||
|
||||
parentEl[bar.scroll] = (thumbPositionPercentage * parentEl[bar.scrollSize]) / 100;
|
||||
}
|
||||
|
||||
function mouseUpDocumentHandler() {
|
||||
const bar = unref(getBarRef);
|
||||
commonState.cursorDown = false;
|
||||
commonState[bar.axis] = 0;
|
||||
off(document, 'mousemove', mouseMoveDocumentHandler);
|
||||
document.onselectstart = null;
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
off(document, 'mouseup', mouseUpDocumentHandler);
|
||||
});
|
||||
return () => {
|
||||
const bar = unref(getBarRef);
|
||||
const { size, move } = props;
|
||||
return (
|
||||
<div
|
||||
class={['scrollbar__bar', 'is-' + bar.key]}
|
||||
onMousedown={clickTrackHandler}
|
||||
ref={elRef}
|
||||
>
|
||||
<div
|
||||
ref={thumbRef}
|
||||
class="scrollbar__thumb"
|
||||
onMousedown={clickThumbHandler}
|
||||
style={renderThumbStyle({ size, move, bar })}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
146
src/components/Scrollbar/src/Scrollbar.tsx
Normal file
146
src/components/Scrollbar/src/Scrollbar.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import { addResizeListener, removeResizeListener } from '/@/utils/event/resizeEvent';
|
||||
import scrollbarWidth from '/@/utils/scrollbarWidth';
|
||||
import { toObject } from './util';
|
||||
import Bar from './Bar';
|
||||
import { isString } from '/@/utils/is';
|
||||
import {
|
||||
defineComponent,
|
||||
PropType,
|
||||
unref,
|
||||
reactive,
|
||||
ref,
|
||||
provide,
|
||||
onMounted,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
} from 'vue';
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
||||
import { tryTsxEmit } from '/@/utils/helper/vueHelper';
|
||||
import './index.less';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Scrollbar',
|
||||
props: {
|
||||
native: Boolean as PropType<boolean>,
|
||||
wrapStyle: {
|
||||
type: Object as PropType<any>,
|
||||
},
|
||||
wrapClass: { type: String as PropType<string>, required: false },
|
||||
viewClass: { type: String as PropType<string> },
|
||||
viewStyle: { type: Object as PropType<any> },
|
||||
noresize: Boolean as PropType<boolean>,
|
||||
tag: {
|
||||
type: String as PropType<string>,
|
||||
default: 'div',
|
||||
},
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const resizeRef = ref<Nullable<HTMLDivElement>>(null);
|
||||
const wrapElRef = ref<Nullable<HTMLDivElement>>(null);
|
||||
provide('scroll-bar-wrap', wrapElRef);
|
||||
const state = reactive({
|
||||
sizeWidth: '0',
|
||||
sizeHeight: '0',
|
||||
moveX: 0,
|
||||
moveY: 0,
|
||||
});
|
||||
|
||||
function handleScroll() {
|
||||
const warpEl = unref(wrapElRef);
|
||||
if (!warpEl) return;
|
||||
const { scrollTop, scrollLeft, clientHeight, clientWidth } = warpEl;
|
||||
|
||||
state.moveY = (scrollTop * 100) / clientHeight;
|
||||
state.moveX = (scrollLeft * 100) / clientWidth;
|
||||
}
|
||||
function update() {
|
||||
const warpEl = unref(wrapElRef);
|
||||
if (!warpEl) return;
|
||||
const { scrollHeight, scrollWidth, clientHeight, clientWidth } = warpEl;
|
||||
const heightPercentage = (clientHeight * 100) / scrollHeight;
|
||||
const widthPercentage = (clientWidth * 100) / scrollWidth;
|
||||
|
||||
state.sizeHeight = heightPercentage < 100 ? heightPercentage + '%' : '';
|
||||
state.sizeWidth = widthPercentage < 100 ? widthPercentage + '%' : '';
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
tryTsxEmit((instance) => {
|
||||
instance.wrap = unref(wrapElRef);
|
||||
});
|
||||
|
||||
const { native, noresize } = props;
|
||||
const resizeEl = unref(resizeRef);
|
||||
const warpEl = unref(wrapElRef);
|
||||
if (native || !resizeEl || !warpEl) return;
|
||||
nextTick(update);
|
||||
if (!noresize) {
|
||||
addResizeListener(resizeEl, update);
|
||||
addResizeListener(warpEl, update);
|
||||
}
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
const { native, noresize } = props;
|
||||
const resizeEl = unref(resizeRef);
|
||||
const warpEl = unref(wrapElRef);
|
||||
if (native || !resizeEl || !warpEl) return;
|
||||
if (!noresize) {
|
||||
removeResizeListener(resizeEl, update);
|
||||
removeResizeListener(warpEl, update);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
const { native, tag, viewClass, viewStyle, wrapClass, wrapStyle } = props;
|
||||
let style: any = wrapStyle;
|
||||
const gutter = scrollbarWidth();
|
||||
|
||||
if (gutter) {
|
||||
const gutterWith = `-${gutter}px`;
|
||||
const gutterStyle = `margin-bottom: ${gutterWith}; margin-right: ${gutterWith};`;
|
||||
|
||||
if (Array.isArray(wrapStyle)) {
|
||||
style = toObject(wrapStyle);
|
||||
style.marginRight = style.marginBottom = gutterWith;
|
||||
} else if (isString(wrapStyle)) {
|
||||
style += gutterStyle;
|
||||
} else {
|
||||
style = gutterStyle;
|
||||
}
|
||||
}
|
||||
|
||||
const Tag = tag as any;
|
||||
const view = (
|
||||
<Tag class={['scrollbar__view', viewClass]} style={viewStyle} ref={resizeRef}>
|
||||
{getSlot(slots)}
|
||||
</Tag>
|
||||
);
|
||||
const wrap = (
|
||||
<div
|
||||
ref={wrapElRef}
|
||||
style={style}
|
||||
onScroll={handleScroll}
|
||||
class={[wrapClass, 'scrollbar__wrap', gutter ? '' : 'scrollbar__wrap--hidden-default']}
|
||||
>
|
||||
{[view]}
|
||||
</div>
|
||||
);
|
||||
let nodes: any[] = [];
|
||||
const { moveX, sizeWidth, moveY, sizeHeight } = state;
|
||||
if (!native) {
|
||||
nodes = [
|
||||
wrap,
|
||||
/* eslint-disable */
|
||||
<Bar move={moveX} size={sizeWidth}></Bar>,
|
||||
<Bar vertical move={moveY} size={sizeHeight}></Bar>,
|
||||
];
|
||||
} else {
|
||||
nodes = [
|
||||
<div ref="wrap" class={[wrapClass, 'scrollbar__wrap']} style={style}>
|
||||
{[view]}
|
||||
</div>,
|
||||
];
|
||||
}
|
||||
return <div class="scrollbar">{nodes}</div>;
|
||||
};
|
||||
},
|
||||
});
|
69
src/components/Scrollbar/src/index.less
Normal file
69
src/components/Scrollbar/src/index.less
Normal file
@@ -0,0 +1,69 @@
|
||||
.scrollbar {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&__wrap {
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
|
||||
&--hidden-default {
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__thumb {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
cursor: pointer;
|
||||
background-color: rgba(144, 147, 153, 0.3);
|
||||
border-radius: inherit;
|
||||
transition: 0.3s background-color;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(144, 147, 153, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
&__bar {
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
bottom: 2px;
|
||||
z-index: 1;
|
||||
border-radius: 4px;
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 120ms ease-out;
|
||||
transition: opacity 120ms ease-out;
|
||||
|
||||
&.is-vertical {
|
||||
top: 2px;
|
||||
width: 6px;
|
||||
|
||||
& > div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-horizontal {
|
||||
left: 2px;
|
||||
height: 6px;
|
||||
|
||||
& > div {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scrollbar:active > .scrollbar__bar,
|
||||
.scrollbar:focus > .scrollbar__bar,
|
||||
.scrollbar:hover > .scrollbar__bar {
|
||||
opacity: 1;
|
||||
transition: opacity 280ms ease-out;
|
||||
}
|
14
src/components/Scrollbar/src/types.d.ts
vendored
Normal file
14
src/components/Scrollbar/src/types.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface BarMapItem {
|
||||
offset: string;
|
||||
scroll: string;
|
||||
scrollSize: string;
|
||||
size: string;
|
||||
key: string;
|
||||
axis: string;
|
||||
client: string;
|
||||
direction: string;
|
||||
}
|
||||
export interface BarMap {
|
||||
vertical: BarMapItem;
|
||||
horizontal: BarMapItem;
|
||||
}
|
49
src/components/Scrollbar/src/util.ts
Normal file
49
src/components/Scrollbar/src/util.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { BarMap } from './types';
|
||||
export const BAR_MAP: BarMap = {
|
||||
vertical: {
|
||||
offset: 'offsetHeight',
|
||||
scroll: 'scrollTop',
|
||||
scrollSize: 'scrollHeight',
|
||||
size: 'height',
|
||||
key: 'vertical',
|
||||
axis: 'Y',
|
||||
client: 'clientY',
|
||||
direction: 'top',
|
||||
},
|
||||
horizontal: {
|
||||
offset: 'offsetWidth',
|
||||
scroll: 'scrollLeft',
|
||||
scrollSize: 'scrollWidth',
|
||||
size: 'width',
|
||||
key: 'horizontal',
|
||||
axis: 'X',
|
||||
client: 'clientX',
|
||||
direction: 'left',
|
||||
},
|
||||
};
|
||||
|
||||
export function renderThumbStyle({ move, size, bar }) {
|
||||
const style = {} as any;
|
||||
const translate = `translate${bar.axis}(${move}%)`;
|
||||
|
||||
style[bar.size] = size;
|
||||
style.transform = translate;
|
||||
style.msTransform = translate;
|
||||
style.webkitTransform = translate;
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
function extend<T, K>(to: T, _from: K): T & K {
|
||||
return Object.assign(to, _from);
|
||||
}
|
||||
|
||||
export function toObject<T>(arr: Array<T>): Record<string, T> {
|
||||
const res = {};
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i]) {
|
||||
extend(res, arr[i]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
Reference in New Issue
Block a user