使用场景:楼层之间的快速切换,锚点的跳转,点击字母滚动到相关的城市
原理解析
1.代码
1.1.wxml
<view class='container'>
<view class='left'>
<scroll-view class='leftScroll' scroll-y>
<block wx:for="{{list}}" wx:key="list">
<view bindtap='clickScroll' data-id="{{item}}">{{item}}</view>
</block>
</scroll-view>
</view>
<view class='right'>
<scroll-view class='rightScroll' scroll-y scroll-into-view="{{toView}}" scroll-with-animation="true">
<block wx:for="{{list}}" wx:key="list">
<view bindtap='clickScroll' id="{{item}}">{{item}}</view>
</block>
</scroll-view>
</view>
</view>
1.2.wxss
page{
width:100%;
height:100%;
}
.container{
flex-direction: row;
height:100%;
}
.left{
width:100px;
height:80%;
}
.left scroll-view{
height:100%;
}
.left view{
padding:10px;
text-align: center;
background-color:#0f0;
margin-bottom:10px;
color:#fff;
}
.right{
flex:1;
height:80%;
}
.right scroll-view{
height:100%;
}
.right view{
height:150px;
background-color:#f00;
color:#fff;
margin-bottom:10px;
}
/* 隐藏滚动条的 */
::-webkit-scrollbar{
width:0;
height:0;
color:transparent;
}
1.3.js
// pages/maodian/maodian.js
Page({
/**
* 页面的初始数据
*/
data: {
list: ["list0", "list1", "list2", "list3", "list4", "list5", "list11", "list12", "list13", "list14", "list15", "list25", "list26", "list27", "list28", "list29", "list30"],
toView: ''
},
clickScroll: function(e) {
var id = e.currentTarget.dataset.id
this.setData({
toView: id
})
console.log(e.currentTarget.dataset);
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function() {
},
})
1.4.效果
2.应用举例
2.1wxml
<view class='head flex flexSb'>
<view class='headL flex'>
<view class='headL-name'>分类</view>
<view class='headL-sj'></view>
</view>
<view class='headR flex flexC alignC'>
<view class='headR-ss'></view>
<view class='headR-name'>我是顶部悬浮</view>
</view>
</view>
<view class='tab flex flexSb'>
<view class='tab-li rowAndColCenter {{index==tab_index?"on":""}}' wx:for="{{tab_arr}}" data-index='{{index}}' catchtap='tabClick'>
<view class='tab-li-wrap flex alignC'>
<view class='tab-line'></view>
<view class='tab-name'>{{item}}</view>
</view>
</view>
</view>
<view class='main'>
<scroll-view scroll-y style='height:{{ht}}px;' scroll-with-animation="false" bindscroll="scrollRight" scroll-into-view="{{toViewRt}}">
<block wx:for="{{tab_arr}}">
<view class='mainLi'>
<view class='mainLi-title {{index==tab_index?"on":""}}' id='t{{index}}'>{{item}}</view>
<view class='mainLi-li'>
<view class='mainLi-li-title'>我是副标题1</view>
<view class='flex alignC mt20'>
<view class='mainLi-li-li'>tip1</view>
<view class='mainLi-li-li'>tip2</view>
<view class='mainLi-li-li'>tip3</view>
</view>
</view>
<view class='mainLi-li'>
<view class='mainLi-li-title'>我是副标题2</view>
<view class='flex alignC mt20'>
<view class='mainLi-li-li'>tip1</view>
<view class='mainLi-li-li'>tip2</view>
</view>
</view>
</view>
<image wx:if='{{index==0}}' class='ad' src='http://demo.sc.chinaz.com/Files/DownLoad/webjs1/201801/jiaoben5647/img/5.jpg'></image>
</block>
</scroll-view>
</view>
2.2wxss
/* 这里是公共样式 */
.flex {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
display: box;
flex-wrap:wrap;
}
.flexC {
-webkit-box-pack: center;
justify-content: center;
-webkit-justify-content: center;
-moz-justify-content: center;
-ms-justify-content: center;
-o-justify-content: center;
}
.alignC{
align-items: center;
-webkit-box-align: center;
-webkit-align-items: center;
-moz-align-items: center;
-ms-align-items: center;
-o-align-items: center;
}
.flexSb {
justify-content: space-between;
-webkit-justify-content: space-between;
-moz-justify-content: space-between;
-ms-justify-content: space-between;
-o-justify-content: space-between;
}
.rowAndColCenter {
display: flex;
display: -webkit-flex;
display: -moz-flex;
flex-direction: column;
-webkit-flex-direction: column;
-moz-flex-direction: column;
justify-content: center;
-webkit-justify-content: center;
-moz-justify-content: center;
align-items: center;
-webkit-align-items: center;
-moz-align-items: center;
}
/* 页面样式 */
page {
background-color: #f2f2f2;
}
.head {
width: 100%;
padding: 15rpx 24rpx;
box-sizing: border-box;
background-color: #fff;
border-bottom: 1rpx solid #ddd;
position: fixed;
top: 0;
left: 0;
}
.headL {
width: 102rpx;
}
.headL, .headL-name, .headL-sj, .headR-name, .headR-ss {
height: 62rpx;
}
.headL-name {
line-height: 62rpx;
color: #ff7500;
font-size: 28rpx;
}
.headL-sj {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAHCAYAAAA8sqwkAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkE0RTY1QzZCMTE4NDExRUFBNDVEQUFFODJGMkU1NzU4IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkE0RTY1QzZDMTE4NDExRUFBNDVEQUFFODJGMkU1NzU4Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QTRFNjVDNjkxMTg0MTFFQUE0NURBQUU4MkYyRTU3NTgiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QTRFNjVDNkExMTg0MTFFQUE0NURBQUU4MkYyRTU3NTgiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7BouK/AAAAaUlEQVR42mL8X8pwh4GBQYiBOHCPEajBFcjYAsRsBBR/A2IfJiCxG4iTgPg/HsV/gTgSiPczQQWWAnE+DsUgg7KAeBOIw4QkMRmIO7BoqALiWTAOExbJ2fgMYcFifSYQiwDxV2zOBAgwAM8fFaNjssJvAAAAAElFTkSuQmCC);
background-repeat: no-repeat;
background-position: center;
background-size: 12rpx 7rpx;
width: 12rpx;
margin-left: 7rpx;
}
.headR {
width: 600rpx;
height: 62rpx;
background-color: #f1f1f1;
border-radius: 10rpx;
}
.headR-ss {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABsAAAAbCAYAAACN1PRVAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkVCREIyMURFMTE4NTExRUFCRUY1RTBDMEY5MzNBMEE4IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkVCREIyMURGMTE4NTExRUFCRUY1RTBDMEY5MzNBMEE4Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6RUJEQjIxREMxMTg1MTFFQUJFRjVFMEMwRjkzM0EwQTgiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6RUJEQjIxREQxMTg1MTFFQUJFRjVFMEMwRjkzM0EwQTgiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz6u60W0AAACZklEQVR42qyWXYhNURTHz0XJGCVRoybFvDBz75S8jLo+UhIaiQeR8kBT04RGMxpSShmfoQyRooh5kQchhRruzYOiJkMMEQ8uSl4oX+G36n9qz+mec09z96pfa+19zz3/s85eZ+2dKRQKQQprhMXQBDXwDQZhIJ/Pvw5S2riE3yZBG7RDQ9xFxWLxJe44omcriWViMtsAp2CyM/cMhuEdTIG5kHN+f28PhuitOLExZeYOwWUJfYAOmApZWAOdsAmaoU6Zl2AG3CTTnrRih2Gn4j6YBafhS8z/P5HJGV13XnMHENxdSawVuhXvha3wM83CI/gDNtvaaWo/gvPjxMxfUXwO9gWjMAR34MI1ux4ntgdq9bragupsHfyxdSa7jVExK//tGndWKWTZ2Td4UMOOqFhOpfwPLgV+7KJ8C9nVuGLNiu97ErLsXlmlarggmpnZ08CvvZFvcsVqFf/2LPZLfsRr/Kp4vGexCfLfXbEhxTnPYjPlX7hig4oXwlgfKlTgbNw0DQvRzEoat3vKKrzPI313IzrIsbCJeshqOm6bhifKtauj2n2tMq9WqXdNvkRW/XFdf738WjgyyqysibeowrNJW8wNp9t3wYXITp0k0gC3Cbdo6i/UpzkW7IJe5xvp1XYxVEbHWtEqPVzYGIadrrGUV3k36VhgRbIM3sJE2wjVyuyI8BgewBP4rLjLaXdzuHlW82Z3yLi10oEntG7tT/MSrnkI/Yj0RV7tAG6RhivtIJRJeW6sV0doVBuyyn1uzZabfExYy3u4JRquTiuWtJ1UKh47JiyPWzOvxsOswJ20Q9R/AQYAYUSpQ57DGdAAAAAASUVORK5CYII=);
background-repeat: no-repeat;
background-position: center;
background-size: 27rpx 27rpx;
width: 27rpx;
margin-right: 22rpx;
}
.headR-name {
line-height: 62rpx;
color: #b5b5b5;
font-size: 26rpx;
}
.tab {
width: 180rpx;
padding-top: 30rpx;
background-color: #fff;
position: fixed;
left: 0;
top: 93rpx;
}
.tab-li {
width: 180rpx;
height: 104rpx;
}
.tab-line {
width: 8rpx;
height: 18rpx;
margin-right: 14rpx;
}
.tab-li.on {
background-color: #f2f2f2;
color: #ff7500;
border-radius: 10rpx;
}
.tab-li.on .tab-line {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAASCAYAAABmQp92AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkNEREJBODUxMTE4NzExRUE5NkUyOEUxMUQ2MUUyRUU2IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkNEREJBODUyMTE4NzExRUE5NkUyOEUxMUQ2MUUyRUU2Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6Q0REQkE4NEYxMTg3MTFFQTk2RTI4RTExRDYxRTJFRTYiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6Q0REQkE4NTAxMTg3MTFFQTk2RTI4RTExRDYxRTJFRTYiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7ciuujAAAAcUlEQVR42mL8X8oAAqpA3AHELgwQsAeIK4D4NiNQgTqQcQKIBRhQwQcgtmACEm1YJBmgYm1MSMZiA24gBXx4FPAwMRAAowoQCj7hkf/CBI1aXGAXSEE1NGoZsER3NUjBDVC8A/E6IP4MxeugYjcAAgwAn/0VlirT1WUAAAAASUVORK5CYII=);
background-size: 8rpx 18rpx;
background-position: center;
background-repeat: no-repeat;
width: 8rpx;
height: 18rpx;
}
.main {
position: fixed;
top: 123rpx;
right: 15rpx;
}
.mainLi {
background-color: #fff;
border-radius: 10rpx;
padding: 16rpx 32rpx 10rpx;
width: 528rpx;
box-sizing: border-box;
margin-bottom: 24rpx;
}
.mainLi-title {
font-size: 34rpx;
color: #212121;
height: 60rpx;
line-height: 60rpx;
}
.mainLi-title.on{
color: #ff7500;
font-weight: bold;
}
.mainLi-li {
padding: 25rpx 0;
border-bottom: 1rpx solid #ddd;
}
.mainLi-li:last-child {
border-bottom: 0;
}
.mainLi-li-title {
font-size: 26rpx;
color: #404040;
}
.mainLi-li-li {
font-size: 24rpx;
color: #606266;
height: 53rpx;
line-height: 53rpx;
padding: 0 20rpx;
background-color: #f6f7f9;
border-radius: 5rpx;
margin-right: 13rpx;
}
.mainLi-li-li:last-child {
margin-right: 0;
}
.mt20 {
margin-top: 20rpx;
}
.ad {
display: block;
width: 100%;
height: 134rpx;
margin-bottom: 24rpx;
}
/* 去除滚动条 */
::-webkit-scrollbar{
width: 0;
height: 0;
color: transparent;
}
2.3j
Page({
/**
* 页面的初始数据
*/
data: {
// 左侧楼层数据
tab_arr: ['我是tab1', '我是tab2', '我是tab3', '我是tab4', '我是tab5', '我是tab6', '我是tab7'],
// 记录当前点击到第几个了
tab_index: 0,
// 初始值
toViewRt: 't0',
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
var that = this;
// 滚动的高度
that.data.scrolltop = 0;
// 监听各个种类的距离顶部的高度
that.data.element = [];
// 控制是否使用scroll标签的滚动事件(防止点击后同时出发滚动事件而引起的回弹效果)
that.data.scrollFlag = true;
// 获取当前设备的宽高,设置scroll标签的高度
wx.getSystemInfo({
success: (res) => {
var windowHeight = res.windowHeight;
that.setData({
ht: (windowHeight - 70)
});
},
});
// 渲染完成后。计算各个元素的距离顶部的高度
that.getTitleTop();
},
// 点击tab
tabClick(e) {
var that = this;
var index = e.currentTarget.dataset.index;
that.data.scrollFlag = false;
if (that.data.tab_index == index) {
return false
} else {
that.setData({
tab_index: index,
toViewRt: 't' + index
}, () => {
setTimeout(() => {
that.data.scrollFlag = true;
}, 1000);
});
}
},
// 滚动右侧菜单
scrollRight(e) {
var that = this;
if (that.data.scrollFlag) {
that.data.scrolltop = e.detail.scrollTop;
var element = that.data.element;
var elementIndex = that.getTimeIndex(element, that.data.scrolltop);
that.setData({
tab_index: elementIndex
});
}
},
// 获取位置
getTitleTop(scrolltop = 0, cb = '') {
var that = this;
//获取导航的初始位置
const query = wx.createSelectorQuery()
query.selectAll('.mainLi-title').boundingClientRect();
query.exec(function (res) {
res = res[0];
var arr = [];
for (var i = 0; i < res.length; i++) {
arr.push(res[i].top + scrolltop - 70);
}
that.data.element = arr;
});
if (typeof cb == "function") {
cb(scrolltop);
}
},
// 判断在数组哪两项之间
getTimeIndex(timeArr, time) {
var timeIndex = -1;
for (var i = 0; i < timeArr.length; i++) {
if (timeArr[i] > time) {
timeIndex = i - 1;
break;
}
}
return timeIndex;
}
});
2.4效果
3.说明
3.1实现思路解析: 其实就是通过,scroll-view标签绑定scroll-into-view,给scroll-view标签内需要跳转的标签绑定一个id名字,点击时把scroll-into-view里的内容替换为对应的跳转的锚点名字即可;
3.2 当中有一些小的细节,例如滚动条的隐藏 ::-webkit-scrollbar{width: 0;height: 0;color: transparent;} ;
3.3 实例思路:首先请求数据当页面渲染完成后,获取页面各个元素距离顶部的高度。监听页面滚动距离页面顶部的高度,通过滚动高度和各个元素距离顶部的高度做一个比较,如果滚动距离在两个值之间就取小的。
3.4 在实例中会遇到这种情况,点击左侧快捷导航时,右侧的scroll标签会同时执行滚动事件,如果页面位于最顶部,点击最后一个快捷导航按钮,当页面滚动到最底部时页面会有回弹现象(表现感觉有点怪异,但逻辑是说得通的,点击执行滚动到最底部的动作,但在滚动时会执行滚动事件监听,到达最底部不满足滚动的条件,滚动事件将index重新设置为满足滚动的条件值)
3.5 针对3.4中的问题,我就想干脆来个优先级吧,点击我就不让你滚了,用一个变量控制一下,在2.3的代码中通过scrollFlag有体现,用户体验效果会好点。