Scroll

滚动列表,提供了优质的原生滚动体验,便捷的配置项和事件,是一个基于better-scroll进行封装的组件。

滚动原理

由于 better-scroll 的滚动原理为:在滚动方向上,第一个子元素的长度超过了容器的长度。

那么对于 Scroll 组件,其实就是内容元素.cube-scroll-content在滚动方向上的长度必须大于容器元素 .cube-scroll-wrapper。根据滚动方向的不同,有以下两种情况:

1)纵向滚动:内容元素的高度必须大于容器元素。由于容器元素的高度默认会被子元素的高度撑开,所以为了满足我们的滚动前提,你需要给 Scroll 组件的 .cube-scroll-wrapper元素一个非弹性高度。

2)横向滚动:内容元素的宽度必须大于容器元素。由于在默认情况下,子元素的宽度不会超过容器元素,所以需要给 Scroll 组件的 .cube-scroll-content 元素设置大于 .cube-scroll-wrapper 的宽度。

注意:任何时候如果出现无法滚动的情况,都应该首先查看内容元素.cube-scroll-content的元素高度/宽度是否大于容器元素.cube-scroll-wrapper的高度/宽度。这是内容能够滚动的前提条件。如果内容存在图片的情况,可能会出现 DOM 元素渲染时图片还未下载,因此内容元素的高度小于预期,出现滚动不正常的情况。此时你应该在图片加载完成后,比如 onload 事件回调中,手动调用 Scroll 组件的 refresh() 方法,它会重新计算滚动距离。 Scroll 相关常见问题可以查看 Cube-UI/Question-Answer.

示例

7 个示例代码快速了解如何使用 Scroll 组件。

  • 1. 基本使用 - Default

    通过设置 data 属性为一个数组,即可生成能够在容器内优雅滚动的列表。完整示例代码在这里

    <div class="scroll-list-wrap">
      <cube-scroll
        ref="scroll"
        :data="items"
        :options="options">
      </cube-scroll>
    </div>
    
    .scroll-list-wrap
      height: 350px
    

    注意:由上面的滚动原理可知,这里给滚动容器提供一个固定高度是必须的,同时只有在滚动内容的高度大于容器高度时才可滚动。

    options中可以设置滚动条是否可见以及初始滚动位置startY/startX

    Scroll 组件提供了一个scrollTo()方法,可以手动控制列表滚动位置。

    scrollTo() {
      this.$refs.scroll.scrollTo(
        0,
        this.scrollToY,
        this.scrollToTime,
        ease[this.scrollToEasing]
      )
    },
    

    实际上这是一个非常有用的方法,如当我们想要实现“点击不同锚点,列表滚动到相应位置展现不同内容”时,可以使用scrollTo()方法。

  • 2. 横向滚动 - Horizontal

    Scroll 组件支持横向滚动,只需指定direction="horizontal",同时需要添加相应样式如下。完整示例代码在这里

    <cube-scroll
      ref="scroll"
      :data="items"
      direction="horizontal"
      class="horizontal-scroll-list-wrap">
      <ul class="list-wrapper">
        <li v-for="item in items" class="list-item">{{ item }}</li>
      </ul>
    </cube-scroll>
    
    .horizontal-scroll-list-wrap
      border: 1px solid rgba(0, 0, 0, 0.1)
      border-radius: 5px
      .cube-scroll-content
        display: inline-block
      .list-wrapper
        padding: 0 10px
        line-height: 60px
        white-space: nowrap
      .list-item
        display: inline-block
    

    注意:1. 由上面的滚动原理可知,这里的 CSS 样式设置是必须的,只有在滚动内容的宽度大于容器宽度时才可滚动。2. 有时候我们希望横向滚动使用Scroll组件来模拟,纵向保留浏览器原生滚动,或者相反的情况。这时你需要传递 better-scroll 配置项 eventPassthrough

    这里对样式的设定做简要的解释,为list-item元素添加display: inline-block是希望元素能够不换行,单行显示。list-wrapper添加white-space: nowrap是希望遇到父元素边界,依然不换行。另外,关键是cube-scroll-content元素添加display: inline-block样式,此时cube-scroll-content元素的宽度为能够包裹子孙元素的最小宽度,即为连续内联list-item元素的宽度之和子元素的最大宽度。具有同样性质的样式还有,浮动元素和绝对定位元素,在不设置具体宽度时,其宽度为包裹子孙元素的最小宽度。

  • 3. 自定义内容和上拉刷新下拉加载 - Customized

    Scroll组件支持通过插槽自定义列表内容和样式。完整示例代码在这里

    <div class="scroll-list-wrap">
      <cube-scroll
        ref="scroll"
        :data="items"
        :options="options"
        @pulling-down="onPullingDown"
        @pulling-up="onPullingUp">
        ... // 自定义内容
      </cube-scroll>
    </div>
    

    Scroll 组件还支持下拉刷新和上拉加载的能力。默认无下拉刷新/上拉加载,可通过options传递配置项pullDownRefreshpullUpLoad开启相应功能。开启后,下拉时,Scroll 组件会展示默认下拉动画以及派发pulling-down事件,你可以监听pulling-down事件更新数据。同理,开启上拉加载后,可通过pulling-up事件更新数据。

    pullDownRefresh的相关配置有:下拉阈值(threshold), 回弹位置(stop), 更新成功文案(txt)和文案显示时间(stopTime)。pullDownRefreshpullUpLoad对象的所有配置项和含义见 Props 配置

    ... // 省略非核心代码
    computed: {
      options() {
        return {
          pullDownRefresh: this.pullDownRefreshObj,
          pullUpLoad: this.pullUpLoadObj,
          scrollbar: true
        }
      },
      ...
    },
    methods: {
      onPullingDown() {
        // 模拟更新数据
        setTimeout(() => {
          if (Math.random() > 0.5) {
            // 如果有新数据
            this.items.unshift(_foods[1])
          } else {
            // 如果没有新数据
            this.$refs.scroll.forceUpdate()
          }
        }, 1000)
      },
      onPullingUp() {
        // 模拟更新数据
        setTimeout(() => {
          if (Math.random() > 0.5) {
            // 如果有新数据
            let newPage = _foods.slice(0, 5)
            this.items = this.items.concat(newPage)
          } else {
            // 如果没有新数据
            this.$refs.scroll.forceUpdate()
          }
        }, 1000)
      },
      ...
    }
    

    注意:如果请求结果没有数据更新,则必须调用 Scroll 组件的forceUpdate()方法结束此次下拉刷新/上拉加载,这样 Scroll 组件才会开始监听下一次下拉刷新/上拉加载操作。在上例中数据更新时,没有调用forceUpdate()方法,原因为:如果你向Scroll组件传递了data属性,那么当Scroll组件监听到data有更新时会自行调用forceUpate(true)方法,因此推荐传递data属性。

  • 4. 自定义下拉刷新动画 - 仿京东 App 首页

    如果你不喜欢内置的下拉刷新和上拉加载动画,还可以用作用域插槽做自定义动画。Scroll 组件的作用域插槽暴露出的变量非常完善,可以满足绝大多数场景下自定义下拉/上拉动画的需求。下面的例子模仿了京东 App 首页的下拉刷新动画。完整示例代码在这里

    <cube-scroll
      ref="scroll"
      :scroll-events="['scroll']"
      :options="scrollOptions"
      @scroll="onScrollHandle"
      @pulling-down="onPullingDown">
      <img src="http://om0jxp12h.bkt.clouddn.com/jd_content.JPG">
      <template slot="pulldown" slot-scope="props">
        <div v-if="props.pullDownRefresh"
          class="cube-pulldown-wrapper"
          :style="pullDownStyle">
          <div class="pulldown-content">
            <img src="http://om0jxp12h.bkt.clouddn.com/pulldow-img.jpg">
            <span v-if="props.beforePullDown">{{ pullDownTip }}</span>
            <template v-else>
              <span v-if="props.isPullingDown">正在更新...</span>
              <span v-else>更新成功</span>
            </template>
          </div>
        </div>
      </template>
    </cube-scroll>
    
    data() {
      return {
        options: {
          pullDownRefresh: {
            threshold: 60,
            stop: 40,
            txt: '更新成功'
          }
        },
        ...
      }
    },
    computed: {
      pullDownTip() {
        if (this.pullDownY <= 60) {
          return '下拉刷新...'
        } else if (this.pullDownY <= 90) {
          return '继续下拉有惊喜...'
        } else {
          return '松手得惊喜!'
        }
      },
      headerStyle() {
        return Math.min(1, Math.max(0, 1 - this.pullDownY / 40))
      }
    },
    methods: {
      onScrollHandle(pos) {
        this.pullDownY = pos.y
        if (pos.y > 0) {
          this.pullDownStyle = `top:${pos.y}px`
          this.triggerSurpriseFlag = false
          if (this.pullDownY > 90) {
            this.triggerSurpriseFlag = true
          }
        }
        this.$refs.topHeader.style.opacity = this.headerStyle
      },
      onPullingDown() {
        if (this.triggerSurpriseFlag) {
          this.triggerSurprise = true
          this.$refs.scroll.forceUpdate()
          return
        }
        setTimeout(() => {
          this.$refs.scroll.forceUpdate()
        }, 1000)
      },
      ...
    }
    

    通过作用域插槽提供的作用域参数,如:beforePulldownisPullingDown,你可以根据状态的变化来控制动画流程,其他作用域参数及其含义详见下面的插槽。在一个完整的下拉刷新过程中,beforePullDownisPullingDown的状态变化如下:

    流程 beforePulldown isPullingDown 备注
    1. 未触发下拉刷新 true - 展示继续下拉引导图案
    2. 触发下拉刷新 false true 异步请求数据,显示 loading
    3. 获取数据成功 false false 调用 forceUpdate(true), 显示成功文案, 延迟 stopTime 时间进入步骤 4
    4. 下拉刷新完成 true - -
  • 5. 高级使用 - 仿头条 App 首页

    Scroll 组件能够满足绝大多数移动端应用的滚动需求。本例中通过横向和纵向的两个 Scroll 组件快速实现了模仿头条 App 首页的滚动体验。完整的示例代码在这里

    <div class="nav-scroll-list-wrap">
      <cube-scroll ref="navScroll" direction="horizontal">
        <ul class="nav-wrapper">
          <li v-for="(item, index) in navTxts" :key="index" class="nav-item">{{ item }}</li>
        </ul>
      </cube-scroll>
      <div class="more-wrapper">
        <span class="more"></span>
      </div>
    </div>
    <div class="content-scroll-wrapper">
      <div class="content-scroll-list-wrap" ref="scrollWrapper">
        <cube-scroll
          ref="contentScroll"
          :data="content"
          :options="options"
          @pulling-down="onPullingDown"
          @pulling-up="onPullingUp">
          <ul class="imgs-wrapper">
            <li v-for="(item, index) in content" :key="index" class="imgs-item">
              <img :src="item.url">
            </li>
          </ul>
          <template slot="pulldown" slot-scope="props">
            <div v-if="props.pullDownRefresh"
              class="cube-pulldown-wrapper"
              :style="props.pullDownStyle">
              <div v-if="props.beforePullDown"
                class="before-trigger"
                :style="{paddingTop: props.bubbleY + 'px'}">
                <span :class="{rotate: props.bubbleY > options.pullDownRefresh.threshold - 60}"></span>
              </div>
              <div class="after-trigger" v-else>
                <div v-show="props.isPullingDown" class="loading">
                  <cube-loading></cube-loading>
                </div>
                <transition name="success">
                  <div v-show="!props.isPullingDown" class="text-wrapper"><span class="refresh-text">今日头条推荐引擎有x条更新</span></div>
                </transition>
              </div>
            </div>
          </template>
        </cube-scroll>
      </div>
    </div>
    

    和“仿京东 APP”示例不同的是,在下拉刷新的自定义动画中,使用了pulldown作用域插槽中的pullDownStylebubbleY更方便的实现下拉动画。

    pullDownStyle用来控制下拉内容的位置,值为字符串top: n px(n 代表数值)。Scroll 组件是通过绝对定位的top值来控制下拉内容位置的。初始状态top值为负值,大小刚好为下拉内容的高度,因此下拉内容被隐藏到滚动区域上方,当下拉过程中,Scroll 组件会逐渐增大top值,实时更新下拉内容的位置。top最大值为0,即当下拉内容完全显示后top值不再增加。即 pullY - height <= top <= 0。(pullY 为下拉距离,height 为下拉内容高度)

    bubbleY用来辅助实现自定义动画。在默认动画中,bubbleY用来控制气泡尾巴长度;在头条例子中,用来控制箭头的padding-top值,间接控制箭头位置。bubbleY的最小值为 0,下拉过程中,当下拉距离大于下拉内容高度后,bubbleY开始增大。即 0 <= bubbleY <= pullY - height

    在本例中,pullDownRefresh配置项没有传入stop值,但是下拉后依然能够回弹到正确位置,原因是 Scroll 组件初始化时会将 beforePullDown === false && isPullingDown === true 时下拉内容高度作为 stop 默认值。

  • 6. 嵌套纵向滚动 - Vertical Scrolls1.12.0

    Scroll组件还支持嵌套的场景(目前只支持两层嵌套)。当遇到嵌套场景时,你需要给内层scroll组件设置 Prop nestMode,可选值有 'native' 和 'free'。当设置为 'native' 时,嵌套Scroll与浏览器原生嵌套场景的滚动行为相同。下面是Scroll组件实现纵向嵌套滚动的例子。完整的示例代码在这里这里

    <cube-scroll
      ref="scroll1"
      class="scroll-list-outer-wrap">
      ...
      <cube-scroll
        ref="scroll2"
        class="scroll-list-inner-wrap"
        nest-mode="native">
        <ul class="cube-scroll-list">
          <li class="cube-scroll-item border-bottom-1px"
            v-for="(item, index) in items2"
            :key="index">{{item}}</li>
        </ul>
      </cube-scroll>
      ...
    </cube-scroll>
    
  • 7. 嵌套横向滚动 - Horizontal Scrolls1.12.0

    你还可以实现横向的嵌套滚动。这里同时设置nestModefree,与native模式不同的是,free模式下,内层滚动过程中只要触发边界,便会开启外层滚动。而native模式下,只在开始滚动时判断是否到达边界,与浏览器原生的嵌套滚动保持一致。完整的示例代码在这里

    <cube-scroll
      ref="scroll"
      :data="items1"
      direction="horizontal"
      class="outer-horizontal-scroll">
      <ul class="list-wrapper">
        <li v-for="item in items1" class="list-item">{{ item }}</li>
        <li class="list-item inner-horizontal-scroll">
          <cube-scroll
            ref="scroll"
            :data="items2"
            direction="horizontal"
            nest-mode="free">
            <ul class="list-wrapper">
              <li v-for="item in items2" class="list-item">{{ item }}</li>
            </ul>
          </cube-scroll>
        </li>
        <li v-for="item in items1" class="list-item">{{ item }}</li>
      </ul>
    </cube-scroll>
    

Props 配置

参数 说明 类型 可选值 默认值
data 用于列表渲染的数据 Array - []
direction 滚动方向 String 'vertical', 'horizontal' 'vertical'
options better-scroll 配置项,具体请参考BS 官方文档 Object - {
observeDOM: true,
click: true,
probeType: 1,
scrollbar: false,
pullDownRefresh: false,
pullUpLoad: false
}
注意:从1.12.38版本开始,因修复BSiOS13.4版本的滚动问题,useTransition在iOS版本>=13.4时默认为fasle
具体请参考#978
scrollEvents1.9.0 配置需要派发的 scroll 事件 Array 可包含子项:'scroll', 'before-scroll-start', 'scroll-end' []
listenScroll 是否派发 scroll 事件。即将废弃,推荐使用 scroll-events 属性 Boolean true/false false
listenBeforeScroll 是否派发 before-scroll-start 事件。即将废弃,推荐使用 scroll-events 属性 Boolean true/false false
refreshDelay data属性的数据更新后,scroll 的刷新延时 Number - 20
nestMode1.12.0 嵌套滚动模式,默认是none,即不做嵌套处理。native只在开始滚动时判断是否到达边界并开启外层滚动,与浏览器原生的嵌套滚动保持一致。free模式下,内层滚动过程中只要触发边界,便会开启外层滚动。 String 'none', 'native', 'free' 'none'

options中 better-scroll 的几个常用配置项,scrollbarpullDownRefreshpullUpLoad这三个配置即可设为 Booleanfalse 关闭该功能,true 开启该功能,并使用默认子配置),也可设为Object,开启该功能并具体定制其子配置项。

  • scrollbar 子配置项
参数 说明 类型 可选值 默认值
fade 是否淡入淡出 Boolean true/false false
  • pullDownRefresh 子配置项
参数 说明 类型 可选值 默认值
threshold 下拉刷新动作的下拉距离阈值 Number - 90
stop 回弹停留的位置 Number - 组件会自动计算回弹时显示的元素高度作为默认值
stopTime 刷新成功的文案显示时间 Number - 600
txt 刷新成功的文案 String - 'Refresh success'
  • pullUpLoad 子配置项
参数 说明 类型 可选值 默认值
threshold 上拉刷新动作的上拉距离阈值 Number - 0
txt 上拉加载的相关文案 Object - { more: '', noMore: '' }
visible1.12.21 内容不满一屏时,txt 文案是否可见 Boolean true/false false

当开启 pullUpLoad,且内容较少,内容高度小于容器时,默认情况下,pullUpLoad.txt 配置的文案如“上滑加载更多”,需要上拉后才能看到。如果希望无需上拉即可看到提示文案,可以设置 pullUpLoad.visibletrue

插槽

名字 说明 作用域参数
default 基于data属性渲染的列表 -
pulldown 位于列表上方,会在下拉刷新时显示 pullDownRefresh: 是否开启了下拉刷新功能
pullDownStyle: 移入移出的样式
beforePullDown: 是否正在做下拉操作
isPullingDown: 是否正在拉取数据
bubbleY: 当前下拉的距离 - 50
pullup 位于列表下方,会在上拉加载时显示 pullUpLoad: 是否开启了上拉加载功能
isPullUpLoad: 是否正在加载数据

事件

事件名 说明 参数
click 点击列表项时触发 item - 该列表项的数据
scroll scroll-events 包含 scroll 时,根据 probeType 的值决定派发时机 Object {x, y} - 实时滚动位置的坐标
before-scroll-start scroll-events 包含 before-scroll-start 时,在滚动开始之前触发 -
scroll-end1.9.0 scroll-events 包含 scroll-end 时,在滚动结束时触发 Object {x, y} - 实时滚动位置的坐标
pulling-down 当 pullDownRefresh 属性为 true 时,在下拉超过阈值时触发 -
pulling-up 当 pullUpLoad 属性为 true 时,在上拉超过阈值时触发 -

方法

方法名 说明 参数
scrollTo(x, y, time, ease) 滚动到指定位置 x: number, 横向位置
y: number, 纵向位置
time: number, 过渡动画时间 (ms)
ease: EasingFn, 缓动曲线
forceUpdate(dirty, nomore1.12.21) 标记上拉下拉结束,强制重新计算可滚动距离 dirty: boolean, 标识有数据更新,默认为 false。
nomore: boolean, pullUpLoad 中标识没有更多数据,默认为 false。1.12.21版本后支持 nomore 参数,当 nomore 为 true 时,上拉加载展示 pullUpLoad.txt.nomore 值,但当 dirty 为 false 时,nomore 无效。
disable() 禁用滚动 -
enable() 启用滚动,默认是开启滚动的。 -
resetPullUpTxt() 当从无更多切换到有更多时,重置上拉文本内容 -
refresh() 刷新,重新计算高度且刷新 BetterScroll 实例 -

内部属性

属性名 说明
scroll 可以通过该属性获得内部实现滚动核心的 BScoll 实例,从而获得更多 BScoll 的底层能力,如监听touchEnd事件,获得滚动中的中间状态等,具体可查看 better-scroll 文档