Vue2 封装 better-scroll 插件,实现顺滑的滚动效果

目录

1. batter-scroll 原理

2. 初始化 batter-scroll 的正确姿势

3. better-scroll 注意事项

4. 在 Vue2 中使用 better-scroll

4.1 Vue2 中,初始化 better-scroll 的注意事项

4.2 better-scroll 请求异步数据

4.3 better-scroll 的封装

4.4 better-scroll 上拉加载下拉刷新



1. batter-scroll 原理

  • wrapper 是父容器,它会有固定的高度;
  • 父容器的第一个子元素为 content,它的高度会随着内容的大小而撑高;
  • 当 content 的高度不超过 wrapper 的高度,是不能滚动的;
  • 当 content 的高度超过了 wrapper 的高度,就可以滚动 content 了;

2. 初始化 batter-scroll 的正确姿势

<div class="wrapper">
  <ul class="content">
    <li>...</li>
    ...
  </ul>
</div>
 
// 初始化 better-scroll
import BScroll from 'better-scroll'
let wrapper = document.querySelector('.wrapper')
// 后面添加了插件配置项对象
let scroll = new BScroll(wrapper, {})

3. better-scroll 注意事项

  1. 初始化时机,初始化时,会计算父元素和子元素的高度和宽度,来决定是否可以滚动;因此,初始化 better-scroll 时,必须确保父元素和子元素的内容已经正确渲染了
  2. 如果子元素或者父元素 DOM 结构发生改变,必须重新调用 scroll.refresh() 方法重新计算,来确保滚动效果的正常

4. 在 Vue2 中使用 better-scroll

4.1 Vue2 中,初始化 better-scroll 的注意事项

  1. 在 mounted 函数中执行 better-scroll 初始化,此时 wrapper 的 DOM 已经渲染了
  2. 在 $nextTick 函数回调中执行 better-scroll 初始化,因为 better-scroll 依赖随数据变化而变化的 DOM
  3. this.$refs.wrapper 用于获取父元素 DOM

  • 初始化的小技巧:把 this.$nextTick 替换成 setTimeout(fn, 20) 也是可以的(20 ms 是一个经验值,每一个 Tick 约为 17 ms

  • Vue2 中,初始化 better-scroll 的示例代码:
<template>
  <div class="wrapper" ref="wrapper">
    <ul class="content">
      <li>...</li>
      ...
    </ul>
  </div>
</template>

<script>
  import BScroll from 'better-scroll'
  export default {
    mounted() {
      this.$nextTick(() => {
        this.scroll = new Bscroll(this.$refs.wrapper, {})
      })
    }
  }
</script>

4.2 better-scroll 请求异步数据

  • Vue 数据发生变化(this.data = res.data)到 页面重新渲染 是一个异步的过程
  • 初始化 better-scroll 要在 DOM 重新渲染后,也就是获取数据之后
  • 获取数据之后,数据就发生了变化,会让 dom也发生变化,而滚动条依赖于变化之后的 dom 元素,所以这里用到了 this.$nextTick 包裹初始化 better-scroll 代码,当然替换成 setTimeout(fn, 20) 也是可以的
<template>
  <div class="wrapper" ref="wrapper">
    <ul class="content">
      <li v-for="item in data">{{ item }}</li>
    </ul>
  </div>
</template>

<script>
  import BScroll from 'better-scroll'
  export default {
    data() {
      return {
        data: []
      }
    },
    created() {
      requestData().then((res) => {
        this.data = res.data
        this.$nextTick(() => {
          this.scroll = new Bscroll(this.$refs.wrapper, {})
        })
      })
    }
  }
</script>

  • 几点解释:
  • 异步请求数据在 created() 里进行,而不是放到 mounted() 的原因?
  • ① 异步请求数据是异步的过程,①执行完之后,没拿到数据的 dom 早就渲染好了;② 数据改变重新渲染 dom 也是异步的过程,所以异步拿到数据之后,再异步初始化 better-scroll 

4.3 better-scroll 的封装

  • html 结构部分的封装:
  • better-scroll 只需考虑父元素,内部多少层都没关系
  • 所以除了父元素,其他地方放插槽就行

  • js 逻辑部分的封装:
  • 通过 props ,把对 better-scroll 定制化的控制权,交给父组件
  • 通过 methods,代理 better-scroll 的方法
  • 通过 watch,监听传入的 data,data 改变,就 refresh

  • 加延时 refresh 的原因:如果给列表加了动画效果,那么 dom 渲染完成是动画完成之后
<template>
  <div ref="wrapper">
    <slot></slot>
  </div>
</template>

<script>
  import BScroll from "better-scroll";
  export default {
    props: {
      /**
       * 1 滚动的时候会派发 scroll事件,会截流。
       * 2 滚动的时候实时派发 scroll事件,不会截流。
       * 3 除了实时派发 scroll事件,在 swipe 的情况下仍然能实时派发 scroll事件
       */
      probeType: {
        type: Number,
        default: 1,
      },
      /**
       * 点击列表是否派发 click事件
       */
      click: {
        type: Boolean,
        default: true,
      },
      /**
       * 是否开启横向滚动
       */
      scrollX: {
        type: Boolean,
        default: false,
      },
      /**
       * 是否派发滚动事件
       */
      listenScroll: {
        type: Boolean,
        default: false,
      },
      /**
       * 列表的数据
       */
      data: {
        type: Array,
        default: null,
      },
      /**
       * 是否派发滚动到底部的事件,用于上拉加载
       */
      pullup: {
        type: Boolean,
        default: false,
      },
      /**
       * 是否派发顶部下拉的事件,用于下拉刷新
       */
      pulldown: {
        type: Boolean,
        default: false,
      },
      /**
       * 是否派发列表滚动开始的事件
       */
      beforeScroll: {
        type: Boolean,
        default: false,
      },
      /**
       * 当数据更新后,刷新scroll的延时
       */
      refreshDelay: {
        type: Number,
        default: 20,
      },
    },
    mounted() {
      // 保证在DOM渲染完毕后,初始化 better-scroll
      setTimeout(() => {
        this._initScroll();
      }, 20); // 模拟了点击速度 17ms 也可放入 nextTick
    },
    methods: {
      // 初始化 better-scroll
      _initScroll() {
        if (!this.$refs.wrapper) {
          return;
        }
        // 配置 better-scroll
        this.scroll = new BScroll(this.$refs.wrapper, {
          probeType: this.probeType, // 何时派发 scroll事件
          click: this.click, // 点击列表是否派发
          scrollX: this.scrollX, // 是否开启横向滚动
        });

        // 是否派发滚动事件
        if (this.listenScroll) {
          this.scroll.on("scroll", (pos) => {
            this.$emit("scroll", pos);
          });
        }

        // 是否派发滚动到底部事件,用于上拉加载
        if (this.pullup) {
          this.scroll.on("scrollEnd", () => {
            // 滚动到底部
            if (this.scroll.y <= this.scroll.maxScrollY + 50) {
              this.$emit("scrollToEnd");
            }
          });
        }

        // 是否派发顶部下拉事件,用于下拉刷新
        if (this.pulldown) {
          this.scroll.on("touchend", (pos) => {
            // 下拉动作
            if (pos.y > 50) {
              this.$emit("pulldown");
            }
          });
        }

        // 是否派发列表滚动开始的事件
        if (this.beforeScroll) {
          this.scroll.on("beforeScrollStart", () => {
            this.$emit("beforeScroll");
          });
        }
      },
      disable() {
        // 代理 better-scroll 的 disable 方法
        this.scroll && this.scroll.disable();
      },
      enable() {
        // 代理 better-scroll 的 enable 方法
        this.scroll && this.scroll.enable();
      },
      refresh() {
        // 代理 better-scroll 的 refresh 方法
        this.scroll && this.scroll.refresh();
      },
      scrollTo() {
        // 代理 better-scroll 的 scrollTo 方法
        this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments);
      },
     scrollToElement() {
       // 代理 better-scroll 的 scrollToElement 方法
       this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments);
     },
    },
    watch: {
      // 延时 refreshDelay 时间后调用refresh方法重新计算,保证滚动效果正常
      data() {
        setTimeout(() => {
          this.refresh();
        }, this.refreshDelay);
      },
    },
  };
</script>

4.4 better-scroll 上拉加载下拉刷新

  • 下拉加载、上拉刷新,可以 动态更新 列表中的数据
  • 下面用 下拉加载 作为例子:
  1. 父组件需要把数据 data 通过 prop 传给 scroll 组件
  2. 实现下拉刷新,只需要通过 prop 把 pulldown 设置为 true;并且监听 pulldown 的事件,去做一些 获取并更新数据 的动作

<template>
  <!-- 假设已经全局注册 scroll 组件 -->
  <scroll
    class="wrapper"
    :data="data"
    :pulldown="pulldown"
    @pulldown="loadData"
  >
    <ul class="content">
      <li v-for="item in data">{{item}}</li>
    </ul>
    <div class="loading-wrapper"></div>
  </scroll>
</template>

<script>
  import BScroll from "better-scroll";
  export default {
    data() {
      return {
        // 列表数据
        data: [],
        // 是否派发顶部下拉的事件,用于下拉加载
        pulldown: true,
      };
    },
    created() {
      this.loadData();
    },
    methods: {
      // 重新拼接获取的列表数据
      loadData() {
        requestData().then((res) => {
          this.data = res.data.concat(this.data);
        });
      },
    },
  };
</script>
  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lyrelion

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值