微信小程式-自定義菜單導航(實現樓梯效果)
設計初衷
在開發頁面時,往往需要實現,點擊頁面的導航菜單頁面滾動到相應位置,滾動頁面實現菜單選項的高亮。在html開發中,我們可以用到a標籤錨點實現,jq的動畫相結合實現類似效果。在框架中vant UI框架也為我們實現了這一效果。
微信小程式該如何實現??
效果展示
- 當菜單導航滾動到頁面頂部時,菜單吸頂
- 當點擊菜單按鈕時,切換到對應區域(過渡到該區域,有動畫效果)
- 當內容區滾動到某類區域時,對應區域的菜單按鈕高亮
設計思路
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。
屬性 | 類型 | 說明 |
---|---|---|
id | string | 節點的 ID |
dataset | Object | 節點的 dataset |
left | number | 節點的左邊界坐標 |
right | number | 節點的右邊界坐標 |
top | number | 節點的上邊界坐標 |
bottom | number | 節點的下邊界坐標 |
width | number | 節點的寬度 |
height | number | 節點的高度 |
④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:
屬性 | 類型 | 說明 |
---|---|---|
scrollTop | Number | 頁面在垂直方向已滾動的距離(單位px) |
注意:請只在需要的時候才在 page 中定義此方法,不要定義空方法。以減少不必要的事件派發對渲染層-邏輯層通訊的影響。 注意:請避免在 onPageScroll 中過於頻繁的執行 setData 等引起邏輯層-渲染層通訊的操作。尤其是每次傳輸大量數據,會影響通訊耗時。
2、切換到對應區域
- 記錄當前點擊的菜單並高亮
- 獲取每個區域初始距離頁面頂部距離
- 設置當前頁面滾動條滾動到的位置,設置過度時間
// 導航欄切換設置
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)
將頁面滾動到目標位置,支援選擇器和滾動距離兩種方式定位
屬性 | 類型 | 默認值 | 必填 | 說明 |
---|---|---|---|---|
scrollTop | number | 無 | 否 | 滾動到頁面的目標位置,單位 px |
duration | number | 300 | 否 | 滾動動畫的時長,單位 ms |
selector | string | 無 | 否 | 選擇器 2.7.3 |
success | function | 無 | 否 | 介面調用成功的回調函數 |
fail | function | 無 | 否 | 介面調用失敗的回調函數 |
complete | unction | 無 | 否 | 介面調用結束的回調函數(調用成功、失敗都會執行) |
3) 滾動到某類區域時,對應區域的菜單按鈕高亮
- 獲取初始時區域距離頂端距離
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() })
- 滾動監聽是否超過了該區域
// 監聽頁面滾動 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;
}