如何实现向下滚动时,含分类的ListView随分类位置横向移动?
实现滚动页面时分类栏横向联动的方案
核心思路
- 监听页面滚动事件,实时检测当前可视区域内的分类内容区块
- 匹配对应分类栏中的选项,计算横向滚动的目标位置,控制分类栏自动滚动对齐
原生JavaScript实现步骤及代码
- 准备DOM结构
<!-- 横向分类栏 --> <div class="category-list-container"> <ul class="category-list"> <li class="category-item active" data-target="category-1">分类1</li> <li class="category-item" data-target="category-2">分类2</li> <li class="category-item" data-target="category-3">分类3</li> <li class="category-item" data-target="category-4">分类4</li> <li class="category-item" data-target="category-5">分类5</li> </ul> </div> <!-- 页面内容区块 --> <div class="content-container"> <div class="category-block" id="category-1">分类1内容</div> <div class="category-block" id="category-2">分类2内容</div> <div class="category-block" id="category-3">分类3内容</div> <div class="category-block" id="category-4">分类4内容</div> <div class="category-block" id="category-5">分类5内容</div> </div>
- 基础样式
.category-list-container { position: sticky; top: 0; overflow-x: auto; background: #fff; border-bottom: 1px solid #eee; } .category-list { display: flex; list-style: none; padding: 0; margin: 0; } .category-item { padding: 12px 20px; cursor: pointer; white-space: nowrap; } .category-item.active { color: #1677ff; border-bottom: 2px solid #1677ff; } .category-block { height: 600px; margin: 20px 0; padding: 20px; border: 1px solid #eee; }
- JavaScript逻辑
// 获取DOM元素 const categoryItems = document.querySelectorAll('.category-item'); const categoryBlocks = document.querySelectorAll('.category-block'); const categoryList = document.querySelector('.category-list'); // 存储每个分类区块的顶部位置和对应分类项的偏移 const categoryMap = Array.from(categoryBlocks).map(block => { const targetId = block.id; const item = document.querySelector(`.category-item[data-target="${targetId}"]`); return { blockTop: block.offsetTop, blockHeight: block.offsetHeight, itemLeft: item.offsetLeft, item: item }; }); // 监听滚动事件 window.addEventListener('scroll', () => { const scrollTop = window.pageYOffset || document.documentElement.scrollTop; // 找到当前处于视口内的分类区块 const currentCategory = categoryMap.find(item => { return scrollTop >= item.blockTop - 100 && scrollTop < item.blockTop + item.blockHeight - 100; }); if (currentCategory) { // 移除所有激活状态 categoryItems.forEach(item => item.classList.remove('active')); // 添加当前分类激活状态 currentCategory.item.classList.add('active'); // 计算横向滚动目标位置,让当前分类项居中(可选,也可以对齐左侧) const targetScrollLeft = currentCategory.itemLeft - (categoryList.offsetWidth - currentCategory.item.offsetWidth) / 2; // 平滑滚动 categoryList.scrollTo({ left: targetScrollLeft, behavior: 'smooth' }); } });
Vue3框架下的实现思路
- 使用
onScroll监听页面滚动,通过ref获取分类栏和内容区块的DOM元素 - 计算每个内容区块的位置,滚动时匹配对应分类项,控制分类栏滚动并添加激活状态
<template> <div> <div class="category-list-container"> <ul ref="categoryList" class="category-list"> <li v-for="category in categories" :key="category.id" :class="{ active: activeCategoryId === category.id }" :data-target="`category-${category.id}`" > {{ category.name }} </li> </ul> </div> <div class="content-container"> <div v-for="category in categories" :key="category.id" :id="`category-${category.id}`" class="category-block" > {{ category.name }}内容 </div> </div> </div> </template> <script setup> import { ref, onMounted, onUnmounted } from 'vue'; const categories = ref([ { id: 1, name: '分类1' }, { id: 2, name: '分类2' }, { id: 3, name: '分类3' }, { id: 4, name: '分类4' }, { id: 5, name: '分类5' } ]); const categoryList = ref(null); const activeCategoryId = ref(1); let categoryMap = []; const handleScroll = () => { const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const currentCategory = categoryMap.find(item => { return scrollTop >= item.blockTop - 100 && scrollTop < item.blockTop + item.blockHeight - 100; }); if (currentCategory && activeCategoryId.value !== currentCategory.id) { activeCategoryId.value = currentCategory.id; const item = categoryList.value.querySelector(`[data-target="category-${currentCategory.id}"]`); const targetScrollLeft = item.offsetLeft - (categoryList.value.offsetWidth - item.offsetWidth) / 2; categoryList.value.scrollTo({ left: targetScrollLeft, behavior: 'smooth' }); } }; onMounted(() => { categoryMap = categories.value.map(category => { const block = document.getElementById(`category-${category.id}`); const item = categoryList.value.querySelector(`[data-target="category-${category.id}"]`); return { id: category.id, blockTop: block.offsetTop, blockHeight: block.offsetHeight, itemLeft: item.offsetLeft }; }); window.addEventListener('scroll', handleScroll); }); onUnmounted(() => { window.removeEventListener('scroll', handleScroll); }); </script> <style scoped> /* 样式同原生示例,略 */ </style>
注意事项
- 可调整滚动判断的阈值(示例中的100),让联动触发时机更贴合需求
- 若内容为动态加载,需在内容渲染完成后重新计算分类区块的位置信息
- 平滑滚动的
behavior: 'smooth'在旧浏览器不兼容时,可自定义动画或使用轻量滚动库替代
内容的提问来源于stack exchange,提问作者Ayz
相关产品推荐
相关产品推荐

