uniapp uses scroll-into-view to implement anchor positioning and scroll monitoring functions [floor effect/side navigation linkage effect]

Big guy website:

https://blog.csdn.net/weixin_47136265/article/details/132303570

Effect

Please add image description

code

<template>
	<!-- 这里面有2个bug,已经解决,需要知道的地方
	 1.methods里的scrollEvt(e)方法里面的  this.tabIndex = index != -1 ?  index : 0;
	 2. 样式里面的.main 的height: calc(100vh - 300px);这个样式防止,分类滑动的时候,整体滑倒页面顶部bug,
	 设置固定高度,就不会整体滑动到页面顶部了
	 -->
	<view class="max">
		<view class="box">
			123
		</view>
		<view class="main">
			<scroll-view scroll-y="true" class="left-content">
				<view class="left-item" :class="{ 'activeItem': tabIndex == index }" v-for="(item,index) in leftData"
					:key="item.id" @click="clickLeftItem(index)">{
   
   {item.title}}</view>
			</scroll-view>
			<scroll-view scroll-y="true" class="right-content" :scroll-into-view="scrollId" scroll-with-animation
				@scroll="scrollEvt">
				<view class="right-item" v-for="(item,index) in rightData" :key="item.id" :id="'item'+index">
					<view class="title">
						{
   
   {item.title}}
					</view>
					<view class="content">
						<view class="content-item" v-for="itm in item.children" :key="itm.id">
							{
   
   {itm.text}}
						</view>
					</view>
				</view>
			</scroll-view>
		</view>
	</view>
</template>
<script>
	export default {
      
      
		data() {
      
      
			return {
      
      
				tabIndex: 0,
				scrollId: '',
				distanceList: [],
				timer: null,
				isLeftClick: false,
				leftData: [{
      
      
						title: '第一个',
						id: 10001
					},
					{
      
      
						title: '第二个',
						id: 10002
					},
					{
      
      
						title: '第三个',
						id: 10003
					},
					{
      
      
						title: '第四个',
						id: 10004
					},
					{
      
      
						title: '第五个',
						id: 10005
					},
					{
      
      
						title: '第六个',
						id: 10006
					}
				],
				rightData: [{
      
      
						id: '20001',
						title: '第一部分标题',
						children: [{
      
      
								id: '30001',
								text: '第一个子项'
							},
							{
      
      
								id: '30002',
								text: '第二个子项'
							},
							{
      
      
								id: '30003',
								text: '第三个子项'
							},
							{
      
      
								id: '30004',
								text: '第四个子项'
							}
						]
					},
					{
      
      
						id: '20002',
						title: '第二部分标题',
						children: [{
      
      
								id: '30005',
								text: '第一个子项'
							},
							{
      
      
								id: '30006',
								text: '第二个子项'
							},
							{
      
      
								id: '30007',
								text: '第三个子项'
							}
						]
					},
					{
      
      
						id: '20003',
						title: '第三部分标题',
						children: [{
      
      
								id: '30008',
								text: '第一个子项'
							},
							{
      
      
								id: '30009',
								text: '第二个子项'
							},
							{
      
      
								id: '30010',
								text: '第三个子项'
							},
							{
      
      
								id: '30011',
								text: '第四个子项'
							}
						]
					},
					{
      
      
						id: '20004',
						title: '第四部分标题',
						children: [{
      
      
								id: '30012',
								text: '第一个子项'
							},
							{
      
      
								id: '30013',
								text: '第二个子项'
							}
						]
					},
					{
      
      
						id: '20005',
						title: '第五部分标题',
						children: [{
      
      
								id: '300014',
								text: '第一个子项'
							},
							{
      
      
								id: '300015',
								text: '第二个子项'
							}
						]
					},
					{
      
      
						id: '20006',
						title: '第六部分标题',
						children: [{
      
      
								id: '300016',
								text: '第一个子项'
							},
							{
      
      
								id: '300017',
								text: '第二个子项'
							},
							{
      
      
								id: '300018',
								text: '第三个子项'
							},
							{
      
      
								id: '300019',
								text: '第四个子项'
							}
						]
					}
				]
			}
		},
		mounted() {
      
      
			setTimeout(() => {
      
      
				this.getDistanceToTop();
			}, 500)
		},

		methods: {
      
      
			clickLeftItem(index) {
      
      
				this.isLeftClick = true
				this.tabIndex = index
				this.scrollId = 'item' + index
			},
			getDistanceToTop() {
      
       //获取右侧各部分距离顶部的距离
				let that = this
				let selectorQuery = uni.createSelectorQuery().in(this);

				selectorQuery.selectAll('.right-item').boundingClientRect(function(rects) {
      
      
					rects.forEach(function(rect) {
      
      
						that.distanceList.push(rect.top)
					})
					console.log('that.distanceList', that.distanceList);
				}).exec()
			},

			// 元素滚动到顶部时,对应的左侧导航栏变为选中状态
			scrollEvt(e) {
      
      
				// 点击左侧导航栏引起的滚动不做判断
				if (this.isLeftClick) {
      
      
					this.isLeftClick = false
					return
				}
				// 防抖
				if (this.timer) {
      
      
					clearTimeout(this.timer)
				}
				this.timer = setTimeout(() => {
      
      
					let scrollTop = e.detail.scrollTop //滚动的高度
					// 找到位于顶部元素的索引,距离大于滚动高度的第一个元素的上一个元素就是此时位于顶部的元素
					let index = this.distanceList.findIndex(it => {
      
      
						// 滚动条的位置大于元素距离顶部位置的距离时,说明元素已经滑过了顶部
						return (it > scrollTop)
					}) - 1
					if (index == this.tabIndex) return
					// this.tabIndex = index;		// 这里是个	bug

					this.tabIndex = index != -1 ? index : 0; //  修改后可以了正确了,找人花了我50大洋,五分钟解决,太厉害了!!!
				}, 200)
			}
		}
	}
</script>
<style lang="less" scoped>
	.max {
      
      
		display: flex;
		flex-direction: column;
	}

	.box {
      
      
		width: 100%;
		height: 200rpx;
		border: 1rpx solid red;
	}

	.main {
      
      
		display: flex;
		justify-content: space-between;
		width: 100vw;
		// 这里控制楼层高度,防止全部滑动到页面顶部,这里设置高度,这样设置!!!
		height: calc(100vh - 300px);
		// 或者用这个var(--status-bar-height) 是app的上面状态栏高度
		//height: calc(100vh - var(--status-bar-height) - 300rpx);//重点!!!
		margin-top: 100rpx;
		border: 1rpx solid red;
		
	}

	.left-content {
      
      
		width: 250rpx;
		height: 100%;
		background-color: #E7E7E7;
	}

	.left-item {
      
      
		width: 100%;
		height: 100rpx;
		text-align: center;
		line-height: 100rpx;
	}

	.activeItem {
      
      
		background-color: #fff;
		color: skyblue;
	}

	.right-content {
      
      
		flex: 1;
		height: 100%;
		background-color: #f4f4f4;
	}

	.content-item {
      
      
		width: 100%;
		height: 200rpx;
		background-color: aqua;
		margin-top: 20rpx;
		overflow:auto;//重点
		height: calc(100vh - var(--status-bar-height) - 300rpx);//重点!!!
	}

	.title {
      
      
		padding: 15px 0 0 10px;
	}

	// 去掉右部分有滚动条
	/deep/::-webkit-scrollbar {
      
      
		display: none;
		width: 0;
		height: 0;
	}
</style>

two

Boss

Effect

Please add image description

code

<template>
	<view class="bodys">
		<view class="scroll_box" id="scroll_box">
			<scroll-view :style="{ height: scrollHeight + 'px' }" scroll-y='true' class="left_box"
				:scroll-into-view="leftIntoView">
				<view class="left_item" v-for="(item,i) in leftArray" :key='i' @click="onLeft" :data-index="i"
					:id="'left-'+i" :class="{select:i == leftIndex}">
					{
    
    {
    
    item}}
				</view>
			</scroll-view>
			<scroll-view :style="{ height: scrollHeight + 'px' }" @scroll="mainScroll" :scroll-into-view="scrollInto"
				scroll-y='true' class="right_box" scroll-with-animation="true">
				<slot></slot>
				<view class="right_item" v-for="(item,i) in rightArray" :key='i' :id="'item-'+i">
					<view class="rigth_title">
						{
    
    {
    
    item.title}}
					</view>
					<view class="lis" v-for="(items,j) in item.list" :key='j'>
						{
    
    {
    
    items}}
					</view>
				</view>
				<view class="fill-last" :style="{ height: fillHeight + 'px' }"></view>
			</scroll-view>
		</view>
	</view>
</template>

<script>
	export default {
    
    
		name: "side-navigation",
		data() {
    
    
			return {
    
    
				leftArray: [],
				rightArray: [],
				scrollHeight: 400,
				scrollInto: "",
				leftIndex: 0,
				topArr: [],
				scrollTopSize: 0,
				fillHeight: 0, // 填充高度,用于最后一项低于滚动区域时使用
			}
		},
		computed: {
    
    
			/* 计算左侧滚动位置定位 */
			leftIntoView() {
    
    
				return `left-${
      
      this.leftIndex > 3 ? this.leftIndex - 3 : 0}`;
			}
		},
		mounted() {
    
    
			/* 等待DOM挂载完成 */
			this.$nextTick(() => {
    
    
				/* 在非H5平台,nextTick回调后有概率获取到错误的元素高度,则添加200ms的延迟来减少BUG的产生 */
				setTimeout(() => {
    
    
					/* 等待滚动区域初始化完成 */
					this.initScrollView().then(() => {
    
    
						/* 获取列表数据,你的代码从此处开始 */
						this.getListData();
					});
				}, 200);
			});
		},
		methods: {
    
    
			/* 初始化滚动区域 */
			initScrollView() {
    
    
				return new Promise((resolve, reject) => {
    
    
					let view = uni.createSelectorQuery().select('#scroll_box');
					view.boundingClientRect(res => {
    
    
						console.log(res);
						this.scrollTopSize = res.top;
						this.scrollHeight = res.height;
						this.$nextTick(() => {
    
    
							resolve();
						});
					}).exec();
				});
			},
			// 获取数据
			getListData() {
    
    
				new Promise((resolve, reject) => {
    
    
					uni.showLoading();
					setTimeout(() => {
    
    
						let [left, main] = [
							[],
							[]
						];

						for (let i = 0; i < 25; i++) {
    
    
							left.push(`${
      
      i + 1}类商品`);

							let list = [];
							let r = Math.floor(Math.random() * 10);
							r = r < 1 ? 3 : r;
							for (let j = 0; j < r; j++) {
    
    
								list.push(j);
							}
							main.push({
    
    
								title: `${
      
      i + 1}类商品标题`,
								list
							});
						}

						// 将请求接口返回的数据传递给 Promise 对象的 then 函数。
						resolve({
    
    
							left,
							main
						});
					}, 1000);
				}).then(res => {
    
    
					uni.hideLoading();
					this.leftArray = res.left;
					this.rightArray = res.main;
					// DOM 挂载后 再调用 getElementTop 获取高度的方法。
					this.$nextTick(() => {
    
    
						this.getElementTop();
					});
				});
			},
			// 获取元素顶部信息
			getElementTop() {
    
    
				new Promise((resolve, reject) => {
    
    
					let view = uni.createSelectorQuery().selectAll('.right_item');
					view.boundingClientRect(data => {
    
    
						resolve(data);
					}).exec();
				}).then(res => {
    
    
					console.log(res);
					let topArr = res.map(item => {
    
    
						return item.top - this.scrollTopSize; /* 减去滚动容器距离顶部的距离 */
					});
					this.topArr = topArr;

					/* 获取最后一项的高度,设置填充高度。判断和填充时做了 +-20 的操作,是为了滚动时更好的定位 */
					let last = res[res.length - 1].height;
					if (last - 20 < this.scrollHeight) {
    
    
						this.fillHeight = this.scrollHeight - last + 20;
					}
				});
			},
			// 点击左侧导航
			onLeft(e) {
    
    
				const index = e.currentTarget.dataset.index;
				// this.leftIndex = index
				this.scrollInto = `item-${
      
      index}`
			},
			// 右侧滑动
			mainScroll(e) {
    
    
				let top = e.detail.scrollTop;
				let index = 0;
				/* 查找当前滚动距离 */
				for (let i = this.topArr.length - 1; i >= 0; i--) {
    
    
					/* 在部分安卓设备上,因手机逻辑分辨率与rpx单位计算不是整数,滚动距离与有误差,增加2px来完善该问题 */
					if (top + 2 >= this.topArr[i]) {
    
    
						index = i;
						break;
					}
				}
				this.leftIndex = index < 0 ? 0 : index;
			},
		},

	}
</script>

<style>
	page,.bodys {
    
    
		height: 100%;
	}

	.scroll_box {
    
    
		display: flex;
		height: 100%;
	}

	.left_box {
    
    
		width: 30%;
	}

	.left_item {
    
    
		height: 80rpx;
	}

	.lis {
    
    
		height: 200rpx;
		border-radius: 10rpx;
		background: #808080;
		color: #FFFFFF;
		text-align: center;
		line-height: 200rpx;
		margin-bottom: 10rpx;
	}

	.select {
    
    
		background-color: #4CD964;
	}
</style>

Vant-UI official website

URL 1

URL 2

Recommended uniapp plug-in market

uniapp插件市场搜索商品分类!!!
好用,兼容性各个端,好用,我就用的这个插件!!!

address

https://ext.dcloud.net.cn/plugin?id=13148

Guess you like

Origin blog.csdn.net/m0_49714202/article/details/133354892