Uniapp实现左侧导航和右侧scroll-view联动

一、实现目标

1、点击左侧分类列表,高亮显示当前分类项,右侧scroll-view滑动到分类对应的位置。

2、滑动右侧的scroll-view,左侧的分类列表高亮显示当前商品所属的分类。

二、功能点实现

1、点击左侧分类列表,高亮显示当前分类项。

<!--左侧导航栏-->
<view class="navigation">
    <view v-for="(cuisineType,index) in cuisineTypes"
          :key="index" :id="'left'+index"                                         
          @tap="chooseCuisineType(index)"
          class="cuisineTypeStatic"
		  :class="{'cuisineTypeActive': index === currentCuisineTypeIndex}">
	    {{cuisineType.typeName}}
	</view>
</view>

<script>
export default {
    data() {
        return{
            cuisineTypes: [],
            currentCuisineTypeIndex: 0,
        }
    },
   
    methods: {
        chooseCuisineType(index){
            this.currentCuisineTypeIndex = index   
        }
    }    
}
</script>

<style>
    .cuisineTypeActive {background: rgba(255, 255, 255, 1)}
</style>

cuisineTypes是一个向后端请求得到的按优先级排序的json数据,此处为

[
  {
    "id": 3,
    "typeName": "招牌菜",
    "priorityLevel": 0
  },
  {
    "id": 4,
    "typeName": "小吃",
    "priorityLevel": 1
  },
  {
    "id": 2,
    "typeName": "饮料",
    "priorityLevel": 2
  },
  {
    "id": 1,
    "typeName": "主食",
    "priorityLevel": 3
  }
]

currentCuisineTypeIndex记录当前点击的cuisineTypes列表下标。

:class动态class属性,当index === currentCuisineTypeIndex结果为true时,为<view>标签叠加cuisineTypeActive样式。

2、点击左侧分类列表项,右侧scroll-view滑动

<!--左侧导航栏-->
<view class="navigation">
    <view v-for="(cuisineType,index) in cuisineTypes"
          :key="index" :id="'left'+index"                                         
          @tap="chooseCuisineType(index)"
          class="cuisineTypeStatic"
		  :class="{'cuisineTypeActive': index === currentCuisineTypeIndex}">
	    {{cuisineType.typeName}}
	</view>
</view>

<!--右侧scroll-view-->
<scroll-view class="right-scroll" :scroll-y="true" :scroll-into-view="rightId">
    <view v-for="(cuisine,index) in cuisines" :key="index">
        <view :id="'right'+index">{{cuisine.cuisineType.typeName}}</view>
        <view v-for="(sameTypeCuisine,i) in cuisine.sameTypeCuisines" :key="i">
		    <image :src="sameTypeCuisine.cuisinePictureUrl"></image>
			<view class="cuisine-info">
			    <view>{{sameTypeCuisine.cuisineName}}</view>
				<view>¥{{sameTypeCuisine.cuisinePrice}}</view>
			</view>	
		</view> 
    </view>
<scroll-view>
<script>
export default {
    data() {
        return{
            cuisineTypes: [],
            cuisines: [],
            currentCuisineTypeIndex: 0,
            rightId: '',
        }
    },
   
    methods: {
        chooseCuisineType(index){
            this.currentCuisineTypeIndex = index
            this.rightId = 'right' + index   
        }
    }    
}
</script>

<style>
    .cuisineTypeActive {background: rgba(255, 255, 255, 1)}
</style>

cuisines是一个向后端请求得到按照类型排好的列表,此处为

[
  {
    "cuisineType": {
      "id": 3,
      "typeName": "招牌菜",
      "priorityLevel": 0
    },
    "sameTypeCuisines": [
      {
        "id": 1,
        "cuisineName": "冰皮鸡",
        "cuisinePictureUrl": "https://localhost:7106/StaticFiles/556733-冰皮鸡.jpg",
        "cuisinePrice": 66,
        "cuisineDescription": "清远三黄鸡",
        "t_CuisineType_Id": 3,
        "number": 0,
      },
      {
        "id": 2,
        "cuisineName": "北京烤鸭",
        "cuisinePictureUrl": "https://localhost:7106/StaticFiles/1621493-北京烤鸭.png",
        "cuisinePrice": 88,
        "cuisineDescription": "老北京招牌烤鸭",
        "t_CuisineType_Id": 3,
        "number": 0,
      }
    ]
  },
  {
    "cuisineType": {
      "id": 4,
      "typeName": "小吃",
      "priorityLevel": 1
    },
    "sameTypeCuisines": [
      {
        "id": 3,
        "cuisineName": "臭豆腐",
        "cuisinePictureUrl": "https://localhost:7106/StaticFiles/490542-臭豆腐.jpg",
        "cuisinePrice": 8,
        "cuisineDescription": "正宗台湾臭豆腐",
        "t_CuisineType_Id": 4,
        "number": 0,
      },
      {
        "id": 6,
        "cuisineName": "冰粉",
        "cuisinePictureUrl": "https://localhost:7106/StaticFiles/859892-屏幕截图 2023-03-15 002936.png",
        "cuisinePrice": 6,
        "cuisineDescription": "",
        "t_CuisineType_Id": 4,
        "number": 0,
      }
    ]
  },
  //省略
]

scroll-into-views属性,值应为子元素id(id不能以数字开头)。chooseCuisineType方法执行时改变了rightId的值,使scroll-view滚动到指定id的子元素。

3、滑动右侧的scroll-view,左侧的分类列表高亮显示当前商品所属的分类。

<!--左侧导航栏-->
<view class="navigation">
    <view v-for="(cuisineType,index) in cuisineTypes"
          :key="index" :id="'left'+index"                                         
          @tap="chooseCuisineType(index)"
          class="cuisineTypeStatic"
		  :class="{'cuisineTypeActive': index === currentCuisineTypeIndex}">
	    {{cuisineType.typeName}}
	</view>
<view>

<scroll-view class="right-scroll" :scroll-y="true" :scroll-into-view="rightId" @scroll="scrolling">
    <view v-for="(cuisine,index) in cuisines" :key="index">
        <view :id="'right'+index" class="typeName">
            {{cuisine.cuisineType.typeName}}
        </view>
        <view v-for="(sameTypeCuisine,i) in cuisine.sameTypeCuisines" :key="i">
		    <image :src="sameTypeCuisine.cuisinePictureUrl"></image>
			<view class="cuisine-info">
			    <view>{{sameTypeCuisine.cuisineName}}</view>
				<view>¥{{sameTypeCuisine.cuisinePrice}}</view>
			</view>	
		</view> 
    </view>
<scroll-view>
<script>
export default {
    data() {
        return{
            rightScrollHeight: 0,
            typeNamePositions: []
        }
    },
    mounted() {
	    this.getRightScrollHeight()
        this.getTypeNamePositions()
    },
    methods: {
        getRightScrollHeight() {
		    const query = uni.createSelectorQuery().in(this);
		    query.select('.right-scroll').boundingClientRect(data => {
		    this.rightScrollHeight = data.height}).exec();
		},
        getTypeNamePositions() {
		    const query = uni.createSelectorQuery().in(this);
			query.selectAll('.typeName').boundingClientRect(data => {
			    data.forEach((item) => {this.typeNamePositions.push(item.top)})
			}).exec();
		},
        scrolling(e) {
            //获取当前窗口顶边与scroll-view顶边的距离
            const scrollTop = e.target.scrollTop

            //右侧scroll-view滑到底时,左侧分类导航高亮显示最后一项
            const lastIndex = this.typeNamePositions.length - 1;
            if (scrollTop >= this.typeNamePositions[lastIndex]) {
	            this.currentCuisineTypeIndex = lastIndex;
	        }

            //判断右侧窗口顶边在哪两个分类名称之间,左侧分类导航高亮显示该索引项
            else{
                for (var i = 0; i < lastIndex; i++) {
                    if (scrollTop>= this.typeNamePositions[i]&&scrollTop<=this.typeNamePositions[i + 1]) {
			            this.currentCuisineTypeIndex = i
				        break;
			        }
		        }
            }
        }
    }
}
</script>

mounted钩子函数调用getRightScrollHeight()方法和getTypeNamePositions()方法,提前计算出右侧scroll-view元素的高度,和每一个分类项名称(typeNamePosition)所在的位置并存入数组中。

@scroll事件,当右侧scroll-view滑动时触发scrolling方法,判定当前窗口顶边在哪两个分类名称之间,左侧分类导航高亮显示该索引项

 

4、调整样式

为右侧scrow-view最后一个子元素(此处为主食数组),添加paddingBottom样式,paddingBottom的数值为倒数第二个元素的高度(即饮料数组的高度)。

mounted钩子函数调用penultSameTypeCuisinesHeight()方法,获取倒数第二个元素的高度。pensult中文释译为倒数第二个。

:style动态样式,三元表达式paddingBottom:index===cuisines.length-1?penultSameTypeCuisinesHeight +'px': ' ' 
当index == cuisines.length-1为true时,为view标签赋予paddingBottom。

三、完整代码

<!--左侧导航栏-->
<view class="navigation">
    <view v-for="(cuisineType,index) in cuisineTypes"
          :key="index" :id="'left'+index"                                         
          @tap="chooseCuisineType(index)"
          class="cuisineTypeStatic"
		  :class="{'cuisineTypeActive': index === currentCuisineTypeIndex}">
	    {{cuisineType.typeName}}
	</view>
</view>

<!--右侧scroll-view-->
<scroll-view class="right-scroll" :scroll-y="true" :scroll-into-view="rightId" @scroll="scrolling">
    <view v-for="(cuisine,index) in cuisines" :key="index">
        <view :id="'right'+index" class="typeName">
            {{cuisine.cuisineType.typeName}}
        </view>
        <view v-for="(sameTypeCuisine,i) in cuisine.sameTypeCuisines" :key="i"
:style="{paddingBottom:index===cuisines.length-1?penultSameTypeCuisinesHeight +'px':''}"
:class="[index===cuisines.length-2?'penultSameTypeCuisines':'']">
		    <image :src="sameTypeCuisine.cuisinePictureUrl"></image>
			<view class="cuisine-info">
			    <view>{{sameTypeCuisine.cuisineName}}</view>
				<view>¥{{sameTypeCuisine.cuisinePrice}}</view>
			</view>	
		</view> 
    </view>
<scroll-view>

<script>
export default {
    data() {
        return{
            cuisineTypes: [],
            cuisines: [],
            currentCuisineTypeIndex: 0,
            rightId: '',
            rightScrollHeight: 0,
            typeNamePositions: [],
            penultSameTypeCuisinesHeight: 0
        }
    },
    mounted() {
	    this.getRightScrollHeight()
        this.getTypeNamePositions()
        this.getPenultSameTypeCuisinesHeight()
    },
    methods: {
        chooseCuisineType(index){
            this.currentCuisineTypeIndex = index
            this.rightId = 'right' + index
        },
        getRightScrollHeight() {
		    const query = uni.createSelectorQuery().in(this);
		    query.select('.right-scroll').boundingClientRect(data => {
		    this.rightScrollHeight = data.height}).exec();
		},
        getTypeNamePositions() {
		    const query = uni.createSelectorQuery().in(this);
			query.selectAll('.typeName').boundingClientRect(data => {
			    data.forEach((item) => {this.typeNamePositions.push(item.top)})
			}).exec();
		},
        getPenultSameTypeCuisinesHeight() {
		    const query = uni.createSelectorQuery().in(this);
			query.select('.penultSameTypeCuisines').boundingClientRect(data => {
			    this.penultSameTypeCuisinesHeight = data.height
			}).exec();
		},
        scrolling(e) {
		    const scrollTop = e.target.scrollTop
			const lastIndex = this.typeNamePositions.length - 1;
			if (scrollTop >= this.typeNamePositions[lastIndex]) {
			    this.currentCuisineTypeIndex = lastIndex;
			} else {
			    for (var i = 0; i < lastIndex; i++) {
				    if (scrollTop >= this.typeNamePositions[i] && scrollTop <= this.typeNamePositions[i + 1]) {
					this.currentCuisineTypeIndex = i
					break;
					}
				}
			}
		}
    }    
}
</script>

<style>
    .cuisineTypeActive {background: rgba(255, 255, 255, 1)}
<style>

  • 29
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值