需求背景
商品详情页往往有基础信息和较多的图文信息,需要分屏加载优化。(其实是因为UED想模仿天猫的加载方式-0-)
框架选择
第一次我选择了mint-ui(项目已引入的框架)的loadmore组件,阻尼切换的方式依靠手指对内容页的拖动阈值判断切换。出现的交互问题是,当拖动的是在webview中的网页本身主体容器时,不能正常切换阻尼,此时的滑动对象体是网页容器而不是内容页。(参照下图理解)
滑动的问题看起来并不好解决,以另外思维方式来考虑实现方案,我们不用拖动阈值来做切换的判断条件而是以内容页滚动阈值呢?ok,使用better-scroll来实现阻尼。
具体实现
实现方案
需切换的两屏内容包含在一个总容器,每屏内容父级各为better-scroll对象,第一屏为上拉阻尼,第二屏为下拉阻尼,切换效果为向上/下滑出。下图为元素框架及交互示意图:
实现效果图
具体代码实现
(项目使用的是vue框架,具体实现并不复杂可自行修改适用)
- dom层
- {{item}}
{{upScrollLoad ? '↓释放' : '↑上拉'}}查看第二屏
- {{item}}
{{downScrollLoad ? '↑释放' : '↓下拉'}}查看第一屏
- css
.page-loadmore-wrapper { overflow-y: auto; }
.one-page,.two-page{ position: relative; }
.two-page-content{ width: 100%;background-color: #c3cc9e; }
.one-page-wapper,.two-page-wapper{ overflow-y: hidden; } /*需要注意的是bscroll对象的父容器需要添加overflow-y: hidden 否则滚动计算会出错*/
.bottom-text{ position: absolute;width: 100%;height: 40px;line-height: 40px;text-align: center;margin-bottom: -40px; }
.top-text{ position: absolute;width: 100%;height: 40px;line-height: 40px;text-align: center;margin-top: -40px;top: 0; }
.list-item {height: 50px;line-height: 50px;border-bottom: solid 1px #eee;text-align: center; }
.content{ padding: 0;margin: 0; }
- js
import BScroll from 'better-scroll';
export default {
data() {
return {
list: [],
data: [],
wrapperHeight: 0, //整体阻尼容器高度
upScroll: null, //上拉阻尼-第一屏
upScrollLoad: false, //上拉阻尼-达到阈值标志位
downScroll: null, //下拉阻尼-第二屏
downScrollLoad: false, //下拉阻尼-达到阈值标志位
towPageShowFlag: false,//第二屏显示与否
firstLoadData: false, //是否首次请求数据
pullThreshold: 70 //拖动阈值,默认70px
};
},
created() {
for (let i = 1; i <= 20; i++) {
this.list.push(i);
}
},
mounted() {
//页面可视区域作为整体阻尼容器高度
this.wrapperHeight = document.documentElement.clientHeight;
//注意:一定要在 nextTick 之后初始化阻尼
this.$nextTick(() => {
this.initScroll();
})
},
watch: {
//监听数据的变化,延时20ms后调用refresh方法重新计算,保证滚动效果正常
list() {
setTimeout(() => {
this.upScroll && this.upScroll.refresh()
}, 20)
},
data(){
setTimeout(() => {
this.downScroll && this.downScroll.refresh()
}, 20)
}
},
methods: {
//初始化阻尼
initScroll(){
this.initOnePageScroll();
this.initTwoPageScroll();
},
//初始化上拉阻尼
initOnePageScroll(){
this.upScroll = new BScroll(this.$refs.onePage, {
probeType: 2,
click: true
});
let overHeight = this.$refs.onePage.getElementsByClassName('one-page')[0].offsetHeight - this.wrapperHeight + this.pullThreshold;
this.upScroll.on('scroll', (pos) => {
//向上拖动达到阈值
if (pos.y <= -overHeight) {
this.upScrollLoad = true;
}else{
this.upScrollLoad = false;
}
});
//手指释放位检测,超过阈值则去到第二屏,否则弹回原位
this.upScroll.on('touchend', () => {
if (this.upScrollLoad == true) {
this.upScrollLoad = false;
this.pullUp();
}
});
},
//初始化下拉阻尼
initTwoPageScroll(){
this.downScroll = new BScroll(this.$refs.twoPage, {
probeType: 2,
click: true
});
this.downScroll.on('scroll', (pos) => {
//向下拖动达到阈值
if (pos.y > this.pullThreshold ) {
this.downScrollLoad = true;
}else{
this.downScrollLoad = false;
}
});
//手指释放位检测,超过阈值则回到第一屏,否则弹回原位
this.downScroll.on('touchend', () => {
if (this.downScrollLoad == true) {
this.pullDown();
}
});
},
//下拉-回到第一屏
pullDown() {
this.animate(document.getElementById('pageContent'),{marginTop: 0},10,0.2,() => {
this.towPageShowFlag = false;
});
},
//上拉-去到第二屏
pullUp() {
this.towPageShowFlag = true;
if (!this.firstLoadData) {
this.loadData();
}
this.$nextTick(()=>{
this.downScroll.refresh();
this.animate(document.getElementById('pageContent'),{marginTop: -this.wrapperHeight},10,0.2);
})
},
//加载第二屏数据
loadData() {
this.firstLoadData = true;
for (let i = 21; i <= 40; i++) {
this.data.push(i);
}
},
//动画
animate(obj, json, interval, sp, callback) {
obj.timer = null;
clearInterval(obj.timer);
obj.timer = setInterval(() => {
var flag = true;
for(var arr in json) {
var icur = parseInt(document.defaultView.getComputedStyle(obj, null)[arr]);
var speed = (json[arr] - icur) * sp;
speed = speed > 0 ? Math.ceil(speed): Math.floor(speed);
if(icur != json[arr]){
flag = false;
}
obj.style[arr] = icur + speed + "px";
}
if(flag){
clearInterval(obj.timer);
if(callback){
callback();
}
}
},interval);
}
}
};
PS
具体的实现可以参照代码去实践、修改,代码中已有注释说明。如果还是遇到难以解决的问题可以私信我,better-scroll中的刷新时机是需要注意的点。最后,如有疏漏不妥之处,望不吝赐教。