選單元素溢位時,自動滾動到可視區域

热饭班长發表於2024-11-03

chrome-capture-2024-11-3.gif

方案1:計算何時的滾動位置

獲取當前點選元素的offsetLeft,然後將offsetLeft設定為容器的scrollLeft。
雖然這樣可以使當前點選元素處於可視區域,但是會導致無法點選前一個元素。
解決辦法是用offsetLeft減去容器寬度的一半,這樣可以讓當前點選元素展示在容器中間,問題就解決了。

<script setup lang="ts">
import { ref } from 'vue';

const containerRef = ref<HTMLElement | null>(null);
const items = ref([
  'item1',
  'item2',
  'item3',
  'item4',
  'item5',
  'item6',
  'item7',
  'item8',
  'item9',
  'item10',
]);

// 加上了target.offsetWidth / 2,是為了讓居中操作兼顧到當前點選元素自身的寬度
const getScrollPosition = (target: HTMLElement, container: HTMLElement) =>
  target.offsetLeft - container.offsetWidth / 2 + target.offsetWidth / 2;

const onClick = (event: MouseEvent) => {
  const container = containerRef.value;
  const currentTarget = event.currentTarget

  if (container && currentTarget instanceof HTMLElement) {
    container.scrollTo({
      left: getScrollPosition(currentTarget, container),
      behavior: "smooth"
    });
  }
};
</script>

<template>
  <nav ref="containerRef">
    <span
      v-for="(item, index) in items"
      :key="index"
      @click="onClick($event)"
    >
      {{ item }}
    </span>
  </nav>
</template>

<style lang="less" scoped>
nav {
  display: flex;
  flex-wrap: nowrap;
  overflow: auto;
}
</style>

注意:scrollTo的left有效值為0和scrollWidth之間,所以不用擔心給容器設定了非法的scrollLeft

方案2:藉助scrollIntoView方法

scrollIntoView可以將元素滾動到可視區域

<script setup lang="ts">
import { ref } from 'vue';

const items = ref([
  'item1',
  'item2',
  'item3',
  'item4',
  'item5',
  'item6',
  'item7',
  'item8',
  'item9',
  'item10',
]);

const onClick = (event: MouseEvent) => {
  const currentTarget = event.currentTarget;

  if (currentTarget instanceof HTMLElement) {
    currentTarget.scrollIntoView({
      behavior: "smooth",
      block: "nearest",
      inline: "center"
    });
  }
};
</script>

<template>
  <nav>
    <span
      v-for="(item, index) in items"
      :key="index"
      @click="onClick($event)"
    >
      {{ item }}
    </span>
  </nav>
</template>

<style lang="less" scoped>
nav {
  display: flex;
  flex-wrap: nowrap;
  overflow: auto;
}
</style>

改進為宣告式:

<script setup lang="ts">
import { ref, watch } from 'vue';

const items = ref([
  'item1',
  'item2',
  'item3',
  'item4',
  'item5',
  'item6',
  'item7',
  'item8',
  'item9',
  'item10',
]);

const activeIndex = ref(0);
const itemRefs = ref<HTMLElement[]>([]);

watch(activeIndex, (value: number) => {
  const targetItem = itemRefs.value[value]
  if (targetItem) {
    targetItem.scrollIntoView({
      behavior: "smooth",
      block: "nearest",
      inline: "center"
    })
  }
})
</script>

<template>
  <nav>
    <span
      v-for="(item, index) in items"
      :key="index"
      ref="itemRefs"
      @click="activeIndex = index"
    >
      {{ item }}
    </span>
  </nav>
</template>

<style>
nav {
  display: flex;
  flex-wrap: nowrap;
  overflow: auto;
}
</style>

相關文章