<template>
|
<div class="scrollbar">
|
<div
|
ref="wrap"
|
:class="[wrapClass, 'scrollbar__wrap', native ? '' : 'scrollbar__wrap--hidden-default']"
|
:style="wrapStyle"
|
@scroll="handleScroll"
|
>
|
<component :is="tag" ref="resize" :class="['scrollbar__view', viewClass]" :style="viewStyle">
|
<slot></slot>
|
</component>
|
</div>
|
<template v-if="!native">
|
<bar :move="moveX" :size="sizeWidth" />
|
<bar vertical :move="moveY" :size="sizeHeight" />
|
</template>
|
</div>
|
</template>
|
<script lang="ts" setup>
|
import {
|
ref,
|
onMounted,
|
onBeforeUnmount,
|
nextTick,
|
provide,
|
unref,
|
watch,
|
type PropType,
|
} from 'vue';
|
import type { StyleValue } from '@/utils/types';
|
import { addResizeListener, removeResizeListener } from '@/utils/event';
|
import componentSetting from '@/settings/componentSetting';
|
import Bar from './bar';
|
|
defineOptions({ name: 'Scrollbar' });
|
|
const props = defineProps({
|
native: {
|
type: Boolean,
|
default: componentSetting.scrollbar?.native ?? false,
|
},
|
wrapStyle: {
|
type: [String, Array, Object] as PropType<StyleValue>,
|
default: '',
|
},
|
wrapClass: {
|
type: [String, Array],
|
default: '',
|
},
|
viewClass: {
|
type: [String, Array],
|
default: '',
|
},
|
viewStyle: {
|
type: [String, Array],
|
default: '',
|
},
|
noresize: Boolean, // 如果 container 尺寸不会发生变化,最好设置它可以优化性能
|
tag: {
|
type: String,
|
default: 'div',
|
},
|
scrollHeight: {
|
// 用于监控内部scrollHeight的变化
|
type: Number,
|
default: 0,
|
},
|
});
|
|
const sizeWidth = ref('0');
|
const sizeHeight = ref('0');
|
const moveX = ref(0);
|
const moveY = ref(0);
|
const wrap = ref();
|
const resize = ref();
|
|
provide('scroll-bar-wrap', wrap);
|
|
const handleScroll = () => {
|
if (!props.native) {
|
moveY.value = (unref(wrap).scrollTop * 100) / unref(wrap).clientHeight;
|
moveX.value = (unref(wrap).scrollLeft * 100) / unref(wrap).clientWidth;
|
}
|
};
|
|
const update = () => {
|
if (!unref(wrap)) return;
|
|
const heightPercentage = (unref(wrap).clientHeight * 100) / unref(wrap).scrollHeight;
|
const widthPercentage = (unref(wrap).clientWidth * 100) / unref(wrap).scrollWidth;
|
|
sizeHeight.value = heightPercentage < 100 ? heightPercentage + '%' : '';
|
sizeWidth.value = widthPercentage < 100 ? widthPercentage + '%' : '';
|
};
|
|
watch(
|
() => props.scrollHeight,
|
() => {
|
if (props.native) return;
|
update();
|
},
|
);
|
|
defineExpose({
|
wrap,
|
});
|
|
onMounted(() => {
|
if (props.native) return;
|
nextTick(update);
|
if (!props.noresize) {
|
addResizeListener(unref(resize), update);
|
addResizeListener(unref(wrap), update);
|
addEventListener('resize', update);
|
}
|
});
|
|
onBeforeUnmount(() => {
|
if (props.native) return;
|
if (!props.noresize) {
|
removeResizeListener(unref(resize), update);
|
removeResizeListener(unref(wrap), update);
|
removeEventListener('resize', update);
|
}
|
});
|
</script>
|
<style lang="less">
|
.scrollbar {
|
position: relative;
|
height: 100%;
|
overflow: hidden;
|
|
&__wrap {
|
height: 100%;
|
overflow: auto;
|
|
&--hidden-default {
|
scrollbar-width: none;
|
|
&::-webkit-scrollbar {
|
display: none;
|
width: 0;
|
height: 0;
|
opacity: 0;
|
}
|
}
|
}
|
|
&__thumb {
|
display: block;
|
position: relative;
|
width: 0;
|
height: 0;
|
transition: 0.3s background-color;
|
border-radius: inherit;
|
background-color: rgb(144 147 153 / 30%);
|
cursor: pointer;
|
|
&:hover {
|
background-color: rgb(144 147 153 / 50%);
|
}
|
}
|
|
&__bar {
|
position: absolute;
|
z-index: 1;
|
right: 2px;
|
bottom: 2px;
|
transition: opacity 80ms ease;
|
border-radius: 4px;
|
opacity: 0;
|
|
&.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 {
|
transition: opacity 340ms ease-out;
|
opacity: 1;
|
}
|
</style>
|