移动端-vue-BScroll用法教程

移动端-vue-BScroll用法教程

简介

BetterScroll 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件,是最接近与原生的滚动插件,兼容pc和移动端。

官网

https://better-scroll.github.io/docs/zh-CN/

安装

javascript 复制代码
npm install better-scroll --save

使用

javascript 复制代码
import BScroll from "better-scroll";

let bs = new BScroll('.wrapper', {
   scrollbar: true,//是否显示滚动条
   bounce: true, //回弹动画
   click: true,//派发点击事件
   scrollX: false, //是否横向滚动
})
 bs.on("pullingDown", async () => {
   //下拉刷新      
   //当使用监听事件下拉刷新,但是pullingDown回调完函数,要在最后添加finishPullDown(); 
   // 作用是事情做完,需要调用此方法告诉    better-scroll 数据已加载,否则下拉事件只会执行一次
  bs.finishPullDown();
  bs&& bs.refresh(); //重新计算 BetterScroll
 });
 bs.on("pullingUp", async () => {
 //上拉加载
 //上拉同理与下拉,否则上拉事件只会执行一次。
     bs.finishPullUp();
     bs&&bs.refresh(); //重新计算 BetterScroll
 });

移动端滚动使用

javascript 复制代码
<template>
  <div
    class="scroll"
    ref="scrollRef"
  >
    <div class="scroll-wrap">
      <div
        class="pulldown"
        v-if="pullDownRefresh"
      >
        <div v-show="beforePullDown">
          <span>继续下拉以刷新</span>
        </div>
        <div v-show="!beforePullDown">
          <div v-show="isPullingDown">
            <span>加载中</span>
          </div>
          <div v-show="!isPullingDown">
            <span>加载完成</span>
          </div>
        </div>
      </div>
      <slot></slot>
      <div
        class="pullup"
        v-if="pullUpRefresh"
      >
        <div
          v-if="!isPullUpLoad && !loading"
          class="before-trigger"
        >
          <span class="pullup-txt">{{ finished ? "暂无更多数据" : "上拉加载更多数据" }}</span>
        </div>
        <div
          v-else
          class="after-trigger"
        >
          <span class="pullup-txt">加载中...</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, onMounted, nextTick, watch } from "vue";
import BScroll from "better-scroll";

export default defineComponent({
  name: "BetterScroll",
  props: {
    loading: {
      //加载
      type: Boolean,
      default: false,
    },
    refresh: {
      //是否刷新
      type: Boolean,
      default: false,
    },
    pullDownRefresh: {
      //是否下拉加载
      type: Boolean,
      default: false,
    },
    downRefresh: {
      //下拉加载方法
      type: Function,
      default: async () => {},
    },
    pullUpRefresh: {
      //是否上拉刷新
      type: Boolean,
      default: false,
    },
    upRefresh: {
      //上拉刷新方法
      type: Function,
      default: async () => {},
    },
    scrollX: {
      // 是否横向滚动
      type: Boolean,
      default: false,
    },
    finished: {
      // 是否滚动到底-数据加载完成
      type: Boolean,
      default: false,
    },
    bounce: {
      // 是否需要回弹动画 当滚动超过边缘的时候会有一小段回弹动画
      type: Boolean,
      default: true,
    },
  },
  setup(props, context) {
    const scrollRef = ref<HTMLElement | null>(null); //滚动父盒子
    const bs = ref<BScroll | null>(null); //滚动值

    const beforePullDown = ref(true); //显示继续下拉以刷新
    const isPullingDown = ref(false); //显示下拉加载中
    const isPullUpLoad = ref(false); //显示上拉加载中
    watch(
      () => props.refresh,
      async () => {
        if (props.refresh) {
          await nextTick(); //视图更新后
          setTimeout(() => {
            bs.value && bs.value.refresh();
            // 新查询数据后重新计算 BetterScroll
          }, 500);
          context.emit("update:refresh", false);
          // 将refresh状态改变 达到监听refresh的目的 为了重新计算BetterScroll
        }
      }
    );
    // 完成下拉事件
    const finishPullDown = async () => {
      if (bs.value) {
        //当使用监听事件下拉刷新,但是pullingDown回调完函数,要在最后添加finishPullDown();
        // 作用是事情做完,需要调用此方法告诉    better-scroll 数据已加载,否则下拉事件只会执行一次
        bs.value.finishPullDown();
        bs.value && bs.value.refresh();
        setTimeout(() => {
          beforePullDown.value = true; //数据加载完成,显示初始上拉已刷新
        }, 100);
      }
    };
    onMounted(() => {
      if (scrollRef.value) {
        //Record的内部定义,接收两个泛型参数;Record后面的泛型就是对象键和值的类型
        //作用 :定义一个对象的 key 和 value 类型
        const options: Record<string, any> = {
          scrollbar: true, //是否展示滚动条
          bounce: props.bounce, //回弹动画
          click: true, //单独点击事件
          scrollX: props.scrollX, //当设置为 true 的时候,可以开启横向滚动。
        };
        if (props.pullDownRefresh) {
          // 当顶部下拉距离超过阈值 pullDownRefresh为true 执行pullingDown事件
          options.pullDownRefresh = true;
        }
        if (props.pullUpRefresh) {
          //当底部下拉距离超过阈值 pullUpLoad为true 执行pullingUp事件
          options.pullUpLoad = true;
        }
        bs.value = new BScroll(scrollRef.value, options); //创建实例
        bs.value.on("pullingDown", async () => {
          if (!props.pullDownRefresh) {
            //pullDownRefresh值为false 则不执行
            return;
          }
          beforePullDown.value = false; //显示加载中/加载完成
          isPullingDown.value = true; //加载中
          await props.downRefresh(); //下拉刷新数据 初始化数据
          isPullingDown.value = false; //加载完成
          finishPullDown();
        });
        bs.value.on("pullingUp", async () => {
          if (props.finished || !props.pullUpRefresh) {
            // 如果数据已加载完成,或pullUpRefresh为false 则不执行该事件
            return;
          }
          isPullUpLoad.value = true; //显示上拉加载
          await props.upRefresh(); //上拉获取数据
          if (bs.value) {
            //当使用监听事件下拉刷新,但是pullingUp回调完函数,要在最后添加finishPullUp();
            // 作用是事情做完,需要调用此方法告诉better-scroll 数据已加载,否则上拉事件只会执行一次
            bs.value.finishPullUp();
            bs.value.refresh();
          }
          isPullUpLoad.value = false;
        });
      }
    });
    return {
      scrollRef,
      isPullingDown,
      isPullUpLoad,
      beforePullDown,
    };
  },
});
</script>

<style lang="less" scoped>
.scroll {
  overflow: hidden;
  width: 100%;
  height: 100%;
  position: relative;
  z-index: 2;
}
.pulldown {
  position: absolute;
  width: 100%;
  padding: 20px;
  box-sizing: border-box;
  transform: translateY(-100%) translateZ(0);
  text-align: center;
  color: #999;
}
.pullup {
  padding: 20px;
  text-align: center;
  color: #999;
}
</style>

移动端联合滚动实现懒加载

javascript 复制代码
<!-- 
 loading:加载
 pullDownRefresh:是否下拉刷新
 pullUpRefresh:是否上拉加载
 refresh:是否改变刷新状态-用于强制重新计算BetterScroll值
 finished:是否完成状态
 downRefresh:下拉刷新事件-分页为一
 upRefresh:上拉加载事件-分页加一
 -->

<template>
  <better-scroll
    :loading="loading"
    :pullDownRefresh="true"
    :pullUpRefresh="true"
    v-model:refresh="isRefresh"
    :finished="finished"
    :downRefresh="downRefresh"
    :upRefresh="upRefresh"
  >
    <template
      v-for="(item, index) in data"
      :key="index"
    >
      <slot
        name="content"
        :data="item"
        :index="index"
      ></slot>
    </template>
  </better-scroll>
</template>

<script lang="ts">
import { defineComponent, ref, watch, reactive, computed, SetupContext } from "vue";
import betterScroll from "./betterScroll.vue";
interface PropsType {
  order: string;
  apiService: (query: any, url?: string) => Promise<any>;
  query: object;
  refresh: boolean;
}

interface PageQueryType {
  currentPage: number;
  pageSize: number;
}

export default defineComponent({
  name: "scrollList",
  components: {
    betterScroll,
  },
  props: {
    // 数据调用接口
    apiService: {
      type: Function,
      default: new Promise(() => {}),
    },
    // 传参
    query: {
      type: [Object, Array],
      default: () => {},
    },
    // 是否更新BetterScroll位置
    refresh: {
      type: Boolean,
      default: false,
    },
    // 排序字段
    orderBy: {
      type: String,
      default: "captureDate",
    },
  },
  setup(props, context) {
    const { data, isRefresh, finished, downRefresh, upRefresh } = useSearch(props, context);

    return {
      data,
      isRefresh,
      finished,
      upRefresh,
      downRefresh,
    };
  },
});

// 搜索
function useSearch(props: PropsType, context: SetupContext) {
  //context:SetupContext,即是setup函数的上下文 用于获取props
  const page = reactive<PageQueryType>({
    currentPage: 1,
    pageSize: 10,
  }); //初始化分页

  const data = ref([]); //数据
  const totalNum = ref<Nullable<number>>(null); //数据总条数

  const finished = computed(() => {
    return totalNum.value !== null ? data.value.length >= totalNum.value : false;
  }); //通过数据长度和总数据条数判断是否加载完
  const queryTimer = ref<Nullable<NodeJS.Timer>>(null);

  watch(
    () => props.query, //监听到查询条件
    async () => {
      isRefresh.value = true; //isRefresh设为true 因为有查询条件会重新加载数据 需要重新计算BetterScroll 使数据回到顶部位置

      // 采用防抖执行下拉刷新事件 多次快速查询只触发最后一个事件
      if (queryTimer.value) {
        clearTimeout(queryTimer.value);
        queryTimer.value = null;
      }
      queryTimer.value = setTimeout(async () => {
        data.value = [];
        await downRefresh();
      }, 50);
    },
    {
      deep: true,
    }
  );
  watch(
    () => props.refresh,
    async () => {
      isRefresh.value = true;
      if (props.refresh) {
        data.value = [];
        context.emit("update:refresh", false);
        await downRefresh();
      }
    }
  );

  // 下拉刷新事件
  const downRefresh = async () => {
    page.currentPage = 1;
    totalNum.value = null;
    data.value = await getData(props.query);
    return finished.value; //重新更新finished状态
  };
  // 上拉加载事件
  const upRefresh = async () => {
    page.currentPage += 1;
    const resData = await getData(props.query);
    data.value = data.value.concat(resData);
    return finished.value; //重新更新finished状态
  };
  const isRefresh = ref(false);
  // 获取数据
  const getData = async (query: object) => {
    try {
      const { data, total } = await props.apiService({
        ...page,
        ...query,
      });
      totalNum.value = total;
      isRefresh.value = true;
      return data || [];
    } catch (e) {
      totalNum.value = 0;
      isRefresh.value = true;
      return [];
    }
  };
  queryTimer.value = setTimeout(async () => {
    downRefresh();
  }, 50);
  return {
    data,
    isRefresh,
    finished,
    upRefresh,
    downRefresh,
  };
}
</script>

<style lang="less" scoped>
.scroll {
  height: 100%;
}
</style>

页面使用

javascript 复制代码
import scrollList from "./scrollList.vue";
....
<scroll-list
    :apiService="请求方法"
    :query="query"
  >
    <template #content="{ data }">
      <!-- 内容们 -->
    </template>
  <scroll-list>

扩展-什么是防抖和节流

相同点

防抖和节流都是为了阻止操作高频触发,从而浪费性能。

区别

javascript 复制代码
// 防抖是触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。
//适用于可以多次触发但触发只生效最后一次的场景。
//如王者的回城,在回城中途可能会被频繁打断,但是只有经历完整5s,也就是最后一次能够回城成功。
function debounce(fn, delay){
	let timer = null;
	return function(){
		clearTimeout(timer);
		timer = setTimeout(()=> {
			fn.apply(this, arguments);
		}, delay)
	}
}
javascript 复制代码
//节流是高频事件触发,但在n秒内只会执行一次,如果n秒内触发多次函数,只有一次生效,节流会稀释函数的执行频率
//适用于可以在一段时间内触发只生效一次的场景。
//如王者每一个英雄在使用一个技能后,该技能都会有一个固定的冷却时间。冷却时间过后即可再次使用。在技能冷却时间,无论我们连续点击多少次都不会触发技能,而该技能只在单位时间内触发一次。
function throttling(fn, delay) {
  let timer = null;
  return (n) => {
    if (!timer) {
        timer = setTimeout(() => {
            fn.call(this, n);
            clearTimeout(timer)
            timer = null;
        }, delay)
    }
    }
}
相关推荐
m0_748235618 分钟前
从零开始学前端之HTML(三)
前端·html
一个处女座的程序猿O(∩_∩)O2 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink5 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者7 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-7 小时前
验证码机制
前端·后端
燃先生._.8 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖9 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235249 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_7482402510 小时前
前端如何检测用户登录状态是否过期
前端