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

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>


http://www.kler.cn/a/594059.html

相关文章:

  • 使用 `pytest` 框架时,可以通过极限封装将 YAML 文件的读取、解析
  • 8、Python 字符串处理与正则表达式实战指南
  • 【css酷炫效果】纯CSS实现全屏粒子连线
  • Qt 实现波浪填充的圆形进度显示
  • 【Java】TCP网络编程:从可靠传输到Socket实战
  • coze ai assistant Task5
  • 学术PPT模板_院士_国家科学技术奖_杰青基金_长江学者特聘教授_校企联聘长江_重点研发_优青_青长_青拔ppt制作案例
  • RAG优化:python实现基于问题生成(扩展语义表示、优化检索粒度和提升上下文关联性)的文档增强RAG
  • 高级数据结构应用:并查集、跳表、布隆过滤器与缓存结构
  • Android Jetpack Compose介绍
  • RabbitMQ八股文
  • 【软考-架构】8.3、ES-OAS-ERP-电子政务-企业信息化
  • 【机器学习】核心概念
  • MCU-芯片时钟与总线和定时器关系,举例QSPI
  • C# 语法糖
  • 京东API数据清洗与结构化存储:从JSON原始数据到MySQL实战
  • 蓝桥杯之AT24C02的页写页读
  • 【OMCI实践】【案例分享】通过OLT升级ONT未自动重启问题分析
  • LeetCode 热题 100_跳跃游戏 II(79_45_中等_C++)(贪心算法)
  • LeeCode题库第五十八题