当前位置: 首页 > news >正文

Uni-App 双栏联动滚动组件开发详解 (电梯导航)

本文基于提供的代码实现一个左右联动的滚动组件,以下是详细的代码解析与实现原理说明:

<!--
  双栏联动滚动组件 - 技术解析
  功能特性:
  1. 左侧导航栏与右侧内容区双向联动
  2. 自适应容器高度
  3. 平滑滚动定位
  4. 动态内容位置计算
-->
<template>
	<view class="container">
		<!-- 外层容器 -->
		<view class="nav-container" id="navContainer">
			<!-- 左侧导航 ScrollView -->
			<scroll-view
				:scroll-y="true"
				:style="{ height: containerHeight + 'px' }"
				class="nav-sidebar"
				:scroll-into-view="leftScrollId"
				scroll-with-animation
			>
				<!-- 导航项循环渲染 -->
				<view
					v-for="(item, index) in leftData"
					:key="index"
					:id="'navItem-' + index"
					:class="['nav-item', { active: currentIndex === index }]"
					@tap="handleNavClick(index)"
				>
					{{ item }}
				</view>
			</scroll-view>

			<!-- 右侧内容 ScrollView -->
			<scroll-view
				:scroll-y="true"
				:style="{ height: containerHeight + 'px' }"
				class="content-main"
				:scroll-into-view="rightScrollId"
				@scroll="handleContentScroll"
				scroll-with-animation
			>
				<!-- 内容区块循环渲染 -->
				<view
					v-for="(section, sIndex) in rightData"
					:key="sIndex"
					:id="'content-' + sIndex"
					class="content-section"
				>
					<view class="section-title">{{ section.title }}</view>
					<view
						v-for="(para, pIndex) in section.content"
						:key="pIndex"
						class="content-para"
					>
						{{ para }}
					</view>
				</view>
				<view :style="{ height: fillHeight + 'px' }"></view>
			</scroll-view>
		</view>
	</view>
</template>

<script>
	export default {
		// 组件参数定义
		props: {
			leftData: {
				// 左侧导航数据
				type: Array,
				default: () => ['章节1', '章节2', '章节3', '章节4', '章节5', '章节6'],
			},
			rightData: {
				// 右侧内容数据
				type: Array,
				default: () => [
					{
						title: '章节1',
						content: ['内容1'],
					},
					{
						title: '章节2',
						content: ['内容1'],
					},
					{
						title: '章节3',
						content: ['内容1'],
					},
					{
						title: '章节4',
						content: ['内容1'],
					},
					{
						title: '章节5',
						content: ['内容1'],
					},
				],
			},
		},

		// 组件状态管理
		data() {
			return {
				containerTop: 0, //容器距离顶部距离
				containerHeight: 500, // 容器动态高度
				currentIndex: 0, // 当前激活索引
				sectionPositions: [], // 章节位置缓存数组
				positionsReady: false, // 位置计算完成标志
				fillHeight: 50, // 填充盒子的高度,内容滚动最后一项增加高度方便滚动
			}
		},

		// 计算属性
		computed: {
			// 左侧导航自动定位ID(保持选中项在可视区)
			leftScrollId() {
				return `navItem-${Math.max(this.currentIndex - 2, 0)}` // 提前2项滚动
			},

			// 右侧内容自动定位ID
			rightScrollId() {
				return `content-${this.currentIndex}`
			},
		},

		// 生命周期钩子
		mounted() {
			this.initContainer()
				.then(() => this.calcSectionPositions())
				.catch(console.error)
		},

		// 组件方法
		methods: {
			/**
			 * 初始化容器尺寸
			 * 使用 Promise 保证高度计算完成
			 */
			initContainer() {
				return new Promise((resolve) => {
					uni.createSelectorQuery()
						.in(this)
						.select('#navContainer')
						.boundingClientRect((res) => {
							this.containerTop = res.top //距离父元素顶部高度
							this.containerHeight = res.height
							resolve()
						})
						.exec()
				})
			},

			/**
			 * 计算内容区块位置
			 * 使用 uni API 获取元素位置信息
			 */
			calcSectionPositions() {
				uni.createSelectorQuery()
					.in(this)
					.selectAll('.content-section')
					.boundingClientRect((res) => {
						// 缓存各章节顶部位置
						this.sectionPositions = res.map((item) => item.top - this.containerTop)
						this.positionsReady = true

					
						let lastHeight = res[res.length - 1].height
						console.log(this.containerHeight, 8454545)
						//如果滚动显示的区域大于右侧单个元素的高度就要加入填充高度让元素滚动的时候 左侧的标签可以正常切换
						if (lastHeight- 20 < this.containerHeight ) {
							this.fillHeight = this.containerHeight - last + 20
						}
					})
					.exec()
			},

			/**
			 * 导航点击处理
			 * @param {number} index - 点击的导航索引
			 */
			handleNavClick(index) {
				this.currentIndex = index // 更新当前索引
			},

			/**
			 * 内容滚动处理
			 * @param {Object} e - 滚动事件对象
			 */
			handleContentScroll(e) {
				if (!this.positionsReady) return

				const scrollTop = e.detail.scrollTop
				const positions = this.sectionPositions

				// 二分查找算法优化(当前使用顺序查找)
				let current = this.currentIndex
				while (current < positions.length && positions[current] < scrollTop + 50) {
					current++
				}
				this.currentIndex = Math.max(current - 1, 0)
			},
		},
	}
</script>

<!-- 样式设计说明 -->
<style>
	/* 容器布局 */
	.container {
		height: 20vh; /* 全屏高度 */
		background: #ffffff;
	}

	.nav-container {
		display: flex; /* 弹性布局 */
		height: 100%;
	}

	/* 左侧导航样式 */
	.nav-sidebar {
		width: 200rpx; /* 固定宽度 */
		background: #f5f7fa; /* 浅色背景 */
		border-right: 1px solid #e4e7ed;
	}

	.nav-item {
		padding: 24rpx;
		transition: all 0.3s; /* 平滑过渡效果 */
	}

	.nav-item.active {
		color: #409eff; /* 主题色 */
		background: #ecf5ff; /* 激活背景 */
	}

	/* 右侧内容样式 */
	.content-main {
		flex: 1; /* 剩余空间填充 */
		padding: 32rpx;
	}

	.section-title {
		font-size: 36rpx; /* 标题字号 */
		font-weight: 600;
	}

	.content-para {
		background: #fafafa; /* 段落背景 */
		border-radius: 8rpx;
	}
</style>

技术实现要点

1. 双向滚动联动机制

  • 导航 → 内容:通过 scroll-into-view 绑定计算属性,点击时自动定位
  • 内容 → 导航:监听滚动事件,计算当前可见章节并更新激活状态

2. 性能优化设计

  • 位置信息缓存:预先计算章节位置,避免频繁查询DOM
  • 节流处理:滚动事件默认自带节流,保证性能
  • 异步计算:使用 Promise 链保证初始化顺序

3. 自适应布局

  • 动态高度计算:通过 uni API 获取容器实际高度
  • Flex 布局:实现左右栏自适应排列

4. 扩展性考虑

  • 组件化设计:通过 props 接收数据,方便复用
  • 样式可配置:通过 class 控制样式,易于主题定制

使用示例

<template>
  <dual-scroll 
    :left-data="categories" 
    :right-data="contents"
  />
</template>

<script>
// 示例数据结构
const categories = ['水果', '蔬菜', '肉类']
const contents = [
  {
    title: '水果',
    content: ['苹果', '香蕉', '橙子']
  },
  {
    title: '蔬菜',
    content: ['白菜', '萝卜', '番茄']
  }
]
</script>

相关文章:

  • 【JVM】性能监控与调优概述篇
  • 面试提问:数仓设计不分层可以吗?
  • 关于 51 单片机显示多个数码管时出现残影
  • 基于springboot的高校心理教育辅导系统(019)
  • Django之旅:第二节--启动运行django
  • ffmpeg+ubuntu编译库(完整版本)
  • 基于javaweb的SpringBoot时装购物系统设计与实现(源码+文档+部署讲解)
  • 常见业务分析模型
  • 3D开发工具HOOPS SDK:赋能CAM软件开发的利器
  • 美团Leaf分布式ID生成器:使用详解与核心原理解析
  • debian12运行sql server2022(docker):导入.MDF .LDF文件到容器
  • PHP与Python无缝融合,开启跨语言开发新纪元
  • 内网安全-横向移动Kerberos 攻击SPN 扫描WinRMWinRSRDP
  • C++ std::bitset
  • 【MyDB】5-索引管理之4-单元测试
  • 详解string类+迭代器
  • 【万字总结】前端全方位性能优化指南(二)——AVIF/WebP格式转换、渐进式加载、WASM解码加速
  • 【存储中间件】Redis核心技术与实战(四):Redis高并发高可用(Redis集群介绍与搭建)
  • TDengine 元数据
  • 从零开始实现 C++ TinyWebServer 异步日志系统 Log类详解
  • 海南省检察院专委李思阳被带走,去年曾遭人举报违规插手案件
  • 巴基斯坦副总理兼外长达尔将访华
  • 武汉警方通报一起故意伤害案件:1人死亡,嫌疑人已被抓获
  • 中国情怀:时代记录与家国镜相|澎湃·镜相第三届非虚构写作大赛暨七猫第六届百万奖金现实题材征文大赛征稿启事
  • 安徽省委常委、合肥市委书记费高云卸任副省长职务
  • 中华人民共和国和巴西联邦共和国关于强化携手构建更公正世界和更可持续星球的中巴命运共同体,共同维护多边主义的联合声明