mirror of
https://github.com/gkd-kit/docs.git
synced 2024-12-27 18:05:35 +08:00
feat: BodyScrollbar
This commit is contained in:
parent
b125088e27
commit
a899d73227
184
docs/.vitepress/components/BodyScrollbar.vue
Normal file
184
docs/.vitepress/components/BodyScrollbar.vue
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
useElementBounding,
|
||||||
|
useEventListener,
|
||||||
|
useWindowScroll,
|
||||||
|
useWindowSize,
|
||||||
|
useStyleTag,
|
||||||
|
} from '@vueuse/core';
|
||||||
|
import { computed, onMounted, shallowRef, type CSSProperties } from 'vue';
|
||||||
|
|
||||||
|
useStyleTag(
|
||||||
|
`
|
||||||
|
body::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
`.trim(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { y, x } = useWindowScroll();
|
||||||
|
const { height: winH, width: winW } = useWindowSize();
|
||||||
|
const bodyRef = shallowRef<HTMLElement>(); // support ssr
|
||||||
|
onMounted(() => {
|
||||||
|
bodyRef.value = document.body;
|
||||||
|
});
|
||||||
|
const body = useElementBounding(bodyRef);
|
||||||
|
|
||||||
|
const yShow = computed(() => body.height.value > winH.value);
|
||||||
|
const yHeight = computed(() => {
|
||||||
|
const clientHeight = body.height.value;
|
||||||
|
const bodyHeight = clientHeight;
|
||||||
|
return (winH.value / bodyHeight) * winH.value;
|
||||||
|
});
|
||||||
|
const translateY = computed(() => {
|
||||||
|
const clientHeight = body.height.value;
|
||||||
|
const height = yHeight.value;
|
||||||
|
return (y.value / (clientHeight - winH.value)) * (winH.value - height);
|
||||||
|
});
|
||||||
|
const yStyle = computed<CSSProperties>(() => {
|
||||||
|
if (!yShow.value) return {};
|
||||||
|
return {
|
||||||
|
transform: `translateY(${translateY.value}px)`,
|
||||||
|
height: `${yHeight.value}px`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const clickBoxY = async (e: MouseEvent) => {
|
||||||
|
const deltaY =
|
||||||
|
yHeight.value *
|
||||||
|
0.9 *
|
||||||
|
(e.clientY < yHeight.value + translateY.value ? -1 : 1);
|
||||||
|
const clientHeight = body.height.value;
|
||||||
|
const bodyHeight = clientHeight;
|
||||||
|
const height = (winH.value / bodyHeight) * winH.value;
|
||||||
|
y.value += (deltaY / (winH.value - height)) * (clientHeight - winH.value);
|
||||||
|
};
|
||||||
|
const yDragging = shallowRef(false);
|
||||||
|
let lastYEvent: MouseEvent | undefined = undefined;
|
||||||
|
const pointerdownY = (e: MouseEvent) => {
|
||||||
|
lastYEvent = e;
|
||||||
|
yDragging.value = true;
|
||||||
|
};
|
||||||
|
useEventListener('pointermove', (e) => {
|
||||||
|
if (!lastYEvent) return;
|
||||||
|
const deltaY = e.clientY - lastYEvent.clientY;
|
||||||
|
lastYEvent = e;
|
||||||
|
const clientHeight = body.height.value;
|
||||||
|
const bodyHeight = clientHeight;
|
||||||
|
const height = (winH.value / bodyHeight) * winH.value;
|
||||||
|
y.value += (deltaY / (winH.value - height)) * (clientHeight - winH.value);
|
||||||
|
});
|
||||||
|
useEventListener('pointerup', () => {
|
||||||
|
lastYEvent = undefined;
|
||||||
|
yDragging.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const xShow = computed(() => body.width.value > winW.value);
|
||||||
|
const xWidth = computed(() => {
|
||||||
|
const clientWidth = body.width.value;
|
||||||
|
const bodyWidth = clientWidth;
|
||||||
|
return (winW.value / bodyWidth) * winW.value;
|
||||||
|
});
|
||||||
|
const translateX = computed(() => {
|
||||||
|
const clientWidth = body.width.value;
|
||||||
|
const width = xWidth.value;
|
||||||
|
return (x.value / (clientWidth - winW.value)) * (winW.value - width);
|
||||||
|
});
|
||||||
|
const xStyle = computed<CSSProperties>(() => {
|
||||||
|
if (!xShow.value) return {};
|
||||||
|
return {
|
||||||
|
transform: `translateX(${translateX.value}px)`,
|
||||||
|
width: `${xWidth.value}px`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const clickBoxX = (e: MouseEvent) => {
|
||||||
|
const deltaX =
|
||||||
|
xWidth.value * 0.9 * (e.clientX < xWidth.value + translateX.value ? -1 : 1);
|
||||||
|
const clientWidth = body.width.value;
|
||||||
|
const bodyWidth = clientWidth;
|
||||||
|
const width = (winW.value / bodyWidth) * winW.value;
|
||||||
|
const newX =
|
||||||
|
x.value + (deltaX / (winW.value - width)) * (clientWidth - winW.value);
|
||||||
|
x.value = newX;
|
||||||
|
};
|
||||||
|
const xDragging = shallowRef(false);
|
||||||
|
let lastXEvent: MouseEvent | undefined = undefined;
|
||||||
|
const pointerdownX = (e: MouseEvent) => {
|
||||||
|
lastXEvent = e;
|
||||||
|
xDragging.value = true;
|
||||||
|
};
|
||||||
|
useEventListener('pointermove', (e) => {
|
||||||
|
if (!lastXEvent) return;
|
||||||
|
const deltaX = e.clientX - lastXEvent.clientX;
|
||||||
|
lastXEvent = e;
|
||||||
|
const clientWidth = body.width.value;
|
||||||
|
const bodyWidth = clientWidth;
|
||||||
|
const width = (winW.value / bodyWidth) * winW.value;
|
||||||
|
x.value += (deltaX / (winW.value - width)) * (clientWidth - winW.value);
|
||||||
|
});
|
||||||
|
useEventListener('pointerup', () => {
|
||||||
|
lastXEvent = undefined;
|
||||||
|
xDragging.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
useEventListener('selectstart', (e) => {
|
||||||
|
if (lastXEvent || lastYEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div fixed class="BodyScrollbar">
|
||||||
|
<div
|
||||||
|
v-show="yShow"
|
||||||
|
scrollbar-y
|
||||||
|
fixed
|
||||||
|
right-2px
|
||||||
|
top-0
|
||||||
|
bottom-0
|
||||||
|
z-100
|
||||||
|
w-8px
|
||||||
|
@pointerdown="pointerdownY"
|
||||||
|
@click="clickBoxY"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
@click.stop
|
||||||
|
w-full
|
||||||
|
bg="#909399"
|
||||||
|
opacity-30
|
||||||
|
hover:opacity="50"
|
||||||
|
:style="yStyle"
|
||||||
|
:class="{
|
||||||
|
'opacity-50': yDragging,
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="xShow"
|
||||||
|
scrollbar-x
|
||||||
|
fixed
|
||||||
|
bottom-2px
|
||||||
|
left-0
|
||||||
|
right-0
|
||||||
|
z-100
|
||||||
|
h-8px
|
||||||
|
@pointerdown="pointerdownX"
|
||||||
|
@click="clickBoxX"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
@click.stop
|
||||||
|
h-full
|
||||||
|
bg="#909399"
|
||||||
|
opacity-30
|
||||||
|
hover:opacity="50"
|
||||||
|
:style="xStyle"
|
||||||
|
:class="{
|
||||||
|
'opacity-50': xDragging,
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -2,7 +2,16 @@ import 'uno.css';
|
||||||
import type { Theme } from 'vitepress';
|
import type { Theme } from 'vitepress';
|
||||||
import DefaultTheme from 'vitepress/theme';
|
import DefaultTheme from 'vitepress/theme';
|
||||||
import components from '../components';
|
import components from '../components';
|
||||||
|
import BodyScrollbar from '../components/BodyScrollbar.vue';
|
||||||
import './custom.css';
|
import './custom.css';
|
||||||
|
import {
|
||||||
|
Fragment,
|
||||||
|
h,
|
||||||
|
Teleport,
|
||||||
|
defineComponent,
|
||||||
|
shallowRef,
|
||||||
|
onMounted,
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
// 兼容旧链接/短链重定向
|
// 兼容旧链接/短链重定向
|
||||||
if (!import.meta.env.SSR) {
|
if (!import.meta.env.SSR) {
|
||||||
|
@ -31,8 +40,24 @@ if (!import.meta.env.SSR) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ScrollbarWrapper = defineComponent(() => {
|
||||||
|
const show = shallowRef(false);
|
||||||
|
onMounted(() => {
|
||||||
|
const isMobile = 'ontouchstart' in document.documentElement;
|
||||||
|
show.value = !isMobile;
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
return show.value
|
||||||
|
? h(Teleport, { to: document.body }, h(BodyScrollbar))
|
||||||
|
: undefined;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
extends: DefaultTheme,
|
extends: DefaultTheme,
|
||||||
|
Layout() {
|
||||||
|
return h(Fragment, null, [h(DefaultTheme.Layout), h(ScrollbarWrapper)]);
|
||||||
|
},
|
||||||
enhanceApp({ app }) {
|
enhanceApp({ app }) {
|
||||||
Object.entries(components).forEach(([name, component]) => {
|
Object.entries(components).forEach(([name, component]) => {
|
||||||
app.component(name, component);
|
app.component(name, component);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user