vue3 element-plus实现长滚动,一个一个内容块 和tab联动


简单实现

在 Vue 3 中使用 Element Plus 来实现长滚动页面,并且让每个内容块与 Tab 组件联动,可以通过以下步骤实现:

  1. 布局设计

    • 使用 ElTabs 作为导航栏,每个 ElTabPane 对应一个内容块。
    • 使用 IntersectionObserver 或者基于滚动事件检测滚动位置,并同步到 Tab 组件。
  2. 使用 ElTabs 组件ElTabs 组件用于显示不同的内容块,并且可以通过监听选项卡的切换和手动滚动触发 tab 切换。

  3. 使用 IntersectionObserver 检测滚动: 可以使用 IntersectionObserver 来检测当前页面的哪个内容块在视图区域内,并同步到 ElTabs

代码示例

HTML 全选
<template>
  <div>
    <!-- Tab导航 -->
    <el-tabs v-model="activeTab" @tab-click="handleTabClick">
      <el-tab-pane v-for="(item, index) in tabs" :key="index" :label="item.label" :name="item.name"></el-tab-pane>
    </el-tabs>

    <!-- 滚动区域 -->
    <div ref="scrollContainer" class="scroll-container" @scroll="onScroll">
      <div v-for="(item, index) in tabs" :key="index" :ref="`block-${index}`" class="content-block">
        <h2>{{ item.label }}</h2>
        <p>{{ item.content }}</p>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';

const activeTab = ref('tab-1'); // 默认激活第一个Tab
const tabs = ref([
  { label: 'Tab 1', name: 'tab-1', content: '内容 1' },
  { label: 'Tab 2', name: 'tab-2', content: '内容 2' },
  { label: 'Tab 3', name: 'tab-3', content: '内容 3' },
  { label: 'Tab 4', name: 'tab-4', content: '内容 4' },
]);

// 监听Tab点击事件,滚动到对应内容块
const handleTabClick = (tab) => {
  const targetBlock = document.querySelector(`[ref="block-${tabs.value.findIndex(t => t.name === tab.paneName)}"]`);
  if (targetBlock) {
    targetBlock.scrollIntoView({ behavior: 'smooth' });
  }
};

// 监听滚动事件,更新Tab激活状态
const onScroll = () => {
  const scrollContainer = document.querySelector('.scroll-container');
  const blocks = tabs.value.map((_, index) => document.querySelector(`[ref="block-${index}"]`));

  blocks.forEach((block, index) => {
    const rect = block.getBoundingClientRect();
    if (rect.top >= 0 && rect.top < scrollContainer.offsetHeight / 2) {
      activeTab.value = tabs.value[index].name;
    }
  });
};

</script>

<style>
.scroll-container {
  height: 400px;
  overflow-y: auto;
}

.content-block {
  padding: 20px;
  height: 400px;
  border: 1px solid #dcdcdc;
  margin-bottom: 10px;
}
</style>

关键点解释:

  1. ElTabs

    • 使用 v-model 绑定 activeTab 来控制当前激活的标签页。
    • 监听 tab-click 事件,通过点击 Tab 切换内容块。
  2. 滚动联动

    • 使用 scroll 事件监听滚动条的位置,在滚动时计算当前可视区域的内容块并同步 Tab 激活状态。
    • 通过 scrollIntoView 实现点击 Tab 时的平滑滚动效果。
  3. 样式

    • 设置 .scroll-container 高度和 overflow-y: auto,保证内容可以滚动。

这样你就可以实现一个内容块与 Tab 组件联动的滚动效果。

 

封装为一个通用组件

可以将这个滚动联动的功能封装为一个通用的组件,这样就可以在不同的页面和项目中复用。我们可以将 ElTabs 与内容区域的联动功能封装为一个组件 ScrollableTabs,然后通过 props 来传递数据和选项。

封装通用组件 ScrollableTabs

HTML 全选
<template>
  <div class="scrollable-tabs">
    <!-- Tab导航 -->
    <el-tabs v-model="activeTab" @tab-click="handleTabClick">
      <el-tab-pane
        v-for="(item, index) in tabs"
        :key="index"
        :label="item.label"
        :name="item.name"
      ></el-tab-pane>
    </el-tabs>

    <!-- 滚动区域 -->
    <div ref="scrollContainer" class="scroll-container" @scroll="onScroll">
      <div
        v-for="(item, index) in tabs"
        :key="index"
        :ref="`block-${index}`"
        class="content-block"
      >
        <!-- 插槽内容,支持自定义 -->
        <slot :name="item.name" :content="item.content"></slot>
      </div>
    </div>
  </div>
</template>

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

// 定义 props
const props = defineProps<{
  tabs: { label: string; name: string; content: string }[];
  initialTab?: string; // 默认激活的Tab
}>();

// 当前激活的Tab
const activeTab = ref(props.initialTab || props.tabs[0].name);

// 监听 activeTab 的变化,并滚动到对应内容块
const handleTabClick = (tab: any) => {
  const index = props.tabs.findIndex((t) => t.name === tab.paneName);
  scrollToBlock(index);
};

// 滚动到指定内容块
const scrollToBlock = (index: number) => {
  const targetBlock = document.querySelector(`[ref="block-${index}"]`);
  if (targetBlock) {
    targetBlock.scrollIntoView({ behavior: 'smooth' });
  }
};

// 监听滚动事件,更新Tab激活状态
const onScroll = () => {
  const scrollContainer = document.querySelector('.scroll-container');
  const blocks = props.tabs.map((_, index) =>
    document.querySelector(`[ref="block-${index}"]`)
  );

  blocks.forEach((block, index) => {
    const rect = block.getBoundingClientRect();
    if (rect.top >= 0 && rect.top < (scrollContainer as HTMLElement).offsetHeight / 2) {
      activeTab.value = props.tabs[index].name;
    }
  });
};

// 监听传入的initialTab变化,滚动到对应内容块
watch(() => props.initialTab, (newVal) => {
  if (newVal) {
    const index = props.tabs.findIndex((tab) => tab.name === newVal);
    scrollToBlock(index);
  }
});

onMounted(() => {
  // 初次加载滚动到初始 Tab
  const index = props.tabs.findIndex((tab) => tab.name === activeTab.value);
  scrollToBlock(index);
});
</script>

<style scoped>
.scrollable-tabs {
  width: 100%;
}

.scroll-container {
  height: 400px;
  overflow-y: auto;
  border: 1px solid #ebeef5;
  margin-top: 20px;
}

.content-block {
  padding: 20px;
  height: 400px;
  border: 1px solid #dcdcdc;
  margin-bottom: 10px;
}
</style>

使用 ScrollableTabs 组件

在父组件中,我们可以直接使用封装好的 ScrollableTabs 组件,并通过 props 来传递 tabs 数据和 initialTab

HTML 全选
<template>
  <div>
    <ScrollableTabs
      :tabs="tabData"
      initialTab="tab-2"
    >
      <!-- 内容块的自定义插槽 -->
      <template #tab-1>
        <h2>自定义内容 1</h2>
        <p>这是内容块 1 的自定义内容。</p>
      </template>
      <template #tab-2>
        <h2>自定义内容 2</h2>
        <p>这是内容块 2 的自定义内容。</p>
      </template>
      <template #tab-3>
        <h2>自定义内容 3</h2>
        <p>这是内容块 3 的自定义内容。</p>
      </template>
      <template #tab-4>
        <h2>自定义内容 4</h2>
        <p>这是内容块 4 的自定义内容。</p>
      </template>
    </ScrollableTabs>
  </div>
</template>

<script setup lang="ts">
import ScrollableTabs from './components/ScrollableTabs.vue';

const tabData = [
  { label: 'Tab 1', name: 'tab-1', content: '内容 1' },
  { label: 'Tab 2', name: 'tab-2', content: '内容 2' },
  { label: 'Tab 3', name: 'tab-3', content: '内容 3' },
  { label: 'Tab 4', name: 'tab-4', content: '内容 4' },
];
</script>

组件设计思路

  1. Props 设计

    • tabs: 内容块的数据数组,每个项包含 label, namecontent
    • initialTab: 默认激活的 Tab 名称。
  2. 插槽设计

    • 允许父组件通过具名插槽的方式传入自定义内容,使得每个内容块可以灵活配置。
  3. 联动功能

    • 滚动和 Tab 的联动通过监听 scroll 事件来动态更新 activeTab,并通过点击 Tab 实现滚动。

这种设计可以满足各种场景的需求,包括内容块内容的自定义和 Tab 切换的同步联动。

 

动态插槽内容实现

template 也可以是动态的。可以通过数据驱动的方式动态生成插槽内容,而不是手动定义每个插槽。可以在父组件中传递一个内容对象数组,并在 ScrollableTabs 组件中使用 v-for 动态渲染内容。

动态插槽内容实现

我们可以在 ScrollableTabs 组件中通过 v-for 渲染内容块,并通过 slot 插槽属性传递内容数据。

1. 更新 ScrollableTabs 组件

HTML 全选
<template>
  <div class="scrollable-tabs">
    <!-- Tab导航 -->
    <el-tabs v-model="activeTab" @tab-click="handleTabClick">
      <el-tab-pane
        v-for="(tab, index) in tabs"
        :key="index"
        :label="tab.label"
        :name="tab.name"
      ></el-tab-pane>
    </el-tabs>

    <!-- 滚动区域 -->
    <div ref="scrollContainer" class="scroll-container" @scroll="onScroll">
      <div
        v-for="(tab, index) in tabs"
        :key="index"
        :ref="`block-${index}`"
        class="content-block"
      >
        <!-- 使用插槽显示内容,动态插槽名 -->
        <slot :name="tab.name" :data="tab.content"></slot>
      </div>
    </div>
  </div>
</template>

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

// 定义 props
const props = defineProps<{
  tabs: { label: string; name: string; content: any }[];
  initialTab?: string; // 默认激活的Tab
}>();

const activeTab = ref(props.initialTab || props.tabs[0].name);

// 监听 Tab 点击事件
const handleTabClick = (tab: any) => {
  const index = props.tabs.findIndex((t) => t.name === tab.paneName);
  scrollToBlock(index);
};

// 滚动到指定内容块
const scrollToBlock = (index: number) => {
  const targetBlock = document.querySelector(`[ref="block-${index}"]`);
  if (targetBlock) {
    targetBlock.scrollIntoView({ behavior: 'smooth' });
  }
};

// 监听滚动事件,更新 Tab 激活状态
const onScroll = () => {
  const scrollContainer = document.querySelector('.scroll-container');
  const blocks = props.tabs.map((_, index) =>
    document.querySelector(`[ref="block-${index}"]`)
  );

  blocks.forEach((block, index) => {
    const rect = block.getBoundingClientRect();
    if (rect.top >= 0 && rect.top < (scrollContainer as HTMLElement).offsetHeight / 2) {
      activeTab.value = props.tabs[index].name;
    }
  });
};

// 监听 initialTab 的变化
watch(() => props.initialTab, (newVal) => {
  if (newVal) {
    const index = props.tabs.findIndex((tab) => tab.name === newVal);
    scrollToBlock(index);
  }
});

onMounted(() => {
  // 初次加载滚动到初始 Tab
  const index = props.tabs.findIndex((tab) => tab.name === activeTab.value);
  scrollToBlock(index);
});
</script>

<style scoped>
.scrollable-tabs {
  width: 100%;
}

.scroll-container {
  height: 400px;
  overflow-y: auto;
  border: 1px solid #ebeef5;
  margin-top: 20px;
}

.content-block {
  padding: 20px;
  height: 400px;
  border: 1px solid #dcdcdc;
  margin-bottom: 10px;
}
</style>

2. 父组件使用示例

在父组件中,我们可以将内容对象数组传递给 ScrollableTabs 组件,然后通过默认插槽中的逻辑渲染内容

HTML 全选
<template>
  <div>
    <ScrollableTabs :tabs="tabData" initialTab="tab-2">
      <template v-for="tab in tabData" :slot="tab.name" :key="tab.name" v-slot="{ data }">
        <h2>{{ tab.label }}</h2>
        <!-- 动态内容渲染 -->
        <p>{{ data }}</p>
      </template>
    </ScrollableTabs>
  </div>
</template>

<script setup lang="ts">
import ScrollableTabs from './components/ScrollableTabs.vue';

const tabData = [
  { label: 'Tab 1', name: 'tab-1', content: '这是内容块 1 的内容,可以是任何数据类型。' },
  { label: 'Tab 2', name: 'tab-2', content: '这是内容块 2 的内容,可以是任何数据类型。' },
  { label: 'Tab 3', name: 'tab-3', content: '这是内容块 3 的内容,可以是任何数据类型。' },
  { label: 'Tab 4', name: 'tab-4', content: '这是内容块 4 的内容,可以是任何数据类型。' },
];
</script>

说明

  1. 动态插槽渲染

    • 在父组件中使用 v-for 遍历 tabData,通过 :slot 属性动态指定插槽名,并通过 v-slot 获取传递的 data 内容。
  2. 插槽内容传递

    • ScrollableTabs 组件内部使用 <slot :name="tab.name" :data="tab.content"></slot> 的方式,将每个 tabcontent 作为 slot 的数据传递。
  3. 内容数据灵活性

    • 通过 props 传递的 tabs 数据集,可以包含任意类型的数据(如字符串、对象、HTML),这样父组件可以根据需要动态渲染内容,而不需要定义多个固定模板。

这种方法可以让 ScrollableTabs 组件的插槽内容完全动态化,且灵活地通过数据控制内容显示和渲染。

 

 

 

 

 

 

 

 

 

 

版权声明:本文为YES开发框架网发布内容,转载请附上原文出处连接
张国生
上一篇:使用队列(ConcurrentQueue)时获取队列运行报错
下一篇:NPOI生成Excel文件时设置一整列为文本类型
评论列表

发表评论

评论内容
昵称:
关联文章

vue3 element-plus实现滚动一个一个内容 tab联动
element-ui组件el-tabs控制内容滚动
vue3 ts setup 封装element-plus el-dialog,并使用v-model
vue3重新封装element-plus组件库的按钮组件el-button
VUE3监听div滚动滚动超出后折叠collapse组件
深入理解 Vue 3 中的 ::v-deep:让 Scoped 样式无处不达
Vue 3项目中使用TypeScriptPinia进行持久化状态管理初始化操作
[WPF] 抄一个 CSS3 实现的按钮
vue3+vist 打包空白
如何在Vue 3Vite项目中禁用代码压缩打包
使用Hot Chocolate.NET 6构建GraphQL应用(3) —— 实现Query基础功能
CodeMirror 格式化内容内容选择
vue vue-clie多环境配置
使用Vue3轻松集成Lottie-web动画:从入门到实践
用 WinUI 3 开发了一个摸鱼应用
Vue 3 中,嵌套数据源且需要过滤内部数据源
.NET Core MVC 实现时间任务的进度显示
vue3 keep-alive跳转回来后 scroll位置丢失
YES-CMS内容管理系统 代码高亮配置
vue3 组合模式 组件自己引用自己 递归组件 组件命名

联系我们
联系电话:15090125178(微信同号)
电子邮箱:garson_zhang@163.com
站长微信二维码
微信二维码