一、写在前面

在日常开发中,触摸滑动成为移动应用程序中不可或缺的交互方式。为了提供流畅、自然和高效的滑动体验,许多开发者选择使用开源库来简化开发过程。其中,BetterScroll 因其强大的功能和灵活的定制能力而备受关注。本文将深入探讨 BetterScroll 的工作原理、功能特点以及如何将其应用于实际项目中。

BetterScroll 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件。它的核心是借鉴的iscroll的实现,它的 API 设计基本兼容 iscroll,在 iscroll 的基础上又扩展了一些 feature 以及做了一些性能优化。

BetterScroll 是使用纯 JavaScript 实现的,这意味着它是无依赖的。

二、滚动原理

在介绍BetterScroll之前,我们先来看一下浏览器的滚动原理:浏览器的滚动条大家都会遇到,当页面内容的高度超过视口高度的时候,会出现纵向滚动条;当页面内容的宽度超过视口宽度的时候,会出现横向滚动条。也就是当我们的视口展示不下内容的时候,会通过滚动条的方式让用户滚动屏幕看到剩余的内容。

BetterScroll 也是一样的原理,我们可以用一张图更直观的感受一下:

在这里插入图片描述

绿色部分为 wrapper,也就是父容器,它会有固定的高度。黄色部分为 content,它是父容器的第一个子元素,它的高度会随着内容的大小而撑高。那么,当 content 的高度不超过父容器的高度,是不能滚动的,而它一旦超过了父容器的高度,我们就可以滚动内容区了,这就是 BetterScroll 的滚动原理。

三、安装

1、npm

npm install @better-scroll/core --save
// or
yarn add @better-scroll/core

接下来就可以在代码中引入了,webpack(opens new window)等构建工具都支持从 node_modules 里引入代码:

import BScroll from '@better-scroll/core'

如果是 commonjs 的语法,如下:

var BScroll = require('@better-scroll/scroll')

2、script加载

BetterScroll 也支持直接用 script 加载的方式,加载后会在 window 上挂载一个 BScroll 的对象。

<script src="https://unpkg.com/@better-scroll/core@latest/dist/core.js"></script>

<!-- minify -->
<script src="https://unpkg.com/@better-scroll/core@latest/dist/core.min.js"></script>

let wrapper = document.getElementById("wrapper")
let bs = new BScroll(wrapper, {})

3、具备所有插件能力的 BetterScroll

npm install better-scroll --save
// or
yarn add better-scroll
import BetterScroll from 'better-scroll'
let bs = new BetterScroll('.wrapper', {})

也可以通过 CDN 加载。

<script src="https://unpkg.com/better-scroll@latest/dist/better-scroll.js"></script>

<!-- minify -->
<script src="https://unpkg.com/better-scroll@latest/dist/better-scroll.min.js"></script>
let bs = BetterScroll.createBScroll('.wrapper', {})

四、使用

1、基础滚动

如果你只需要一个拥有基础滚动能力的列表,只需要引入 core。

import BScroll from '@better-scroll/core'
let bs = new BScroll('.wrapper', {
  // ...... 详见配置项
})

2、增强型滚动

如果你需要一些额外的 feature。比如 pull-up,你需要引入额外的插件,详情查看插件(https://better-scroll.github.io/docs/zh-CN/plugins/)。

import BScroll from '@better-scroll/core'
import Pullup from '@better-scroll/pull-up'

// 注册插件
BScroll.use(Pullup)

let bs = new BScroll('.wrapper', {
  probeType: 3,
  pullUpLoad: true
})

3、全能力的滚动

如果你觉得一个个引入插件很费事,我们提供了一个拥有全部插件能力的 BetterScroll 包。它的使用方式与 1.0 版本一模一样,但是体积会相对大很多,推荐按需引入。

import BScroll from 'better-scroll'

let bs = new BScroll('.wrapper', {
  // ...
  pullUpLoad: true,
  wheel: true,
  scrollbar: true,
  // and so on
})

注意:BetterScroll 2.X 里面,我们将 1.X 耦合的 feature 拆分至插件,以达到按需加载、减少包体积的目的。因此,@better-scroll/core 只提供了最核心的滚动能力。如果想要实现上拉加载、下拉刷新的功能,你需要使用对应的插件。

五、起步

BetterScroll 最常见的应用场景是列表滚动,我们来看一下它的 html 结构。

<div class="wrapper">
  <ul class="content">
    <li>...</li>
    <li>...</li>
    ...
  </ul>
  <!-- 这里可以放一些其它的 DOM,但不会影响滚动 -->
</div>

上面的代码中 BetterScroll 是作用在外层 wrapper 容器上的,滚动的部分是 content 元素。这里要注意的是,BetterScroll 默认处理容器(wrapper)的第一个子元素(content)的滚动,其它的元素都会被忽略。

最简单的初始化代码如下:

import BScroll from '@better-scroll/core'
let wrapper = document.querySelector('.wrapper')
let scroll = new BScroll(wrapper)

BetterScroll 提供了一个类,实例化的第一个参数是一个原生的 DOM 对象。当然,如果传递的是一个字符串,BetterScroll 内部会尝试调用 querySelector 去获取这个 DOM 对象。

六、核心滚动

在 BetterScroll 2.0 的设计当中,我们抽象了核心滚动部分,它作为 BetterScroll 的最小使用单元,压缩体积比 1.0 小了将近三分之一,往往你可能只需要完成一个纯粹的滚动需求,那么你只需要引入@better-scroll/core这一个库。

BetterScroll 有多种滚动模式。下面我们介绍一下BetterScroll在vue3中的使用方法。

1、垂直滚动

<template>
  <div class="scroll-container">
    <div class="scroll-wrapper" ref="scroll">
      <div class="scroll-content">
        <div 
          class="scroll-item" 
          v-for="(item, index) in 100" 
          :key="index" 
          @click="clickHandler(item)"
        >{{ item }}</div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue';
import BScroll from '@better-scroll/core'

const scroll = ref()
const bs = ref()

const init = () => {
  bs.value = new BScroll(scroll.value, {
    // 1. probeType 为 0,在任何时候都不派发 scroll 事件,
    // 2. probeType 为 1,仅仅当手指按在滚动区域上,每隔 momentumLimitTime 毫秒派发一次 scroll 事件,
    // 3. probeType 为 2,仅仅当手指按在滚动区域上,一直派发 scroll 事件,
    // 4. probeType 为 3,任何时候都派发 scroll 事件,包括调用 scrollTo 或者触发 momentum 滚动动画
    probeType: 3,
    click: true
  })
  bs.value.on('scrollStart', () => {
    console.log('scrollStart-')
  })
  bs.value.on('scroll', ({ y }: { y: number }) => {
    console.log('scrolling-', y)
  })
  bs.value.on('scrollEnd', (pos: { x: number, y: number }) => {
    // {x: 0, y: -792},滚动结束时容器相对x轴y轴坐标
    console.log(pos)
  })
}
const clickHandler = (item: number) => {
  window.alert(item)
}

onMounted(() => {
  init()
})

onBeforeUnmount(() => {
  bs.value.destroy()
})
</script>

<style lang="scss">
.scroll-container {
  .scroll-wrapper {
    height: 600px;
    position: relative;
    overflow: hidden;

    .scroll-item {
      height: 50px;
      line-height: 50px;
      font-size: 24px;
      font-weight: bold;
      border-bottom: 1px solid #eee;
      text-align: center;

      &:nth-child(2n) {
        background-color: #f3f5f7;
      }

      &:nth-child(2n+1) {
        background-color: #42b983
      }
    }
  }
}
</style>

2、水平滚动

我们还以上面代码为例只需要改动两个地方即可,第一在new BScroll里面添加scrollX: true属性,如下:

 bs.value = new BScroll(scroll.value, {
  probeType: 3,
  click: true,
  scrollX: true
})

第二,把style里面的css代码改成如下代码:

.scroll-container {
  .scroll-wrapper {
    position: relative;
    width: 90%;
    margin: 80px auto;
    white-space: nowrap;
    border: 3px solid #42b983;
    border-radius: 5px;
    overflow: hidden;

    .scroll-content {
      display: inline-block;

      .scroll-item {
        height: 50px;
        line-height: 50px;
        font-size: 24px;
        display: inline-block;
        text-align: center;
        padding: 0 10px;
      }
    }
  }
}

BetterScroll 实现横向滚动,对 CSS 是比较苛刻的。首先你要保证 wrapper 不换行white-space: nowrap,并且 content 的 display 是 inline-block。

效果如下:

图片

是不是感觉非常的丝滑呢!

3、水平和垂直同时滚动

<template>
  <div class="free-scroll-container">
    <div class="free-scroll-wrapper">
      <div class="scroll-wrapper" ref="wrapper">
        <div class="scroll-content">
          <div class="scroll-item" v-for="(item, index) in 100" :key="index">{{ item }}</div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import BScroll from '@better-scroll/core'

const wrapper = ref()
const bs = ref()

const init = () => {
  bs.value = new BScroll(wrapper.value, {
    freeScroll: true,
    bounce: {
      bottom: false,
      left: false,
      right: false,
      top: false
    }
  })
}

onMounted(() => {
  init()
})
</script>

<style lang="scss">
.free-scroll-container {
  position: fixed;
  width: 100%;
  height: 100%;
  .free-scroll-wrapper {
    position: relative;
    width: 100%;
    height: 100%;
    border: 1px solid rgb(96, 108, 113);
    box-sizing: border-box;
    .scroll-wrapper {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      overflow: hidden;
      .scroll-content {
        background-color: #efeff4;
        width: 1500px;
        .scroll-item {
          height: 50px;
          line-height: 50px;
          font-size: 24px;
          font-weight: bold;
          border-bottom: 1px solid #eee;
          text-align: center;

          &:nth-child(2n) {
            background-color: #f3f5f7;
          }

          &:nth-child(2n+1) {
            background-color: #42b983
          }
        }
      }
    }
  }
}
</style>

图片

当然,还有很多其它的用法,更多用法参见https://better-scroll.github.io/docs/zh-CN/。

七、常见问题

1、为什么 BetterScroll 初始化不能滚动?

BetterScroll 滚动原理是 content 元素的高度/宽度超过 wrapper 元素的高度/宽度。而且,如果你的 content 元素含有不固定尺寸的图片,你必须在图片加载完之后,调用 refresh() 方法来确保高度计算正确。还存在一种情况是页面存在表单元素,弹出键盘之后,将页面的视口高度压缩,导致 bs 不能正常工作,依然是调用 refresh() 方法。

2、为什么 BetterScroll 区域的点击事件无法被触发?

BetterScroll 默认会阻止浏览器的原生 click 事件。如果你想要 click 事件生效,BetterScroll 会派发一个 click 事件,并且 event 参数的 _constructed 为 true。配置项如下:

import BScroll from '@better-scroll/core'

let bs = new BScroll('./div', {
  click: true
})

3、为什么我的 BetterScroll 监听 scroll 钩子,监听器不执行?

BetterScroll 通过 probeType 配置项来决定是否派发 scroll 钩子,因为这是有一些性能损耗的。probeType 为 2 的时候会实时的派发事件,probeType 为 3 的时候会在 momentum 动量动画的时候派发事件。建议设置为 3。

import BScroll from '@better-scroll/core'

let bs = new BScroll('./div', {
  probeType: 3
})

4、slide 用了横向滚动,发现在 slide 区域纵向滚动无效?

如果想要保留浏览器的原生纵向滚动,需要如下配置项:

import BScroll from '@better-scroll/core'

let bs = new BScroll('./div', {
  eventPassthrough: 'vertical'
})
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐