initial commit

This commit is contained in:
陈文彬
2020-09-28 20:19:10 +08:00
commit 2f6253cfb6
436 changed files with 26843 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
/**
* copy from element-ui
*/
export { default as Scrollbar } from './src/Scrollbar';

View 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>
);
};
},
});

View 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>;
};
},
});

View 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
View 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;
}

View 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;
}