问题现象
这个问题是最近在优化小程序代码时发现的.
在 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>
总结
方法蠢是蠢了点, 但是好用啊.
而且万一哪天微信小程序修复了这个问题, 咱们的方案不会出问题, 替换起来也很简单.
来源: https://www.cnblogs.com/vvjiang/p/11265206.html