这个标题想了很久,
总感觉没有表达精准,
毕竟要做的东西逼格比较高,
不是只言片语所能概之。
没图不说话,一图胜千言:
(一千文字已凑满,本篇完。)
催稿员:
前言
在持续迭代我们的鲲豆荚小程序过程中,
需要开发一个分类导航浏览的功能:
- 点击左边分类,右边可滚动到对应分类区域;
- 上下滑动右边分类内容区域,左边对应的导航栏自动高亮;
- 滚动要尽可能流畅,卡的不要!
对于老司机来说,
这种两栏布局随手就是一挥:
// .wxml
对于左边导航栏部分,
我们需要它在垂直方向占满屏幕,
超出部分自动出现滚动条,
以此写出样式:
.left{width:120px;border-right:1px solid #eee;height:100vh;overflow: auto;}
如果不希望在滑动的时候出现滚动条:
.left::-webkit-scrollbar{width: 0;}
请稍微留意一下这里
有一个很重要的属性设置height:100vh。
相信很多和我一样,
对于前端所用的解决方案都停留在古老的十年之前。
——十年之后,不管你是前端还是后端,有幸的是你还在敲着代码,正好看见了这篇文章,握个手喝杯浊酒流下纵横的老泪吧。——
看见vh是一脸懵逼:
vh是什么单位?
有编制吗?
享受国家待遇么?
这里一起科普和学习下:
vh,英文全称viewpoint height,
指相对于可视窗口的高度。
视口被均分为100单位的vh,
1vh 等于1/100的可视高度。
比如浏览器可视高度为800px,
1 vh = 800px/100 = 8px。
以前需要通过js去动态获取可视高度
然后再为div设置同等高度的解决方案,
现在只需要一个css属性设置就解决了,
多么美妙的时刻!
我们填充一下数据,
看下效果:
// .jsPage({ data: { icon: 'https://www.kunquer.com/favicon.ico', select_index: 0, list: [] }, onLoad(options) { let list = []; for(let i = 0; i < 20; i++) { list.push({ name: i, items: Array.from(Array(9), (v, k) => '鲲圭云计算') }); } this.setData({list: list}); }, selectIndex(e) { this.setData({ select_index: e.currentTarget.dataset.index }); },})
在wxml的left部分,
加入以下代码:
{{index}}
wxss部分追加以下样式:
.left .item{text-align:center;font-size:13px;padding:10px 0;}.left .item.active{color:#439BF7;font-size:14px;}
接下来我们开始右边的布局设计,
因为右边需要在上下滑动的时候
改变左边导航栏的选中分类,
那么需要监听滑动事件。
微信小程序里有个scroll-view组件
正好可以派上用场:
... {{item.name}}{{item}}
wxml部分:
.index-name{text-align: center;}.index-name::before{content: '——';color:#eee;margin-right:10px;}.index-name::after{content: '——';color:#eee;margin-left:10px;}.weui-grid__icon{border-radius:50%;width:48px;height:48px;}.weui-grids, .weui-grid{border:0;}
上面我们绑定了滚动事件交给scroll方法进行处理,
并且开启了滚动动画,
目的是不引起突兀感。
得益于scroll-into-view属性,
其绑定的id可动态改变当前滚动位置,
这样当我们点击切换左边导航栏时,
右边内容区域也随之变换定位。
但反过来就有点棘手了:
上下滑动右边内容区域,
左边导航栏选中分类也需要随之改变。
要实现这样的功能,
需要首先知道每类内容的区域范围,
通过监听当前滚动的top距离来计算出对应的分类。
要获取每个锚点的位置信息,
应该在界面渲染完毕的情况下进行,
这里我们放在onReady()中去处理:
// .jsPage({ ..., onReady() { wx.createSelectorQuery().selectAll('.index-name').boundingClientRect(results => { results.forEach(rect => { let key = 'list['+rect.dataset.id+'].top'; this.setData({[key]: rect.top}); }); }).exec(); }})
这样,
我们在list中为每个分类保存了一个top属性,
接下来我们在scroll()方法中识别出对应的分类:
scroll(e) { this.data.list.some((v, k) => { if(v.top <= e.detail.scrollTop) { if(this.data.list[k+1] && this.data.list[k+1].top > e.detail.scrollTop) { this.setData({select_index: k}); return true; } } })}
我们先上效果吧:
呃……
确实是实现了滚动后左边导航栏随之变化,
但这种抑扬顿挫的表现手法稍显蛋疼啊,
好像有一股超自然的力量在将你往上提,
让你使出的力气大打折扣。
那么问题出在哪儿了呢?
还记得我们设置过scroll-into-view属性吗?
它绑定了select_index,
当我们在scroll()方法中去修改select_index时,
这个组件又重新将我们拉回锚点的位置,
所以在滚动时就出现了抖动现象。
要解决这个问题,
其实只需要再设置一个变量,
让这个变量和select_index保持一致即可。
我们首先修改wxml部分,
将scroll-into-view条件依赖变量
由select_index变为active_index:
...
在selectIndex()方法中增加对active_index的同步修改:
selectIndex(e) { this.setData({ select_index: e.currentTarget.dataset.index, active_index: e.currentTarget.dataset.index });}
你以为这样就可以了吗?
测试中我们发现滑到底时,
最后一个分类永远不会被选中,
虽然这不是什么大问题,
本着精益求精的态度,
我们还是要解决一下下的。
当然,
这是上面的算法漏洞,
但好像也找不到更好的解决办法,
还好scroll-view提供了一个属性:
bindscrolltolower,
当滑动到最底部时将会触发调用它绑定的方法,
这样我们在scroll-view上增加这样一个属性:
...
bottom()中,
我们只需要将当前选中分类
强制设置为最后一个即可:
bottom(e) { this.setData({select_index: this.data.list.length-1});}
优化空间
虽然通过以上一步步最终达到了功能上的要求,
但是其实还有一个巨大的地方待优化:
当点击左边导航栏时,
发生了哪些事情呢?
由于改变了select_index,
那么右边scroll-view会立即定位到对应锚点,
这样就不可避免触发滚动事件,
由于我们监听了滚动事件,
这时候滚动事件又不停的修改select_index,
这部分的频繁更新操作是多余的。
但是滚动监听是必要的,
那可不可以在我们点击左边导航栏
到scroll-view完成定位这段时间
让监听滚动事件不处理?
不如设置一个时间死区?
就是当我们点击左边导航栏时设置一个变量block,
将其设置为true;
同时1秒时间后,
将其设置为false:
data: { .. block: false},selectIndex(e) { this.setData({ select_index: e.currentTarget.dataset.index, active_index: e.currentTarget.dataset.index, block: true }); setTimeout(() => this.setData({block: false}), 1000);}
目测1秒的时间足够scroll-view完成锚点定位操作了,
当然在这1秒内你滑动右边内容区域
是无法改变左边导航栏选中状态的,
这个时间死区长短由你决定,
你也可以设置成500ms,200ms。
然后我们在scroll()方法中增加条件检测,
只有当不是时间死区时才执行
更新select_index操作:
scroll(e) { this.data.block || this.data.list.some((v, k) => { if(v.top <= e.detail.scrollTop) { if(this.data.list[k+1] && this.data.list[k+1].top > e.detail.scrollTop) { this.setData({select_index: k}); return true; } } })}
结语
“小吉同学,我有个问题,你有什么最简单的办法能让div自适应屏幕的高度,保持100%占满吗?不要用js”
——“试试用100vh”
“卧槽真神踏马奇!这就是我想要的效果”
技术更替日新月异,
也难怪程序员35岁定律让人惶恐,
因为年纪大了,
思维运转比不上年轻人,
记忆在逐年衰退,
但学不懂是一回事,
学不动又是一回事。
不懂多问问就好了,
老话说得好:
活到老学到老,
学不动都只是借口,
只是停留在舒适区不想改变罢了,
借用某个名人的话:
其实我们离自己所以为的极限还有很远。
无须惶恐,
当下在努力前行,
至少心能安。
(小编:——干了这碗鸡汤!)
给你代码往期回顾:
给你代码:短链接生成原理
给你代码:网站图标favicon自动抓取
给你代码:小程序引入icon的三种方式