这几天在开发这个锚点定位的需求本来感觉很简单,结果做起来发现很多坑,有的根本就注意不到,真的是很让人头大:下面是几个大坑
1、scroll-view的坑
- scroll-view要增加高度,有的时候计算高度赋值给scroll-view时,会不起效,这个时候直接给它赋值100VH 就可以了
- scroll-view在使用的时候,如果需要使用禁止滑动,需要直接 scroll-y直接删掉或者定义成非布尔值类型
2、wx.createSelectorQuery()获取值不正确的问题
因为做的类似与京东的商品详情页,那么可定会出现图片以及富文本解析显示的。如果这个时候我们使用小程序自带的生命周期(onLoad、onShow、onReady),你会发现我们获取的节点信息要么不正确要么为null,这个全靠脸。
之后我就想着看能不能做一个延迟在onReady中去获取,这个时候还是会出现获取值不正确的问题,因为图片的一般我们都是自适应的,没有给他固定高度,由于不确定性这个方法也被淘汰了。
最后,请教了我们公司的同事,我们发现在小程序onReady之后,富文本解析的内容以及商品图片未必渲染完成,所以就出现上面那种情况。所以这个时候就想这个节点获取要放到图片渲染完毕以及富文本解析渲染之后。然后我们就找到了mp-html组件:
组件地址:mp-html: 小程序富文本组件,支持渲染和编辑 html,支持在微信、QQ、百度、支付宝、头条和 uni-app 平台使用 (gitee.com)
这个里面具备了渲染解析后的监听,具体的大家可以看一下。
3、锚点位置位于scroll-view 最下方,也就是锚点位置过低不能到达顶部的情况
界面上肯定会出现这种情况,就是最后一个或者最后几个锚点的位置过低,不能根据点击使锚点到达顶部的情况。这个如果单独使用没什么问题,但是如果你配合scroll-view的滑动去改变tab的状态时就会出现问题,它会回弹,这就很让人头大,最后只能计算,再配合scroll-view 的binddragend使用,具体的处理方法一会在代码里我会说明
4、锚点获取紧挨这tab
这个是界面上的问题,我发现如果我给这个view增加了margin-top,而它之上的盒子增加了一个margin-bottom时,锚点点击定位的点就是这个margin-bottom 的点,所以会出现这种紧挨着的问题。这个时候只需要把这个锚点盒子的margin改成padding就可以了
好了接下来就是代码了:(需要锚点的界面index.wxml)
<!-- 这是tab的组件 -->
<view class="navigateBox flexCenter" wx:if="{
{isHidden}}"><nav-tabs list="{
{title}}" active="{
{activeIndex}}" customStyle="width:560rpx;margin:auto;" bind:change="jumpTo"></nav-tabs>
</view><scroll-view style="width:100%;height:100vh" scroll-into-view="{
{toView}}" scroll-y="{
{isScroll}}" scroll-with-animation="true" enhanced="true" bindscroll="toScroll"binddragend="endScroll" ><view id="product"><!-- 内容按需求 --></view><view id="store"><!-- 内容按需求 --></view><view id="info"><!--mp-html为富文本解析器,可以按照上面的路径去下载使用 --><mp-html content="{
{couponDetail.content}}" bindready="mpHtmlReady"/></view></scroll-view >
这里是需要锚点的index.js文件
data:{toView: '',activeIndex: 0,title: [{ name: "商品", opt: 'product', type: 0 }, { name: "门店", opt: 'store', type: 1 }, { name: "商品详情", opt: 'info', type: 2 }],isHidden: false,scrollLock:false,//滑动枷锁isScroll:'',
},...//富文本解析渲染完成
mpHtmlReady(e){this.slideAnchor();
},//获取锚点的节点信息
slideAnchor(e){new Promise(resolve => {let query = wx.createSelectorQuery().in(this);query.select('#product').boundingClientRect();query.select('#store').boundingClientRect();query.select('#info').boundingClientRect();query.exec(function (res) {resolve(res);});}).then(res => {const windowHeight = wx.getSystemInfoSync().windowHeight;let heightArray= [],topArray= [];res.forEach(rect=> {heightArray.push(Math.floor(rect.top)); topArray.push(rect.height)});this.setData({scrollHeight:windowHeight ,heightArray,topArray,scrollLock:true, isScroll:true, //在富文本未解析之前,先禁止界面滑动});});
},//点击锚点跳转jumpTo: function (e) {let target = e.detail.item.opt;let activeIndex = e.detail.item.type;let {topArray,scrollHeight,isHidden} = this.data;let numHeight =0;//计算当前锚点是否能根据tab的点击至顶部for(var i =activeIndex; i<topArray.length;i++){numHeight += topArray[i]}isHidden = target=='product'?false:isHidden;this.setData({toView: target,activeIndex,isHidden,scrollLock:numHeight>=scrollHeight?true:false //如果界面出现锚点位置过低的情况防止tab的active回弹})},//scroll-view滚动监听事件toScroll: function (e) {const { heightArray,scrollLock,toView} = this.data;let scrollTop = e.detail.scrollTop;let isHidden = scrollTop > 20 ? true : false; //控制tab显示与隐藏if (this.data.isHidden != isHidden) {this.setData({ isHidden, toView: '', activeIndex:0, })}//锚点高度足够时,姐买你滑动到相应的位置,tab的active发生相应的改变if(scrollLock){let len = heightArray.length;let lastIndex = len - 1;let activeIndex = 0;for (let i = 0; i < len; i++) {if (scrollTop >= 0 && scrollTop < heightArray[0]) {activeIndex = 0;} else if (scrollTop >= heightArray[i] && scrollTop < heightArray[i + 1]) {activeIndex = i;}else if(scrollTop>=heightArray[lastIndex]){activeIndex=lastIndex;} }this.setData({ activeIndex })}},//停止滚动,防止锚点位置过低,界面滚动时无效的情况endScroll(e){this.setData({scrollLock:true});},
这里是需要锚点的index.wxss文件
.navigateBox{width:100%;position: sticky;height:92rpx;background:#fff;z-index:8;top:0rpx;left:0rpx;border-top:2rpx solid #F2F2F2;}
.navigateBox .menus {position: relative;display: inline-block;height: 92rpx;line-height: 92rpx;width: auto;padding: 0 24rpx;font-size: 28rpx;color: #7F7F7F;
}
这个是nav-tabs组件的.wxml
<!--customStyle 自定义css样式 scrollable 是否横向滚动lineOffsetLeft 当前选择的线条偏移量lineWidth 线条的宽度scrollable 是否滚动
--><view class="cl-tabs" style="{
{customStyle}}"><scroll-view class="scroll-view scrollHeight" scroll-x="{
{ scrollable }}" scroll-with-animation scroll-left="{
{ scrollLeft }}"><view class="cl-tabs-nav {
{scrollable ? 'cl-tabs-scroll' : 'cl-tabs-noscroll'}}"><view class="cl-tabs__line"style="width:{
{lineWidth}}px;transform:translateX({
{lineOffsetLeft}}px);-webkit-transform:translateX({
{lineOffsetLeft}}px);transition-duration:0.3s;-webkit-transition-duration:0.3s"></view><view wx:for="{
{list}}" data-index="{
{index}}" class="cl-tab {
{currentIndex==index ? 'cl-tab-active' : ''}}" data-item="{
{item}}" data-opt="{
{item.opt}}" catchtap="swich"><text>{
{item.name}}</text></view></view></scroll-view>
</view>
这个是nav-tabs组件的.js
const tools = require('../../utils/tools');
Component({properties: {list: {type: Array,value: []},customStyle: {type: String,value: ''},active:{type:Number,value:0,observer(val) {this.change(val);}},scrollable: {type: Boolean,value: true}},data: {scrollLeft: 0,currentIndex: 0,lineOffsetLeft: 0,lineWidth: 14},lifetimes: {//在组件在视图层布局完成后执行ready: function () {let that = this;this.setData({currentIndex:this.data.active,scrollable: this.data.list.length <= 5 ? false : true}, function () {const currentIndex = that.data.currentIndex;Promise.all([tools.getAllRect(this, '.cl-tab'),tools.getRect(this, '.cl-tabs__line'),]).then(([rects = [], lineRect]) => {const rect = rects[currentIndex];if (rect == null) {return;}let lineOffsetLeft = rects.slice(0, currentIndex).reduce((prev, curr) => prev + curr.width, 0);lineOffsetLeft += (rect.width - lineRect.width) / 2;that.setData({lineOffsetLeft});});});}},methods: {//切换组件swich: function (e) {const { data } = this;const currentIndex = e.currentTarget.dataset.index;const item = e.currentTarget.dataset.item;if (currentIndex === data.currentIndex) {return;}const shouldEmitChange = data.currentIndex !== null;this.setData({ currentIndex });tools.nextTick(() => {this.resize(false);this.scrollIntoView();if (shouldEmitChange) {//绑定事件到changethis.triggerEvent('change', {index: currentIndex,item:item})}});},change:function(index){this.setData({currentIndex:index},()=>{this.resize(false);this.scrollIntoView(); })},resize() {const { currentIndex } = this.data;Promise.all([tools.getAllRect(this, '.cl-tab'),tools.getRect(this, '.cl-tabs__line'),]).then(([rects = [], lineRect]) => {const rect = rects[currentIndex];if (rect == null) {return;}let lineOffsetLeft = rects.slice(0, currentIndex).reduce((prev, curr) => prev + curr.width, 0);lineOffsetLeft += (rect.width - lineRect.width) / 2;this.setData({lineOffsetLeft});});},scrollIntoView() {const { currentIndex, scrollable } = this.data;if (!scrollable) {return;}Promise.all([tools.getAllRect(this, '.cl-tab'),tools.getRect(this, '.cl-tabs-nav'),]).then(([tabRects, navRect]) => {const tabRect = tabRects[currentIndex];const offsetLeft = tabRects.slice(0, currentIndex).reduce((prev, curr) => prev + curr.width, 0);this.setData({scrollLeft: offsetLeft - (navRect.width - tabRect.width) / 2,});});},}
})<!-- 其中tools的内容 -->
<!--const nextTick=cb=> {if (wx.canIUse('nextTick')) {wx.nextTick(cb);}else {setTimeout(() => {cb();}, 1000 / 30);}}const getRect=(context, selector) =>{return new Promise((resolve) => {wx.createSelectorQuery().in(context).select(selector).boundingClientRect().exec((rect = []) => resolve(rect[0]));});}const getAllRect=(context, selector) =>{return new Promise((resolve) => {wx.createSelectorQuery().in(context).selectAll(selector).boundingClientRect().exec((rect = []) => resolve(rect[0]));});}module.exports = {nextTick,getRect,getAllRect}
-->
这个是nav-tabs组件的.wxss
.cl-tabs{background-color: #ffffff;
}.cl-tabs-scroll {position: relative;user-select: none;white-space: nowrap;
}
.cl-tabs-noscroll{display: -webkit-flex;display: flex;overflow: hidden;}.cl-tab {position: relative;min-width: 0;height:86rpx;width:150rpx;/* padding: 0 16rpx; */text-align: center;color: #7F7F7F;font-size: 30rpx;line-height: 86rpx;background:#fff;
}
.cl-tabs-scroll .cl-tab{display: inline-block;/* padding:0 24rpx; */
}
.cl-tab text{display: block;width:100%;height:86rpx;
}
.cl-tabs-noscroll .cl-tab{flex: 1;
}
.cl-tab:active{background:#fff!important;
}
.cl-tab-active{font-weight: bold;color:#333333;
}
.cl-tabs__line {position: absolute;bottom:14rpx;left: 0;z-index: 1;height: 6rpx;background: linear-gradient(90deg, #FF9973 0%, #FF4F4F 100%);border-radius: 6rpx;
}
.scrollHeight{height:90rpx;
}
以上就是这个功能的全部过程了,真的是很心酸。目前是这个情况,但是感觉代码还有有点不够简练,也希望各位大佬看看能不能改进优化一下,在此谢过。