vue3 实现上下滚动字段切换Tab

6 阅读1分钟
<div class="gameContainer" :style="{ '--bannerHeight': clientHeight + 'px' }">
        <div class="left-title" ref="leftTitleRef">
          <div class="title-box" v-for="(item, index) in casinoConfig" :class="activeId == index ? 'titleBox-active' : ''" :key="index" @click="scrollToContent(index)" :id="`${item.name}`">
            <img class="categoryIcon" alt="" :src="activeId != index ? item.ico : item.activeIco" error="@/assets/images/defaultIcon.webp" />
            <span class="categoryName">{{ item.name }}</span>
          </div>
        </div>
 <div class="left-game" ref="gameContainer" @scroll="onScroll">
          <div v-for="(data, idx) in menuData" :key="idx" class="content-item">
            <div class="tabPanel" :id="`${idx}`" :data-name="data.name"> <img :src="data.ico" alt="" />{{ data.name }}</div>
            <div>
            <div class="game-img" :class="item.maintenanceStatus ? 'gameIco-maintenance' : 'game-img'" @click="customGoToWhere(item)">
                  <img v-lazy="item.backgroundImage" error="@/assets/images/default-logo.webp" :data-attr="dataAttrComputed(data)" />
                </div>
                </div>
</div>
</div>



        </div>



const leftTitleRef = ref<HTMLElement | null>(null);
const gameContainer = ref<HTMLElement | null>(null);
let observer: IntersectionObserver | null = null;


const onScroll = () => {
  const now = Date.now();
  if (now < ignoreScrollEventsUntil.value) {
    return;
  }

  const gameContainerElement = gameContainer.value;
  const contentItems = Array.from(gameContainerElement.querySelectorAll('.tabPanel'));
  const containerRect = gameContainerElement.getBoundingClientRect();
  const containerHeight = containerRect.height;

  let closestTabPanel: Element | null = null;
  let closestDistance = Infinity;
  let furthestTabPanel: Element | null = null;
  let furthestDistance = -Infinity;

  contentItems.forEach(item => {
    const rect = item.getBoundingClientRect();
    const relativeTop = rect.top - containerRect.top;
    const relativeBottom = rect.bottom - containerRect.top;

    if (relativeTop >= 0 && relativeBottom <= containerHeight) {
      const distanceFromTop = relativeTop;
      if (distanceFromTop < closestDistance) {
        closestDistance = distanceFromTop;
        closestTabPanel = item;
      }
      if (relativeBottom > furthestDistance) {
        furthestDistance = relativeBottom;
        furthestTabPanel = item;
      }
    }
  });

  if (!closestTabPanel) {
    contentItems.forEach(item => {
      const rect = item.getBoundingClientRect();
      const distanceFromViewportTop = Math.abs(rect.top - containerRect.top);
      if (distanceFromViewportTop < closestDistance) {
        closestDistance = distanceFromViewportTop;
        closestTabPanel = item;
      }
    });
  }

  if (closestTabPanel) {
    setActiveItem(closestTabPanel);
  }

  if (gameContainerElement.scrollTop + containerHeight >= gameContainerElement.scrollHeight) {
    if (furthestTabPanel) {
      setActiveItem(furthestTabPanel);
    }
  }
};

const setActiveItem = item => {
  activeId.value = item.id;
  const target = document.getElementById(item.dataset.name);
  if (target) {
    scrollIntoView(target, {
      align: { top: 0.5 },
      behavior: 'smooth'
    });
  }
};

const scrollToContent = async index => {
  activeId.value = index;
  const target = document.getElementById(index);
  if (target) {
    await nextTick();
    ignoreScrollEventsUntil.value = Date.now() + 1000;
    target.scrollIntoView({ behavior: 'smooth' });
  }
};