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- 实现结果如下:

相关推荐
CodeCraft Studio23 分钟前
文档开发组件Aspose 25.12全新发布:多模块更新,继续强化文档、图像与演示处理能力
前端·.net·ppt·aspose·文档转换·word文档开发·文档开发api
PPPPickup1 小时前
easychat项目复盘---获取联系人列表,联系人详细,删除拉黑联系人
java·前端·javascript
老前端的功夫1 小时前
前端高可靠架构:医疗级Web应用的实时通信设计与实践
前端·javascript·vue.js·ubuntu·架构·前端框架
前端大卫2 小时前
【重磅福利】学生认证可免费领取 Gemini 3 Pro 一年
前端·人工智能
孜燃2 小时前
Flutter APP跳转Flutter APP 携带参数
前端·flutter
脾气有点小暴2 小时前
前端页面跳转的核心区别与实战指南
开发语言·前端·javascript
lxh01132 小时前
最长递增子序列
前端·数据结构·算法
vipbic2 小时前
我封装了一个“瑞士军刀”级插件,并顺手搞定了自动化部署
vue.js·nuxt.js
Youyzq3 小时前
前端项目发布到cdn上css被编译失效问题rgba失效和rgb失效
前端·css·算法·cdn
San30.3 小时前
深入 JavaScript 内存机制:从栈与堆到闭包的底层原理
开发语言·javascript·udp