問題現象
這個問題是最近在優化小程序代碼時發現的。
在ios環境下,微信小程序的scrollview組件包裹着一個position:fixed的view。
當在scrollview組件上滑動時,這個view會瘋狂抖動。
直接上圖吧:
下面是簡化后的代碼:
<view class="main">
<scroll-view scroll-y="{{true}}" bindscroll="handleScroll" style="height:100%;" >
<view>
<view class="weui-navbar navbar-fixed">
我是頭部fixed元素
</view>
<view>
這里是一大段test文字,用於占位
</view>
</view>
</scroll-view>
</view>
猜測與驗證
原生組件?
這個現象在沒有簡化代碼前,我以為我是哪里用了什么原生組件。
因為原生組件在ios下的定位緩慢,導致了這個問題的出現。
但是當我的代碼簡化到上面這一步的時候,發現並沒有應用原生組件。
ios下橡皮筋功能的影響?
問題在於我去掉了scroll-view后,滾動得不錯,這個頭部fixed的元素並沒有抖動。
確定是scroll-view組件下fixed元素隨着滑動就會抖動
到這一步我就確定了問題的原因,所以當然我是要先百度一下答案的。
於是我果然發現了一堆難兄難弟:ios下scroll-view中fixed元素無法固定。
貌似他們遇到的問題比我還嚴重啊,都像一條咸魚一樣跟着滾了,而我的只是得了帕金森。
簡單場景解決方案
上面的問題還沒有官方人員回答。
不過最好的解決方案其實就是將fixed元素移出scroll-view,這個沒什么好多說的。
元素都fixed了,沒道理還要放在scroll-view中是吧?
復雜場景解決方案
既然說了上面是簡單場景,那么就肯定有復雜場景嘛。
我元素都fixed了,確實是沒道理要放在一個scroll-view元素中包裹着。
但是有的事就是這么沒道理啊。
就比如我的微信小程序肯定沒有示例這么簡單,里面這個fixed元素不能移出去。
因為這個元素的fixed狀態並不是固定的,最開始他需要跟隨頁面一起滾動,當和頂部貼緊后,它就變成fixed了。
廢話少說,現在就說一下我的解決方案的思路:
既然要隨着頁面一起滾動,那么肯定是要保證這個元素在scroll-view中的。
而scroll-view中的fixed元素肯定會抖,所以這個元素又一定要放在scroll-view外。
看似魚與熊掌不可兼得,實際上我們搞兩個人一人取魚,一人取熊掌就好了嘛。
我們可以在scroll-view外設置一個同樣的元素,並將其設置為fixed,並且隱藏。
當scroll-view內部的元素貼緊頂部后,將內部的元素隱藏,再顯示外部的元素即可。
以下是實現代碼:
index.wxml:
<view class="main">
<view class="navbar navbar-fixed" hidden="{{scrollTop<=initTop}}">
我是頭部fixed元素
</view>
<scroll-view scroll-y="{{true}}" bindscroll="handleScroll" style="height:100%;" >
<view>
<view>
這里是一大段test文字,用於占位
</view>
<view id="navbar" class="weui-navbar navbar-fixed" hidden="{{scrollTop>initTop}}">
我是頭部fixed元素
</view>
<view>
這里是一大段test文字,用於占位
</view>
</view>
</scroll-view>
</view>
index.js:
Page({
data: {
initTop: 0,
scrollTop: 0,
},
onLoad: function (options) {
let query = wx.createSelectorQuery()
query.select('#navbar').boundingClientRect()
query.exec((res) => {
this.setData({
initTop: res[0].top
})
})
},
handleScroll: function (e) {
this.setData({ scrollTop: e.detail.scrollTop })
}
})
index.wxss:
.navbar-fixed {
position:fixed;
width:100%;
top:0;
left:0;
z-index:100;
}
.navbar{
height:80rpx;
line-height: 80rpx;
background:red;
text-align: center;
color: #fff;
}
隱藏BUG與修復
以上代碼如果快速滑動是沒有問題,但是當紅色頭部元素快要貼緊頂部時慢速滑動就會出現一個很詭異的現象:
紅色頭部元素往下彈動,始終不能貼緊頂部。
而實際上不是紅色頭部元素往下彈動,而是紅色頭部元素貼緊頂部后,此時內部頭部元素隱藏,那么scrollTop立刻變小。
因為scrollTop變小,小於了initTop,那么內部頭部元素再次出現,於是就這樣不斷循環。
我們這里需要明白hidden實際上是一個display:none的效果,所以這里我們對內部元素的隱藏不能用hidden,而是用visibility:hidden。
這樣的話,這個內部元素就只是看不見了而已,並且頁面上顯示為背景色(這里我們假設是白色),但是還是占用了那么多的空間。
那么scrollTop就不會突然間變小,也就不會造成BUG。
同時,外部的元素會在內部元素變成白色矩形時直接出現,覆蓋在內部元素上面,那么內部元素隱藏所造成的白色區域實際上就被外部元素遮擋住了。
當用戶在使用時,完全不會感知到內部元素這個白色區域的存在。
好了,這里我們給出修改后的代碼:
<view class="main">
<view class="navbar navbar-fixed" hidden="{{scrollTop<=initTop}}">
我是頭部fixed元素
</view>
<scroll-view scroll-y="{{true}}" bindscroll="handleScroll" style="height:100%;" >
<view>
<view>
這里是一大段test文字,用於占位
</view>
<view id="navbar" class="weui-navbar navbar-fixed" style="visibility:{{scrollTop>initTop?'hidden':'initial'}}">
我是頭部fixed元素
</view>
<view>
這里是一大段test文字,用於占位
</view>
</view>
</scroll-view>
</view>
總結
方法蠢是蠢了點,但是好用啊。
而且萬一哪天微信小程序修復了這個問題,咱們的方案不會出問題,替換起來也很簡單。