123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- import type { TabsProps } from './types';
- import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
- import { VbenScrollbar } from '@vben-core/shadcn-ui';
- import { useDebounceFn } from '@vueuse/core';
- type DomElement = Element | null | undefined;
- export function useTabsViewScroll(props: TabsProps) {
- let resizeObserver: null | ResizeObserver = null;
- let mutationObserver: MutationObserver | null = null;
- let tabItemCount = 0;
- const scrollbarRef = ref<InstanceType<typeof VbenScrollbar> | null>(null);
- const scrollViewportEl = ref<DomElement>(null);
- const showScrollButton = ref(false);
- const scrollIsAtLeft = ref(true);
- const scrollIsAtRight = ref(false);
- function getScrollClientWidth() {
- const scrollbarEl = scrollbarRef.value?.$el;
- if (!scrollbarEl || !scrollViewportEl.value) return {};
- const scrollbarWidth = scrollbarEl.clientWidth;
- const scrollViewWidth = scrollViewportEl.value.clientWidth;
- return {
- scrollbarWidth,
- scrollViewWidth,
- };
- }
- function scrollDirection(
- direction: 'left' | 'right',
- distance: number = 150,
- ) {
- const { scrollbarWidth, scrollViewWidth } = getScrollClientWidth();
- if (!scrollbarWidth || !scrollViewWidth) return;
- if (scrollbarWidth > scrollViewWidth) return;
- scrollViewportEl.value?.scrollBy({
- behavior: 'smooth',
- left:
- direction === 'left'
- ? -(scrollbarWidth - distance)
- : +(scrollbarWidth - distance),
- });
- }
- async function initScrollbar() {
- await nextTick();
- const scrollbarEl = scrollbarRef.value?.$el;
- if (!scrollbarEl) {
- return;
- }
- const viewportEl = scrollbarEl?.querySelector(
- 'div[data-radix-scroll-area-viewport]',
- );
- scrollViewportEl.value = viewportEl;
- calcShowScrollbarButton();
- await nextTick();
- scrollToActiveIntoView();
- // 监听大小变化
- resizeObserver?.disconnect();
- resizeObserver = new ResizeObserver(
- useDebounceFn((_entries: ResizeObserverEntry[]) => {
- calcShowScrollbarButton();
- scrollToActiveIntoView();
- }, 100),
- );
- resizeObserver.observe(viewportEl);
- tabItemCount = props.tabs?.length || 0;
- mutationObserver?.disconnect();
- // 使用 MutationObserver 仅监听子节点数量变化
- mutationObserver = new MutationObserver(() => {
- const count = viewportEl.querySelectorAll(
- `div[data-tab-item="true"]`,
- ).length;
- if (count > tabItemCount) {
- scrollToActiveIntoView();
- }
- if (count !== tabItemCount) {
- calcShowScrollbarButton();
- tabItemCount = count;
- }
- });
- // 配置为仅监听子节点的添加和移除
- mutationObserver.observe(viewportEl, {
- attributes: false,
- childList: true,
- subtree: true,
- });
- }
- async function scrollToActiveIntoView() {
- if (!scrollViewportEl.value) {
- return;
- }
- await nextTick();
- const viewportEl = scrollViewportEl.value;
- const { scrollbarWidth } = getScrollClientWidth();
- const { scrollWidth } = viewportEl;
- if (scrollbarWidth >= scrollWidth) {
- return;
- }
- requestAnimationFrame(() => {
- const activeItem = viewportEl?.querySelector('.is-active');
- activeItem?.scrollIntoView({ behavior: 'smooth', inline: 'start' });
- });
- }
- /**
- * 计算tabs 宽度,用于判断是否显示左右滚动按钮
- */
- async function calcShowScrollbarButton() {
- if (!scrollViewportEl.value) {
- return;
- }
- const { scrollbarWidth } = getScrollClientWidth();
- showScrollButton.value =
- scrollViewportEl.value.scrollWidth > scrollbarWidth;
- }
- const handleScrollAt = useDebounceFn(({ left, right }) => {
- scrollIsAtLeft.value = left;
- scrollIsAtRight.value = right;
- }, 100);
- watch(
- () => props.active,
- async () => {
- // 200为了等待 tab 切换动画完成
- // setTimeout(() => {
- scrollToActiveIntoView();
- // }, 300);
- },
- {
- flush: 'post',
- },
- );
- // watch(
- // () => props.tabs?.length,
- // async () => {
- // await nextTick();
- // calcShowScrollbarButton();
- // },
- // {
- // flush: 'post',
- // },
- // );
- watch(
- () => props.styleType,
- () => {
- initScrollbar();
- },
- );
- onMounted(initScrollbar);
- onUnmounted(() => {
- resizeObserver?.disconnect();
- mutationObserver?.disconnect();
- resizeObserver = null;
- mutationObserver = null;
- });
- return {
- handleScrollAt,
- initScrollbar,
- scrollbarRef,
- scrollDirection,
- scrollIsAtLeft,
- scrollIsAtRight,
- showScrollButton,
- };
- }
|