vue实现导航里面锚点定位和滚动监听功能

需求

我们在开发过程中有时候会遇到左侧导航菜单栏数据需要监听和右侧顶部导航菜单联动效果。这里我们通常使用锚点定位和滚动监听方法实现。这里我们使用两种方案解决,第一是常规的出来方法,第二是通过uniapp里面的scroll-view进行处理

具体实现方案如下
一、vue里面处理,通过监听滚动事件和定义元素

1- 代码如下

javascript 复制代码
<template>
  <div>
    <div class="outBox">
      <div class="first">
        <div :class="['level', {'select': i == first.select}]" style="background-color: #666;" v-for="(v, i) in first.list">{{ v }}</div>
      </div>
      <div class="second" id="select">
        <div :class="['level', {'select': i == second.select}]" style="background-color: #666;" v-for="(v, i) in second.list">{{ v }}</div>
      </div>
      <div class="content" id="content">
        <div v-if="status" class="box box1" style="height: 100px;">二级类目1 商品内容</div>
        <div v-if="status" class="box box2" style="height: 344px;">二级类目2 商品内容</div>
        <div v-if="status" class="box box3" style="height: 470px;">二级类目3 商品内容</div>
        <div v-if="status" class="box box4" style="height: 230px;">二级类目4 商品内容</div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      first: {
        select: 0,
        list: ['一级类目1', '一级类目2', '一级类目3', '一级类目4',  '一级类目5']
      },
      second: {
        select: 0,
        list: ['二级类目1', '二级类目2', '二级类目3', '二级类目4']
      },
      list: [],
      timer: null,
      status: true
    }
  },
  methods: {
    scroll() {
      let scrollTop = document.getElementById('content').scrollTop
      let i = this.list.findIndex(v => v >= scrollTop)
      this.second.select = i
      if (scrollTop >= this.list[this.list.length - 1] - 20) {
        this.timer && clearTimeout(this.timer)
        this.timer = setTimeout(() => {
          this.second.select = 0
          this.first.select += 1
          document.getElementById('content').removeEventListener('scroll', this.scroll)
          document.getElementById('content').scrollTo(0, 10)
          document.getElementById('content').addEventListener('scroll', this.scroll)
          // 判断如果左侧导航菜单数据没有的情况默认选中定义导航菜单
          if (this.first.select >= this.first.list.length) {
            this.first.select = 0
          }
        }, 300)
       
      } else if (scrollTop == 0) {
        this.timer && clearTimeout(this.timer)
        this.timer = setTimeout(() => {
          this.second.select = 0
          if (this.second.select > 0) {
            this.first.select -= 1
          } else {
            // 滚动到顶部刷新数据
            this.first.select = 0
          }
          document.getElementById('content').removeEventListener('scroll', this.scroll)
          document.getElementById('content').scrollTo(0, 10)
          document.getElementById('content').addEventListener('scroll', this.scroll)
        }, 300)
      }
    }
  },
  mounted() {
    let o = document.getElementsByClassName('box')
    for ( let i = 0; i < o.length; i++) {
      // 监测右侧导航菜单距离顶部的距离60
      this.list[i] = i > 0 ? o[i-1].scrollHeight + this.list[i - 1] + (60 * (i > 1 ? 1 : 0)) : 10
    }
    let scrollHeight = document.getElementById('content').scrollHeight
    let offsetHeight = document.getElementById('content').offsetHeight
    if (scrollHeight - offsetHeight < this.list[this.list.length - 1]) {
      this.list[this.list.length - 1] = scrollHeight - offsetHeight
    }
    document.getElementById('content').addEventListener('scroll', this.scroll)
  }
}
</script>
<style>
.outBox{
    width: 400px;
    height: 300px;
    margin: 0;
    padding: 0;
    background-color: #eee;
    position: relative;
  }
  .first{
    position: absolute;
    left: 0;
    top: 0;
    width: 100px;
    height: 100%;
    overflow: auto;
    background-color: rgb(102, 102, 102);
  }
  .first .level{
    height: 30px;
    font-size: 14px;
    text-align: center;
    line-height: 30px;
  }
  .second{
    position: absolute;
    left: 100px;
    top: 0;
    width: calc(100% - 100px);
    height: 60px;
    overflow: auto;
    background-color: #06c;
    display: flex;
    align-items: center;
    justify-content: space-around;
  }
  .second .level{
    height: 30px;
    font-size: 14px;
    text-align: center;
    line-height: 30px;
  }
  .content{
    position: absolute;
    left: 100px;
    top: 60px;
    width: calc(100% - 100px);
    height: calc(100% - 60px);
    min-height: 240px;
    overflow: auto;
    box-sizing: border-box;
  }
  .box{
    background-color: aquamarine;
    margin-bottom: 20px;
  }
  .select{
    color: #f56c6c;
  }
</style>

2- 实现结果如下:


二、uni-app使用scroll-view锚点定位和滚动监听功能

1- 代码如下
html

javascript 复制代码
<template>
	<view class="classicsBox">
		<scroll-view class="classics-left" scroll-y="true" scroll-with-animation :scroll-into-view="clickId">
			<view v-for="(item,index) in contentData" :key="item.id" :id="item.id" class="classics-left-item" :class="picked==index?'checkedStyle':''" @click="selectActiveEvt(item)">
				{{item.title}}
			</view>
		</scroll-view>
		<!-- :scroll-top="scrollRightTop" -->
		<scroll-view scroll-y="true"  :scroll-into-view="clickId" :scroll-anchoring="true" scroll-with-animation class="classics-right" @scroll="scrollEvt">
			<view class="classics-right-item" v-for="item in contentData" :key="item.id" :id="item.id">
				<view class="title">
					{{item.title}}
				</view>
				<view class="item-box" v-for="it in item.content">
					<img class="item-box-left" :src="it.img" alt="">
					<view class="item-box-right">
						<view class="item-box-right-name">
							{{it.name}}
						</view>
						<view class="item-box-right-describe">
							{{it.desc}}
						</view>
						<view class="item-box-right-buy">
							<view class="item-box-right-price">
								¥{{it.price}}
							</view>
							<view class="item-box-right-pick">
								选规格
							</view>
						</view>
					</view>
				</view>
			</view>
		</scroll-view>
	</view>
</template>

js

javascript 复制代码
<script>
	export default {
		components: {},
		data() {
			return {
				contentData:[{
						id:'tab1',
						title:'热销专区',
						content:[
							{id:101,img:'static/rexiao1.png',name:'郁金香',desc:'花语',price:'99',},
							{id:102,img:'static/rexiao2.png',name:'郁金香',desc:'花语',price:'99',},
						]
					},
					{
						id:'tab2',
						title:'生日鲜花',
						content:[
							{id:201,img:'static/shengri1.png',name:'郁金香',desc:'花语',price:'99',},
							{id:202,img:'static/shengri2.png',name:'郁金香',desc:'花语',price:'99',},
						]
					},......],//列表数据
				clickId:'tab1', //点击选项的id
				picked:0, // 左侧选中选项的index
				nowRightIndex:0, // 右边当前滚动的index
				itemArr:[], //用于存放右侧item位置数据
				scrollRightTop:0,
				timer:null,
			}
		},
		
		methods: {
			// 左侧切换点击事件
			selectActiveEvt(e) {
				this.clickId = e.id;
				this.picked = this.contentData.findIndex(it=>it.id==e.id);
			},
			scrollEvt(e){
				// 防抖
				if(this.timer){
					clearTimeout(this.timer);
				}
				this.timer = setTimeout(()=>{
					this.nowRightIndex =  this.itemArr.findLastIndex(it=>e.detail.scrollTop>=(it.bottom-228))+1; //判断当前顶部是处于哪个item,获取当前item的index
					if(this.nowRightIndex==-1) this.nowRightIndex=0;
					if(this.nowRightIndex==this.picked) return;
					this.clickId = this.contentData[this.nowRightIndex].id;
					this.picked = this.nowRightIndex;
				},500);
			},
			// 计算右侧每个item到顶部的距离,存放到数组
			getItemDistence(){
				new Promise(resolve=>{
					let selectQuery = uni.createSelectorQuery().in(this);
					selectQuery.selectAll('.classics-right-item').boundingClientRect(rect=>{
						if(!rect.length){
							setTimeout(()=>{
								this.getItemDistence();
							},10);
							return;
						}
						rect.forEach(it=>{
							this.itemArr.push(it); // 这里获取到的数据是每个item距离页面顶部的数据,以及每个item的自身数据
							resolve();
						})
					}).exec()
				})
			},
		},
		mounted(){
			// 设置一个延时,确保所有dom和样式加载完成,否则拿到的数据可能有误
			setTimeout(()=>{
				this.getItemDistence();
			},500)
			
		},
	}
</script>

css

javascript 复制代码
	.sortBox{
		height: 100%;
		width: 100%;
		box-sizing: border-box;
		/* padding: 12px; */
		padding-bottom: 0;
		position: relative;
	}
	.boxTop{
		width: 100%;
		position: fixed;
		top: 44px;
		left: 0;
		height: 220px;
	}
	.sort-search{
		height: 50px;
		display: flex;
		justify-content: space-between;
		padding: 12px;
		padding-bottom: 0;
		box-sizing: border-box;
	}
	.sort-search-left{
		box-sizing: border-box;
		width: 100px;
		height: 38px;
		line-height: 38px;
		border-radius: 16px;
		border: 1px solid #f0f0f0;
		text-align: right;
		padding: 0 10px;
		background: url('../../static/ping.png') left center no-repeat;
		background-size: 45%;
	}
	.sort-search-right{
		width: 36px;
		height: 100%;
		margin-right: 80px;
		border: #f0f0f0 1px solid;
		border-radius: 19px;
		text-align: center;
		line-height: 36px;
	}
	.sort-collect{
		margin-top: 15px;
 
	}
	.sort-broadcast{
		width: 100%;
		height: 30px;
		background-color: #fff7fa;
		display: flex;
		font-size: 18px;
		line-height: 30px;
		padding: 0 10px;
		box-sizing: border-box;
		font-weight: bold;
		
	}
	.sort-type{
		display: flex;
		height: 60px;
		box-sizing: border-box;
		align-items: flex-end;
		justify-content: space-evenly;
		font-size: 16px;
		font-weight: bold;
		color: #909090;
	}
	.sort-type-item{
		display: flex;
		align-items: center;
		height: 40px;
	}
	.sort-type-item-img{
		height: 30px;
		margin-right: 6px;
	}
	.sort-type-item-text{
		height: 100%;
		line-height: 40px;
	}
	.sort-type-item-text-checked{
		border-bottom: #ff9092 3px solid;
		color: #41414d;
	}
	.opcity{
		opacity: 0;
	}
	.sortBox-scoll{
		position: absolute;
		top: 220px;
		left: 0;
		width: 100%;
		background-color: #f0f0f0;
		height: calc(100vh  - 314px);
		overflow-y: auto;
	}

2- 实现结果如下:

相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax