微信小程序-自定义菜单导航(实现楼梯效果)

设计初衷

在开发页面时,往往需要实现,点击页面的导航菜单页面滚动到相应位置,滚动页面实现菜单选项的高亮。在html开发中,我们可以用到a标签锚点实现,jq的动画相结合实现类似效果。在框架中vant UI框架也为我们实现了这一效果。

微信小程序该如何实现??

效果展示

  1. 当菜单导航滚动到页面顶部时,菜单吸顶
  2. 当点击菜单按钮时,切换到对应区域(过渡到该区域,有动画效果)
  3. 当内容区滚动到某类区域时,对应区域的菜单按钮高亮

设计思路

1、吸顶效果的实现
  • 获取菜单导航距离页面顶部距离wx.createSelectorQuery()
  • 页面滚动监听
  • 滚动距离与菜单初始位置值比较
1) 距离
const query = wx.createSelectorQuery()
query.select('.menu_nav').boundingClientRect(function(res) {
    let obj = {}
    if (res && res.top) {
        obj[item.attr] = parseInt(res.top)
    }
}).exec()

①wx.createSelectorQuery()
返回一个 SelectorQuery 对象实例。在自定义组件或包含自定义组件的页面中,应使用 this.createSelectorQuery() 来代替。

②SelectorQuery.select(string selector)
在当前页面下选择第一个匹配选择器 selector 的节点。返回一个 NodesRef 对象实例,可以用于获取节点信息。

selector 语法
selector类似于 CSS 的选择器,但仅支持下列语法。

  • ID选择器:#the-id
  • class选择器(可以连续指定多个):.a-class.another-class
  • 子元素选择器:.the-parent > .the-child
  • 后代选择器:.the-ancestor .the-descendant
  • 跨自定义组件的后代选择器:.the-ancestor >>> .the-descendant
  • 多选择器的并集:#a-node, .some-other-nodes

③NodesRef.boundingClientRect(function callback)
添加节点的布局位置的查询请求。相对于显示区域,以像素为单位。其功能类似于 DOM 的 getBoundingClientRect。返回 NodesRef 对应的 SelectorQuery。

属性类型说明
idstring节点的 ID
datasetObject节点的 dataset
leftnumber节点的左边界坐标
rightnumber节点的右边界坐标
topnumber节点的上边界坐标
bottomnumber节点的下边界坐标
widthnumber节点的宽度
heightnumber节点的高度

④SelectorQuery.exec(function callback)
执行所有的请求。请求结果按请求次序构成数组,在callback的第一个参数中返回。

2) 页面滚动监听
  • data中初始化--tabFixed=false(表示是否固定定位)
  • 滚动条滚动距离超过了菜单初始距离时,tabFixed=true开启定位
// 监听页面滚动
onPageScroll: function(e) {
    let hTop = parseInt(e.scrollTop)
        // 菜单是否需要定位到顶部
    if (hTop > this.data.menu_top) {
        this.setData({
            tabFixed: true
        })
    } else {
        this.setData({
            tabFixed: false
        })
    }
}

onPageScroll(Object object))
监听用户滑动页面事件。

参数 Object object:

属性类型说明
scrollTopNumber页面在垂直方向已滚动的距离(单位px)

注意:请只在需要的时候才在 page 中定义此方法,不要定义空方法。以减少不必要的事件派发对渲染层-逻辑层通信的影响。 注意:请避免在 onPageScroll 中过于频繁的执行 setData 等引起逻辑层-渲染层通信的操作。尤其是每次传输大量数据,会影响通信耗时。

2、切换到对应区域
  1. 记录当前点击的菜单并高亮
  2. 获取每个区域初始距离页面顶部距离
  3. 设置当前页面滚动条滚动到的位置,设置过度时间
// 导航栏切换设置
setSelectType(event) {
    let index = event.currentTarget.dataset.type
    this.setData({
        tabIndex: index,
    })
    let arr = ['panel1_top', 'panel2_top', 'panel3_top', 'panel4_top']
    let _this = this
    wx.pageScrollTo({
        scrollTop: _this.data[arr[index]],
        duration: 500
    })
},

wx.pageScrollTo(Object object)
将页面滚动到目标位置,支持选择器和滚动距离两种方式定位

属性类型默认值必填说明
scrollTopnumber滚动到页面的目标位置,单位 px
durationnumber300滚动动画的时长,单位 ms
selectorstring选择器 2.7.3
successfunction接口调用成功的回调函数
failfunction接口调用失败的回调函数
completeunction接口调用结束的回调函数(调用成功、失败都会执行)
3) 滚动到某类区域时,对应区域的菜单按钮高亮
  1. 获取初始时区域距离顶端距离
    let arr = [
             { name: '.menu-nav', attr: 'menu_top', addNum: 0 },
             { name: '.panel1', attr: 'panel1_top', addNum: 0 },
             { name: '.panel2', attr: 'panel2_top', addNum: 0 },
             { name: '.panel3', attr: 'panel3_top', addNum: 0 },
             { name: '.panel4', attr: 'panel4_top', addNum: 0 },
         ]
         arr.forEach((item, i) => {
             wx.createSelectorQuery().select(item.name).boundingClientRect(function(res) {
                 let obj = {}
                 if (res && res.top) {
                     obj[item.attr] = parseInt(res.top)
    
                     if (item.addNum) {
                         obj[item.attr] += item.addNum
                     }
                     that.setData({
                         ...obj
                     })
                 }
    
             }).exec()
    
         })
  2. 滚动监听是否超过了该区域
    // 监听页面滚动
     onPageScroll: function(e) {
         let hTop = parseInt(e.scrollTop)
         // 自动切换菜单
         let tab=0
          if (hTop >= (this.data['panel4_top'] - this.data.menu_top)) {
            tab=3
         }else if (hTop >= (this.data['panel3_top'] - this.data.menu_top)){
            tab=2
         }
         else if (hTop >= (this.data['panel2_top'] - this.data.menu_top)){
             tab=1
         }
         this.setData({
             tabIndex: tab,
         })
     },

完整代码

index.js

// pages/index/index.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
    tabIndex: 0, //当前处于那个菜单
    menuList: ['菜单1', '菜单2', '菜单3', '菜单4'], //导航菜单
    tabFixed: false, //是否定位
    // 初始页面距离顶部距离
    menu_top: 0,
    panel1_top: 0,
    panel2_top: 0,
    panel3_top: 0,
    panel4_top: 0,
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },
  onShow:function (options){
    this.getTopDistance()
  },
  // 获取距离页面顶部高度
  getTopDistance() {
    let that = this
    let arr = [{
        name: '.menu-nav',
        attr: 'menu_top',
        addNum: 0
      },
      {
        name: '.panel1',
        attr: 'panel1_top',
        addNum: 0
      },
      {
        name: '.panel2',
        attr: 'panel2_top',
        addNum: 0
      },
      {
        name: '.panel3',
        attr: 'panel3_top',
        addNum: 0
      },
      {
        name: '.panel4',
        attr: 'panel4_top',
        addNum: 0
      },
    ]
    arr.forEach((item, i) => {
      wx.createSelectorQuery().select(item.name).boundingClientRect(function (res) {
        let obj = {}
        if (res && res.top) {
          obj[item.attr] = parseInt(res.top)

          if (item.addNum) {
            obj[item.attr] += item.addNum
          }
          that.setData({
            ...obj
          })
        }

      }).exec()

    })
  },
  // 导航栏切换设置
  setSelectType(event) {
    let index = event.currentTarget.dataset.type
    this.setData({
      tabIndex: index,
    })
    let arr = ['panel1_top', 'panel2_top', 'panel3_top', 'panel4_top']
    let _this = this
    wx.pageScrollTo({
      scrollTop: _this.data[arr[index]],
      duration: 500
    })
  },
  // 监听页面滚动
  onPageScroll: function (e) {
    let hTop = parseInt(e.scrollTop)

    // 菜单是否需要定位到顶部
    if (hTop > this.data.menu_top) {
      this.setData({
        tabFixed: true
      })
    } else {
      this.setData({
        tabFixed: false
      })
    }

    // 自动切换菜单

    if (hTop >= (this.data['panel4_top'] - this.data.menu_top)) {
      this.setData({
        tabIndex: 3,
      })
    }else if (hTop >= (this.data['panel3_top'] - this.data.menu_top)){
      this.setData({
        tabIndex: 2,
      })
    }
    else if (hTop >= (this.data['panel2_top'] - this.data.menu_top)){
      this.setData({
        tabIndex: 1,
      })
    }else{
      this.setData({
        tabIndex: 0,
      })
    }
  },
})

index.wxml

<view class="Main">
    <view class="head">
        我是头部区域
    </view>
    <view class="{{tabFixed?'is-fixed':''}} menu-nav">
        <text wx:for="{{menuList}}" class="{{tabIndex==index?'is-select':''}}" bind:tap="setSelectType" data-type='{{index}}'>{{item}}</text>
        
    </view>
    <view class="content">
        <view class="panel1 panel">页面1</view>
        <view class="panel2 panel">页面2</view>
        <view class="panel3 panel">页面3</view>
        <view class="panel4 panel">页面4</view>
    </view>
</view>

index.wxss

.menu-nav {
  display: flex;
  align-items: center;
  justify-content: space-around;
  color: black;
  padding: 10px 0;
  width: 100%;
  background-color: white;
}

.is-select {
  color: red;
}

.head {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 40px;
  height: 120px;
  background-color: greenyellow;
}

.is-fixed {
  position: fixed;
  top: 0;
}

.panel {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
}

.panel1 {
  height: 800rpx;
  background-color: rebeccapurple;
}

.panel2 {
  height: 700rpx;
  background-color: blue;
}

.panel3 {
  height: 1000rpx;
  background-color: orange;
}

.panel4 {
  height: 1200rpx;
  background-color: pink;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值