Uniapp实现左右联动 左侧标题 右侧滚动

实现左侧点击标题 右侧滑动至相应模块的效果 需要使用scroll-view 进行滚动及模块定位

1637220814276.jpg

1.首先实现左侧点击控制右侧滑动至对应模块

左侧渲染模块标签
 <div class='containerleft'>
         <div class='item' v-for="(item,index) in menuList" :class="index===currenIndex?'active':''" @click='handleMenuChange(item,index)'>
           <p>{{item.title}}</p>
         </div>
     </div>
     
menuList:[{name:'招录数据',id:'zl'},{name:'升学工具',id:'sx'},{name:'高校查询',id:'gx'},{name:'专业探索',id:'zy'},{name:'了解职业',id:'lj'},],
复制代码
右侧使用srollview 设置纵向滚动:scroll-y="true",注意设置高度,通过scroll-into-view的id定位到到对应模块 从而实现点击左侧 右侧滑动的效果 同时scroll-with-animation开启定位动画
 <div class='containerright'>
       <scroll-view scroll-y="true" style="height: 100%;" :scroll-into-view="currentid" :scroll-with-animation='true' @scroll='scroll'>
         <div v-for="(item,index) in menuList" class='contentitem' :id='item.id' :key='index'>
            <div class='title':class="index===positionIndex?'positiontop':''" :style="[index===beforeIndex?positionabsolute:'']">{{item.name}}</div>
            <div class='menubox'>
                <div class='menuitem' v-for="(ite,ind) in Data" :key='ind' >
                  <image :src="ite.icon" mode="" class='icon'></image>
                  <p>{{ite.name}}</p>
                </div>
            </div>
         </div>
       </scroll-view>
     </div>
      data() {
    return {
        isClick:false, // 是否点击左侧
        isShow: false, //是否显示本页面
        currenIndex:0, // 当前点击下标 左侧激活下标

        currentid:'', // 当前id
        topArr:[],    // 各模块顶部距离数组
        beforeIndex:-1, // 前一个定位模块
        positionIndex:-1, // 当前需要定位模块
        positionabsolute:{
          'position':'absolute',
          'bottom':'0',
          'left':'0',
        },
        menuList:[
        {name:'招录数据',id:'zl'},
        {name:'升学工具',id:'sx'},
        {name:'高校查询',id:'gx'},
        {name:'专业探索',id:'zy'},
        {name:'了解职业',id:'lj'},],
        Data:[
        {name:'招生计划',icon:'https://cdn.img.up678.com/ueditor/upload/image/20211117/1637128354571050818.jpg'},
        {name:'历年录取',icon:'https://cdn.img.up678.com/ueditor/upload/image/20211117/1637128354571050818.jpg'},
        {name:'省控线',icon:'https://cdn.img.up678.com/ueditor/upload/image/20211117/1637128354571050818.jpg'},
        {name:'位次表',icon:'https://cdn.img.up678.com/ueditor/upload/image/20211117/1637128354571050818.jpg'},],

    };
  },
复制代码

2.实现右侧滑动左侧激活对应标题

 <div class='containerleft'>
         <div class='item' v-for="(item,index) in menuList" :class="index===currenIndex?'active':''" @click='handleMenuChange(item,index)'>
           <p>{{item.title}}</p>
         </div>
     </div>
     
menuList:[{name:'招录数据',id:'zl'},{name:'升学工具',id:'sx'},{name:'高校查询',id:'gx'},{name:'专业探索',id:'zy'},{name:'了解职业',id:'lj'},],
复制代码
右侧通过 @scroll='scroll'的e.detail.scrollTop获取距离顶部的距离,
 <scroll-view scroll-y="true" style="height: 100%;" :scroll-into-view="currentid" :scroll-with-animation='true' @scroll='scroll'>
复制代码
页面渲染后通过id,uni.createSelectorQuery()获取各模块距离顶部距离的数组,注意在页面mounted之后使用
 // 获取模块距离顶部高度
    getModuleTop(){
      const query = uni.createSelectorQuery().in(this);
      let topArr=[];
      for(var i=0;i<this.DataList.length;i++){
        query.select(`#${this.DataList[i].id}`).boundingClientRect(data => {
          // console.log("得到布局位置信息" + JSON.stringify(data));
          // console.log("节点离页面顶部的距离为" + data.top);
         const res= topArr.findIndex((item)=>{
            return item===data.top
          })
          if(res===-1){
            topArr.push(data.top)
          }
        }).exec();
      }
      setTimeout(()=>{
        this.topArr=[...topArr]
      },500)
    },
复制代码
判断在数组范围内的下标 并返回下标 通过current控制左侧标题
// 判断在数组范围内的下标 并返回下标 右侧标题定位头部
    // 判断在数组范围内的下标 并返回下标 控制左侧标题
    getIndex(value,arr){
      return arr.findIndex((item)=>{
        // console.log(item,value)
        return value>=(item-300) && value<=(item)
      })
    },
复制代码

3.右侧标题加定位滑动效果

通过当前移动到的下标 将其固定定位至头部 而之前的固定定位标题beforeIndex则改为相对定位 将其定位至上一个box的底部 从而实现页面标题上推 下一个标题固定的效果
  <div class='title':class="index===positionIndex?'positiontop':''" :style="[index===beforeIndex?positionabsolute:'']">{{item.title}}</div>
  data(){
  return {
        beforeIndex:-1, // 前一个定位模块
        positionIndex:-1, // 当前需要定位模块
        positionabsolute:{
          'position':'absolute',
          'bottom':'0',
          'left':'0',
        },
      }
  }
复制代码
注意需要提前获取下一个即将固定定位标题的位置 从而将上一个固定定位标题置为相对定位
        // 控制头部定位
         const rightres=this.getPositionIndex(e.detail.scrollTop,this.topArr)
         if(e.detail.scrollTop<5){
           this.beforeIndex=-1
           this.positionIndex=-1
         }else{
           if(rightres!==-1){
              if(this.positionIndex!==rightres){
                 this.beforeIndex=-1
                 this.positionIndex=rightres
              }

           }else{
             this.beforeIndex=-1
             this.positionIndex=-1
           }
         }

         const rightbeforeres=this.getBeforePositionIndex(e.detail.scrollTop,this.topArr)
         if(rightbeforeres!==-1){
             if(this.positionIndex!==rightbeforeres){
                 const index=this.positionIndex
                 this.beforeIndex=index
                 this.positionIndex=-1
             }

         }
            // 判断在数组范围内的下标 并返回下标 右侧标题定位头部
            getPositionIndex(value,arr){
              return arr.findIndex((item,index)=>{
                return value>=(item-50) && value<=(item+(arr[index+1]-arr[index]-40))
              })
            },
            // 判断在数组范围内的下标 并返回下标 上一个固定定位标题相对定位至其box底部
            getBeforePositionIndex(value,arr){
              return arr.findIndex((item,index)=>{
                return value>=(item-60) && value<=(item+(arr[index+1]-arr[index]-60))
              })
            },
复制代码

4.全部代码

<template>
  <div  v-if="isShow" class='toolkit-container'>
   <div class='containerbox'>
     <div class='containerleft'>
         <div class='item' v-for="(item,index) in menuList" :class="index===currenIndex?'active':''" @click='handleMenuChange(item,index)'>
           <p>{{item.name}}</p>
         </div>
     </div>
     <div class='containerright'>
       <scroll-view scroll-y="true" style="height: 100%;" :scroll-into-view="currentid" :scroll-with-animation='true' @scroll='scroll'>
         <div v-for="(item,index) in menuList" class='contentitem' :id='item.id' :key='index'>
            <div class='title':class="index===positionIndex?'positiontop':''" :style="[index===beforeIndex?positionabsolute:'']">{{item.name}}</div>
            <div class='menubox'>
                <div class='menuitem' v-for="(ite,ind) in Data" :key='ind' @click='handleswitch(ite)'>
                  <image :src="ite.icon" mode="" class='icon'></image>
                  <p>{{ite.name}}</p>
                </div>
            </div>
         </div>
       </scroll-view>
     </div>
   </div>
   <div class='footer'>
     主办:贵州省招生考试服务中心
   </div>
  </div>
</template>

<script>
import { footMixins } from "./config.js";
export default {
  data() {
    return {
        isClick:false, // 是否点击左侧
        isShow: false, //是否显示本页面
        currenIndex:0, // 当前点击下标 左侧激活下标
        currentid:'', // 当前id
        topArr:[],    // 各模块顶部距离数组
        beforeIndex:-1, // 前一个定位模块
        positionIndex:-1, // 当前需要定位模块
        positionabsolute:{
          'position':'absolute',
          'bottom':'0',
          'left':'0',
        },
        //测试数据
        menuList:[{name:'招录数据',id:'zl'},{name:'升学工具',id:'sx'},{name:'高校查询',id:'gx'},{name:'专业探索',id:'zy'},{name:'了解职业',id:'lj'},],
         Data:[{name:'招生计划',icon:'https://cdn.img.up678.com/ueditor/upload/image/20211117/1637128354571050818.jpg'},
         {name:'历年录取',icon:'https://cdn.img.up678.com/ueditor/upload/image/20211117/1637128354571050818.jpg'},
        {name:'省控线',icon:'https://cdn.img.up678.com/ueditor/upload/image/20211117/1637128354571050818.jpg'},
       {name:'位次表',icon:'https://cdn.img.up678.com/ueditor/upload/image/20211117/1637128354571050818.jpg'},],

    };
  },
  mixins: [footMixins],
  components: {

  },
	watch: {
        isShow: function(val, oldVal) {
          if(val){
        		this.getInit()
        	}
        }
	},
  mounted(){
   // this.getModuleTop()
  },
  methods: {
    async getInit(){
     const res = await this.$ajax({
       url:'/gzsx/data/toolbox'
     })
     if(res.code===200){
       this.DataList=[...res.list]
     }
      this.DataList.map((item,index)=>{
        item.id=`menu${index}`
      })
     setTimeout(()=>{
       this.getModuleTop()
     },200)
    },
    // 左侧点击 定位右侧内容
    handleMenuChange(item,index){
      this.currentid=item.id
      this.currenIndex=index
      this.isClick=true
      this.beforeIndex=-1
      this.positionIndex=-1
    },
    // 右侧滚动
    scroll(e){
      //点击滑动不进行顶部定位效果
      if(this.isClick){
        this.isClick=false
      }else{
        // 控制左侧标题
        const res=this.getIndex(e.detail.scrollTop,this.topArr)
         if(res!==-1){
           this.currenIndex=res
         }
         // 控制头部定位
         const rightres=this.getPositionIndex(e.detail.scrollTop,this.topArr)
         if(e.detail.scrollTop<5){
           this.beforeIndex=-1
           this.positionIndex=-1
         }else{
           if(rightres!==-1){
              if(this.positionIndex!==rightres){
                 this.beforeIndex=-1
                 this.positionIndex=rightres
              }

           }else{
             this.beforeIndex=-1
             this.positionIndex=-1
           }
         }

         const rightbeforeres=this.getBeforePositionIndex(e.detail.scrollTop,this.topArr)
         if(rightbeforeres!==-1){
             if(this.positionIndex!==rightbeforeres){
                 const index=this.positionIndex
                 this.beforeIndex=index
                 this.positionIndex=-1
             }

         }
      }
    },
    // 获取模块距离顶部高度
    getModuleTop(){
      const query = uni.createSelectorQuery().in(this);
      let topArr=[];
      for(var i=0;i<this.menuList.length;i++){
        query.select(`#${this.DataList[i].id}`).boundingClientRect(data => {
          // console.log("得到布局位置信息" + JSON.stringify(data));
          // console.log("节点离页面顶部的距离为" + data.top);
         const res= topArr.findIndex((item)=>{
            return item===data.top
          })
          if(res===-1){
            topArr.push(data.top)
          }
        }).exec();
      }
      setTimeout(()=>{
        this.topArr=[...topArr]
      },500)
    },
    // 判断在数组范围内的下标 并返回下标 控制左侧标题
    getIndex(value,arr){
      return arr.findIndex((item)=>{
        // console.log(item,value)
        return value>=(item-300) && value<=(item)
      })
    },
    // 判断在数组范围内的下标 并返回下标 右侧标题定位头部
    getPositionIndex(value,arr){
      return arr.findIndex((item,index)=>{
        return value>=(item-50) && value<=(item+(arr[index+1]-arr[index]-40))
      })
    },
    // 判断在数组范围内的下标 并返回下标 右侧标题定位头部
    getBeforePositionIndex(value,arr){
      return arr.findIndex((item,index)=>{
        return value>=(item-60) && value<=(item+(arr[index+1]-arr[index]-60))
      })
    },
    handleswitch(ite){
      uni.navigateTo({
        url:`${ite.actionTarget}`
      })
    }
  }
};
</script>

<style  lang="scss">
  $fontfamily:'font-siyuan';

  .toolkit-container{
    height: calc(100vh - 60px);
    .containerbox{
       display:flex;
       width:100%;
       height:92%;
       .containerleft{
         width:38%;
         height:100%;
        background: #F8F8F8;
         .item{
           width: 100%;
           height: 70px;
           p{
             width:100%;
             text-align:center;
             height:70px;
             font-size: 16px;
             font-family: $fontfamily;
             font-weight: bold;
             color: #858585;
             line-height: 70px;
           }
         }
         .active{
           background: #FFFFFF;
           color: #000000;
           position:relative;
           p{
             color: #333;
           }
         }
         .active::after{
          content: '';
          left: 0;
          top: 0;
          position: absolute;
           width: 3px;
           height: 70px;
           background: #1D51FD;
         }
       }
       .containerright{
         width:62%;
         height:100%;
         background-color:#FFFFFF;
         position: relative;
         padding-top: 19px;
         .contentitem{
           width:100%;
           position: relative;
           .title{
             width: 62vw;
             height: 20px;
             font-size: 14px;
            font-family: PingFangSC-Regular, PingFang SC;
             font-weight: 400;
             color: #999;
             line-height: 20px;
             padding-left: 18px;
             // background-color: #F8F8F8;
           }
           .positiontop{
              position: fixed;
              top: 19px;
              right: 0;
              background-color: #FFFFFF;
              opacity: 1;
              z-index: 99;
           }
           .menubox{
                display: flex;
                flex-wrap: wrap;
                padding: 23px 38px 22px 30px;
                 justify-content: space-between;
              .menuitem{
                flex-shrink: 1;
                margin-bottom: 23px;
                margin-right: 10px;
                display: flex;
                flex-direction: column;
                align-items: center;
                justify-content: center;
                .icon{
                  display: block;
                  width: 47px;
                  height: 47px;
                  margin-bottom: 8px;
                }
                p{
                  text-align: center;
                  height: 17px;
                  font-size: 12px;
                 font-family: PingFangSC-Regular, PingFang SC;
                  font-weight: 500;
                  color: #333333;
                  line-height: 17px;
                }
              }
           }
         }
       }
    }
    .footer{
      display: flex;
      align-items: center;
      justify-content: center;
      width:100%;
      height:8%;
      font-size: 14px;
     font-family: PingFangSC-Regular, PingFang SC;
      font-weight: bold;
      color: #BFBFBF;
      text-align: center;
    }
  }
</style>

复制代码

おすすめ

転載: juejin.im/post/7031840775881097229