• 引入
  • 使用指南
  • 代码演示
    • 基础 添加内容
      • 请在移动设备中扫码预览
    • 下拉刷新 触发下拉刷新
      • 请在移动设备中扫码预览
    • 粘性标题
      • 请在移动设备中扫码预览
    • 手动初始化
      • 请在移动设备中扫码预览
    • 横向滚动
      • isPrevent false, touchAngle 80
    • 加载更多
      • 请在移动设备中扫码预览
    • 配合TabBar
      • 请在移动设备中扫码预览
  • API
    • ScrollView Props
    • ScrollView TouchAngle
    • ScrollViewRefresh Props
    • ScrollViewMore Props
    • ScrollView Slots
      • default
      • refresh
      • more
      • header
      • footer
    • ScrollView Methods
      • init()
      • reflowScroller()
      • scrollTo(left, top, animate = false)
      • triggerRefresh()
      • finishRefresh()
      • finishLoadMore()
    • ScrollView Events
      • @scroll({scrollLeft, scrollTop})
      • @refreshActive()
      • @refreshing()
      • @end-reached()
  • 附录
    • 无法正常滚动且异常回弹
    • 下拉刷新后滚动无法触发endReached

    ScrollView 滚动区域/下拉刷新

    Scan me!

    用于模拟原生的滚动区域,并支持下拉刷新和加载更多

    引入

    1. import { ScrollView, ScrollViewRefresh, ScrollViewMore } from 'mand-mobile'
    2. Vue.component(ScrollView.name, ScrollView)

    使用指南

    • ScrollViewRefresh为组件库内置的下拉刷新组件,仅用于作为视觉展示,需在插槽refresh)中使用,下拉刷新组件也可自定义

    • ScrollViewMore为组件库内置的加载更多组件,仅用于作为视觉展示,需在插槽more)中使用,加载更多组件也可自定义

    • 组件容器需具有高度,否则会出现无法滚动或回弹问题。更多使用的常见问题请查看附录)

    代码演示

    基础 添加内容

    请在移动设备中扫码预览

    ScrollView 滚动区域/下拉刷新 - 图2

    1. <template>
    2. <div class="md-example-child md-example-child-scroll-view md-example-child-scroll-view-0">
    3. <md-scroll-view
    4. ref="scrollView"
    5. @scroll="$_onScroll"
    6. >
    7. <div
    8. v-for="i in list"
    9. class="scroll-view-item"
    10. :key="i"
    11. @click="$_onItemClick(i)"
    12. >
    13. {{i}}
    14. </div>
    15. </md-scroll-view>
    16. </div>
    17. </template>
    18. <script>
    19. import {ScrollView, Toast} from 'mand-mobile'
    20. export default {
    21. name: 'scroll-view-demo-0',
    22. components: {
    23. [ScrollView.name]: ScrollView,
    24. },
    25. data() {
    26. return {
    27. list: 5,
    28. }
    29. },
    30. mounted() {
    31. window.ScrollViewTrigger0 = () => {
    32. this.addItems()
    33. }
    34. },
    35. methods: {
    36. $_onItemClick(i) {
    37. Toast.info(`Click ${i}`)
    38. },
    39. $_onScroll({scrollLeft, scrollTop}) {
    40. console.log(`[Mand Mobile ScrollView - demo0 - onScroll] scrollLeft: ${scrollLeft}, scrollTop: ${scrollTop}`)
    41. },
    42. addItems() {
    43. this.list += 5
    44. // 如果把autoReflow设置为false, 需调用reflowScroller
    45. this.$refs.scrollView.reflowScroller()
    46. },
    47. },
    48. }
    49. </script>
    50. <style lang="stylus">
    51. .md-example-child-scroll-view-0
    52. height 400px
    53. background #FFF
    54. .scroll-view-item
    55. padding 30px 0
    56. text-align center
    57. font-size 28px
    58. font-family DINAlternate-Bold
    59. border-bottom .5px solid #efefef
    60. </style>

    下拉刷新 触发下拉刷新

    请在移动设备中扫码预览

    ScrollView 滚动区域/下拉刷新 - 图3

            <template>
      <div class="md-example-child md-example-child-scroll-view md-example-child-scroll-view-2">
        <md-scroll-view
          ref="scrollView"
          :scrolling-x="false"
          @refreshing="$_onRefresh"
        >
          <md-scroll-view-refresh
            slot="refresh"
            slot-scope="{ scrollTop, isRefreshActive, isRefreshing }"
            :scroll-top="scrollTop"
            :is-refreshing="isRefreshing"
            :is-refresh-active="isRefreshActive"
          ></md-scroll-view-refresh>
          <div
            v-for="i in list"
            :key="i"
            class="scroll-view-list"
          >
            <p class="scroll-view-item">{{i}}</p>
          </div>
        </md-scroll-view>
      </div>
    </template>
    
    <script>
    import {ScrollView, ScrollViewRefresh} from 'mand-mobile'
    
    export default {
      name: 'scroll-view-demo-0',
      components: {
        [ScrollView.name]: ScrollView,
        [ScrollViewRefresh.name]: ScrollViewRefresh,
      },
      data() {
        return {
          list: 5,
        }
      },
      mounted() {
        window.ScrollViewTrigger1 = () => {
          this.$refs.scrollView.triggerRefresh()
        }
      },
      methods: {
        $_onRefresh() {
          // async data
          setTimeout(() => {
            this.list += 5
            this.$refs.scrollView.finishRefresh()
          }, 2000)
        },
      },
    }
    
    </script>
    
    <style lang="stylus">
    .md-example-child-scroll-view-2
      height 800px
      background #FFF
      .scroll-view-item
        padding 30px 0
        text-align center
        font-size 28px
        font-family DINAlternate-Bold
        border-bottom .5px solid #efefef
    </style>
          

    粘性标题

    请在移动设备中扫码预览

    ScrollView 滚动区域/下拉刷新 - 图4

            <template>
      <div class="md-example-child md-example-child-scroll-view md-example-child-scroll-view-4">
        <md-scroll-view
          ref="scrollView"
          :scrolling-x="false"
          @scroll="$_onScroll"
        >
          <div
            v-for="i in category"
            :key="i"
            class="scroll-view-category"
          >
            <p class="scroll-view-category-title">{{ i }}</p>
            <div
              v-for="j in list"
              :key="j"
              class="scroll-view-list"
            >
              <p class="scroll-view-item">{{`${i} - ${j}`}}</p>
            </div>
          </div>
        </md-scroll-view>
        <p v-if="activeBlockIndex > 0" class="scroll-view-striky-title">{{ activeBlockIndex }}</p>
      </div>
    </template>
    
    <script>
    import {ScrollView} from 'mand-mobile'
    
    export default {
      name: 'scroll-view-demo-3',
      components: {
        [ScrollView.name]: ScrollView,
      },
      data() {
        return {
          category: 5,
          list: 5,
          dimensions: [],
          scrollY: 0,
        }
      },
      computed: {
        activeBlockIndex() {
          let activeIndex = -1
          this.dimensions.forEach((dimension, index) => {
            if (this.scrollY >= dimension[0] && this.scrollY <= dimension[1]) {
              activeIndex = index + 1
            }
          })
          return activeIndex
        },
      },
      mounted() {
        // 如果内容发生变化,需重新初始化block和scroller
        this.$_initScrollBlock()
        // this.$refs.scrollView.reflowScroller()
      },
      methods: {
        $_initScrollBlock() {
          const blocks = this.$el.querySelectorAll('.scroll-view-category')
          let offset = 0
          Array.prototype.slice.call(blocks).forEach((block, index) => {
            const innerHeight = block.clientHeight
            this.$set(this.dimensions, index, [offset, offset + innerHeight])
            offset += innerHeight
          })
        },
        $_onScroll({scrollTop}) {
          this.scrollY = scrollTop
        },
      },
    }
    
    </script>
    
    <style lang="stylus">
    .md-example-child-scroll-view-4
      position relative
      height 800px
      background #FFF
      .scroll-view-striky-title
        position absolute
        top 0
        left 0
        right 0
      .scroll-view-category-title, .scroll-view-striky-title
        padding 10px 0
        text-align center
        font-size 32px
        font-family DINAlternate-Bold
        background-color #f0f0f0
      .scroll-view-item
        padding 30px 0
        text-align center
        font-size 32px
        border-bottom .5px solid #efefef
    </style>
          

    手动初始化

    请在移动设备中扫码预览

    ScrollView 滚动区域/下拉刷新 - 图5

            <template>
      <div class="md-example-child md-example-child-scroll-view md-example-child-scroll-view-6">
        <md-tabs
          @change="$_onTabChange"
          immediate
        >
          <md-tab-pane class="content" name="scrollView0" label="Block - 1">
            <md-scroll-view
              ref="scrollView0"
              :scrolling-x="false"
              manual-init
              @refreshing="$_onRefresh(0)"
            >
              <md-scroll-view-refresh
                slot="refresh"
                slot-scope="{ scrollTop, isRefreshActive, isRefreshing }"
                :scroll-top="scrollTop"
                :is-refreshing="isRefreshing"
                :is-refresh-active="isRefreshActive"
              ></md-scroll-view-refresh>
              <div
                v-for="i in list0"
                :key="i"
                class="scroll-view-list"
              >
                <p class="scroll-view-item">{{`1 - ${i}`}}</p>
              </div>
            </md-scroll-view>
          </md-tab-pane>
          <md-tab-pane class="content" name="scrollView1" label="Block - 2">
            <md-scroll-view
              ref="scrollView1"
              :scrolling-x="false"
              manual-init
              @refreshing="$_onRefresh(1)"
            >
              <md-scroll-view-refresh
                slot="refresh"
                slot-scope="{ scrollTop, isRefreshActive, isRefreshing }"
                :scroll-top="scrollTop"
                :is-refreshing="isRefreshing"
                :is-refresh-active="isRefreshActive"
              ></md-scroll-view-refresh>
              <div
                v-for="i in list1"
                :key="i"
                class="scroll-view-list"
              >
                <p class="scroll-view-item">{{`2 - ${i}`}}</p>
              </div>
            </md-scroll-view>
          </md-tab-pane>
        </md-tabs>
      </div>
    </template>
    
    <script>
    import {Tabs, TabPane, ScrollView, ScrollViewRefresh} from 'mand-mobile'
    
    export default {
      name: 'scroll-view-demo-6',
      components: {
        [Tabs.name]: Tabs,
        [TabPane.name]: TabPane,
        [ScrollView.name]: ScrollView,
        [ScrollViewRefresh.name]: ScrollViewRefresh,
      },
      data() {
        return {
          list0: 5,
          list1: 5,
          isFinished: false,
        }
      },
      methods: {
        $_onRefresh(index) {
          // async data
          setTimeout(() => {
            this[`list${index}`] += 5
            this.$refs[`scrollView${index}`].finishRefresh()
          }, 2000)
        },
        $_onTabChange(tab) {
          console.log(tab.name)
          this.$refs[tab.name].init()
        },
      },
    }
    
    </script>
    
    <style lang="stylus">
    .md-example-child-scroll-view-6
      background #FFF
      .content
        height 800px
      .md-tab-bar
        box-shadow 0 2px 8px #f0f0f0
      .scroll-view-item
        padding 30px 0
        text-align center
        font-size 32px
        font-family DINAlternate-Bold
        border-bottom .5px solid #efefef
    </style>
          

    横向滚动

    isPrevent false, touchAngle 80

    ScrollView 滚动区域/下拉刷新 - 图6

            <template>
      <div class="md-example-child md-example-child-scroll-view md-example-child-scroll-view-1">
        <md-scroll-view
          ref="scrollView"
          :scrolling-y="false"
          :touch-angle="80"
          :is-prevent="false"
        >
          <div class="scroll-view-list">
            <p
              v-for="i in list"
              :key="i"
              class="scroll-view-item"
            >{{i}}</p>
          </div>
        </md-scroll-view>
      </div>
    </template>
    
    <script>
    import {ScrollView} from 'mand-mobile'
    
    export default {
      name: 'scroll-view-demo-0',
      components: {
        [ScrollView.name]: ScrollView,
      },
      data() {
        return {
          list: 5,
        }
      },
    }
    
    </script>
    
    <style lang="stylus">
    .md-example-child-scroll-view-1
      height 100px
      background #FFF
      .md-scroll-view
        display flex
        align-items center
        .scroll-view-list
          display flex
          width 1000px
          .scroll-view-item
            flex 1
            text-align center
            font-size 28px
            font-family DINAlternate-Bold
            border-right .5px solid #efefef
    </style>
          

    加载更多

    请在移动设备中扫码预览

    ScrollView 滚动区域/下拉刷新 - 图7

            <template>
      <div class="md-example-child md-example-child-scroll-view md-example-child-scroll-view-3">
        <md-scroll-view
          ref="scrollView"
          :scrolling-x="false"
          @end-reached="$_onEndReached"
        >
          <div
            v-for="i in list"
            :key="i"
            class="scroll-view-list"
          >
            <p class="scroll-view-item">{{i}}</p>
          </div>
          <md-scroll-view-more
            slot="more"
            :is-finished="isFinished"
          >
          </md-scroll-view-more>
        </md-scroll-view>
      </div>
    </template>
    
    <script>
    import {ScrollView, ScrollViewMore} from 'mand-mobile'
    
    export default {
      name: 'scroll-view-demo-2',
      components: {
        [ScrollView.name]: ScrollView,
        [ScrollViewMore.name]: ScrollViewMore,
      },
      data() {
        return {
          list: 10,
          isFinished: false,
        }
      },
      methods: {
        $_onEndReached() {
          if (this.isFinished) {
            return
          }
          // async data
          setTimeout(() => {
            this.list += 5
            if (this.list >= 20) {
              this.isFinished = true
            }
            this.$refs.scrollView.finishLoadMore()
          }, 1000)
        },
      },
    }
    
    </script>
    
    <style lang="stylus">
    .md-example-child-scroll-view-3
      height 800px
      background #FFF
      .scroll-view-item
        padding 30px 0
        text-align center
        font-size 32px
        font-family DINAlternate-Bold
        border-bottom .5px solid #efefef
    </style>
          

    配合TabBar

    请在移动设备中扫码预览

    ScrollView 滚动区域/下拉刷新 - 图8

            <template>
      <div class="md-example-child md-example-child-scroll-view md-example-child-scroll-view-5">
        <md-tab-bar
          v-model="activeBlockIndex"
          :items="tabBarItems"
          :max-length="5"
          ref="tabBar"
          @change="$_onTabChange"
        ></md-tab-bar>
        <md-scroll-view
          class="scroll-view-with-tab-bar"
          ref="scrollView"
          :scrolling-x="false"
          @scroll="$_onScroll"
          @mousedown.native="$_onScrollStart"
          @touchstart.native="$_onScrollStart"
        >
          <div
            v-for="i in category"
            :key="i"
            class="scroll-view-category"
          >
            <div
              v-for="j in list"
              :key="j"
              class="scroll-view-list"
            >
              <p class="scroll-view-item">{{`${i} - ${j}`}}</p>
            </div>
          </div>
        </md-scroll-view>
      </div>
    </template>
    
    <script>
    import {ScrollView, TabBar} from 'mand-mobile'
    
    const debounce = function(fn, delay) {
      let timer
      return function() {
        const context = this
        timer && clearTimeout(timer)
    
        timer = setTimeout(function() {
          fn.apply(context, arguments)
        }, delay)
      }
    }
    export default {
      name: 'scroll-view-demo-3',
      components: {
        [ScrollView.name]: ScrollView,
        [TabBar.name]: TabBar,
      },
      data() {
        return {
          category: 5,
          list: 5,
          dimensions: [],
          scrollY: 0,
          isManual: false,
          activeBlockIndex: 0,
        }
      },
      computed: {
        tabBarItems() {
          return this.dimensions.map((item, index) => {
            return {name: index, label: `Block - ${index + 1}`}
          })
        },
      },
      mounted() {
        // 如果内容发生变化,需重新初始化block和scroller
        this.$_initScrollBlock()
        // this.$refs.scrollView.reflowScroller()
      },
      methods: {
        $_initScrollBlock() {
          const blocks = this.$el.querySelectorAll('.scroll-view-category')
    
          let offset = 0
          Array.prototype.slice.call(blocks).forEach((block, index) => {
            const innerHeight = block.clientHeight
            this.$set(this.dimensions, index, [offset, offset + innerHeight])
            offset += innerHeight
          })
    
          // setTimeout(() => {
          //   this.$refs.tabBar.reflow()
          // }, 1000)
        },
        $_onScrollStart() {
          this.isManual = false
        },
        $_onScroll({scrollTop}) {
          if (!this.isManual) {
            this.dimensions.some((dimension, index) => {
              if (scrollTop >= dimension[0] && scrollTop <= dimension[1]) {
                this.activeBlockIndex = index
                return true
              }
            })
          }
        },
        $_onTabChange(item, index) {
          this.isManual = true
          debounce(() => {
            const offsetTop = this.dimensions[index][0]
            this.$refs.scrollView.scrollTo(0, offsetTop, true)
            this.scrollY = offsetTop
          }, 100)()
        },
      },
    }
    
    </script>
    
    <style lang="stylus">
    .md-example-child-scroll-view-5
      position relative
      height 800px
      background #FFF
      .md-tab-bar
        position absolute
        left 0
        top 0
        right 0
        z-index 2
        box-shadow 0 2px 8px #f0f0f0
      .scroll-view-with-tab-bar
        & > .scroll-view-container
          padding-top 100px
        .scroll-view-item
          padding 30px 0
          text-align center
          font-size 32px
          border-bottom .5px solid #efefef
    </style>
          

    API

    ScrollView Props

    属性说明类型默认值备注
    scrolling-x水平滚动Booleantrue-
    scrolling-y垂直滚动Booleantrue-
    bouncing可回弹Booleantrue-
    auto-reflow内容发生变化时自动重置滚动区域尺寸Booleanfalse当设置为false时,内容发生变化需手动调用reflowScroller
    manual-init手动初始化Booleanfalse一般用于异步初始化的场景,需手动调用init方法完成初始化
    end-reached-threshold触发到达底部的提前量Number0单位px
    immediate-check-end-reaching 2.1.0+初始化时立即触发是否到达底部检查Booleanfalse-
    touch-angle 2.1.0+触发滚动的角度范围Number45单位deg
    is-prevent 2.3.0+阻止浏览器默认滚动Booleantrue如果设置为false,当在非可滚动角度范围触发滚动时会触发浏览器默认滚动

    ScrollView TouchAngle

    ScrollView 滚动区域/下拉刷新 - 图9

    ScrollViewRefresh Props

    属性说明类型默认值备注
    scroll-top距离顶部距离Number0单位px
    is-refresh-active释放可刷新状态Booleanfalse-
    is-refreshing刷新中状态Booleanfalse-
    refresh-text待刷新文案String下拉刷新-
    refresh-active-text释放可刷新文案String释放刷新-
    refreshing-text刷新中文案String刷新中…-
    roller-color 2.2.0+进度条颜色String#2F86F6-

    ScrollViewMore Props

    属性说明类型默认值备注
    is-finished全部已加载Booleanfalse-
    loading-text加载中文案String更多加载中…-
    finished-text全部已加载文案String全部已加载-

    ScrollView Slots

    default

    滚动区域内容插槽,当内容发生变化是,需要调用reflowScroller重置滚动区域,参考reflowScroller)

    refresh

    下拉刷新组件插槽,可如下使用slot-scoped获取相关滚动状态(不兼容slot-scoped时滚动状态也可通过事件中动态设置)

    <md-scroll-view-refresh
      slot="refresh"
      slot-scope="{ scrollTop, isRefreshActive, isRefreshing }"
      :scroll-top="scrollTop"
      :is-refreshing="isRefreshing"
      :is-refresh-active="isRefreshActive"
    ></md-scroll-view-refresh>
    more

    加载更多组件插槽

    header

    吸顶区域插槽

    吸底区域插槽

    ScrollView Methods

    init()

    初始化滚动区域,当manual-init设置为true时使用

    reflowScroller()

    重置滚动区域,一般滚动区域中的内容发生变化之后需调用

    scrollTo(left, top, animate = false)

    滚动至指定位置

    参数说明类型
    left距左侧距离Number
    top距顶部距离Number
    animate使用动画Boolean
    triggerRefresh()

    触发下拉刷新

    finishRefresh()

    停止下拉刷新

    finishLoadMore()

    停止加载更多

    ScrollView Events

    @scroll({scrollLeft, scrollTop})

    滚动事件

    属性说明类型
    scrollLeft距左侧距离Number
    scrollTop距顶部距离Number
    @refreshActive()

    释放可刷新事件

    @refreshing()

    刷新中事件

    @end-reached()

    滚动触底事件

    附录

    无法正常滚动且异常回弹

    首先,大多数滚动异常的情况是由于容器尺寸(垂直滚动:高度,水平滚动:宽度)的问题导致,容器的高度可以通过固定尺寸流式布局flex布局等多种方式控制,当容器尺寸不足时会导致内部Scroller初始化异常。当出现此类情况时,可通过浏览器元素查看器检查容器元素的.md-scroll-view高度是否正确。

    其次,确认是否存在动态变更滚动区域内容,导致滚动区域尺寸变化,此时需调用reflowScroller或者直接将auto-reflow设置为true

    下拉刷新后滚动无法触发endReached

    在组件内部下拉刷新上拉加载应该视为两个无关联的动作,因为动作内容有用户决定(业务逻辑),故无法确定下拉刷新一定是"刷新列表回第一页的状态",所以无法直接在下拉刷新的时候控制isEndReaching。该问题可以抽象为下拉刷新时需将上拉加载的状态重置,可以在refreshing事件去手动重置:

    $_onRefresh() {
      // 重置列表数据
      this.list = 10
      this.$refs.scrollView.finishRefresh()
      // 重置“上拉加载”的状态
      this.isFinished = false
      this.$refs.scrollView.finishLoadMore()
    }