图标高亮_给你代码:小程序内容滚动与导航栏自动高亮联动

ac57ccf60ca1c787b92741e3758bbbd6.png

这是张非常好看的封面(小编:呵呵)

这个标题想了很久,

总感觉没有表达精准,

毕竟要做的东西逼格比较高,

不是只言片语所能概之。

没图不说话,一图胜千言:

ff808de92e164a1da3d27a4bed28e96e.gif

(一千文字已凑满,本篇完。)

催稿员:

bb5831f9a87789beb35aca0848c14895.gif

前言

在持续迭代我们的鲲豆荚小程序过程中,

需要开发一个分类导航浏览的功能:

  1. 点击左边分类,右边可滚动到对应分类区域;
  2. 上下滑动右边分类内容区域,左边对应的导航栏自动高亮;
  3. 滚动要尽可能流畅,卡的不要!

对于老司机来说,

这种两栏布局随手就是一挥:

// .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属性设置就解决了,

多么美妙的时刻!

103fbf40f7e0610b00e6026f4442aab1.png

我们填充一下数据,

看下效果:

// .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;}

64f298daf40f720dd978ff7e4f0fb0a5.gif

接下来我们开始右边的布局设计,

因为右边需要在上下滑动的时候

改变左边导航栏的选中分类,

那么需要监听滑动事件。

微信小程序里有个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; } } })}

我们先上效果吧:

e49ec6e52b6125aa4587022890b8ccb7.gif

呃……

确实是实现了滚动后左边导航栏随之变化,

但这种抑扬顿挫的表现手法稍显蛋疼啊,

好像有一股超自然的力量在将你往上提,

让你使出的力气大打折扣。

那么问题出在哪儿了呢?

还记得我们设置过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的三种方式

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值