Bootstrap

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

需求

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

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

1- 代码如下

<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

<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

<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

	.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- 实现结果如下:
在这里插入图片描述

;