better-scroll实现天猫客户端商详阻尼效果

需求背景

商品详情页往往有基础信息和较多的图文信息,需要分屏加载优化。(其实是因为UED想模仿天猫的加载方式-0-)

框架选择

第一次我选择了mint-ui(项目已引入的框架)的loadmore组件,阻尼切换的方式依靠手指对内容页的拖动阈值判断切换。出现的交互问题是,当拖动的是在webview中的网页本身主体容器时,不能正常切换阻尼,此时的滑动对象体是网页容器而不是内容页。(参照下图理解)


容器示意图

滑动的问题看起来并不好解决,以另外思维方式来考虑实现方案,我们不用拖动阈值来做切换的判断条件而是以内容页滚动阈值呢?ok,使用better-scroll来实现阻尼。

具体实现

实现方案

需切换的两屏内容包含在一个总容器,每屏内容父级各为better-scroll对象,第一屏为上拉阻尼,第二屏为下拉阻尼,切换效果为向上/下滑出。下图为元素框架及交互示意图:


交互示意图.png
实现效果图
效果图.gif
具体代码实现

(项目使用的是vue框架,具体实现并不复杂可自行修改适用)

  • dom层

  • 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中的刷新时机是需要注意的点。最后,如有疏漏不妥之处,望不吝赐教。

你可能感兴趣的:(better-scroll实现天猫客户端商详阻尼效果)