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>

热饭班长
3.7k 声望434 粉丝

先去做,做出一坨狗屎,再改进。