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

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

javascript 复制代码
<!--
  双栏联动滚动组件 - 技术解析
  功能特性:
  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 控制样式,易于主题定制

使用示例

javascript 复制代码
<template>
  <dual-scroll 
    :left-data="categories" 
    :right-data="contents"
  />
</template>

<script>
// 示例数据结构
const categories = ['水果', '蔬菜', '肉类']
const contents = [
  {
    title: '水果',
    content: ['苹果', '香蕉', '橙子']
  },
  {
    title: '蔬菜',
    content: ['白菜', '萝卜', '番茄']
  }
]
</script>
相关推荐
Q_Q5110082855 小时前
python+uniapp基于微信小程序团购系统
spring boot·python·微信小程序·django·uni-app·node.js·php
炒毛豆6 小时前
uniapp微信小程序+vue3基础内容介绍~(含标签、组件生命周期、页面生命周期、条件编译(一码多用)、分包))
vue.js·微信小程序·uni-app
盛夏绽放10 小时前
关于 uni-app 与原生微信小程序中的生命周期 —— 一次“生命旅程”的解读
微信小程序·小程序·uni-app
知识分享小能手11 小时前
uni-app 入门学习教程,从入门到精通,uni-app 基础知识详解 (2)
前端·javascript·windows·学习·微信小程序·小程序·uni-app
2501_9160088912 小时前
iOS 发布全流程详解,从开发到上架的流程与跨平台使用 开心上架 发布实战
android·macos·ios·小程序·uni-app·cocoa·iphone
风清云淡_A15 小时前
【uniapp】uni.uploadFile上传数据多出一个304的get请求处理方法
uni-app
shykevin15 小时前
uni-app x商城,商品列表组件封装以及使用
windows·uni-app
cesske15 小时前
uniapp 编译支付宝小程序canvas 合成图片实例,支付宝小程序 canvas 渲染图片 可以换成自己的图片即可
小程序·uni-app·apache
Q_Q5110082851 天前
python+uniapp基于微信小程序的旅游信息系统
spring boot·python·微信小程序·django·flask·uni-app·node.js
2501_916007471 天前
iOS 混淆工具链实战,多工具组合完成 IPA 混淆与加固(iOS混淆|IPA加固|无源码混淆|App 防反编译)
android·ios·小程序·https·uni-app·iphone·webview