ScrollView 滚动区域/下拉刷新
用于模拟原生的滚动区域,并支持下拉刷新和加载更多
引入
import { ScrollView, ScrollViewRefresh, ScrollViewMore } from 'mand-mobile'
Vue.component(ScrollView.name, ScrollView)
使用指南
ScrollViewRefresh
为组件库内置的下拉刷新组件,仅用于作为视觉展示,需在插槽refresh)中使用,下拉刷新组件也可自定义ScrollViewMore
为组件库内置的加载更多组件,仅用于作为视觉展示,需在插槽more)中使用,加载更多组件也可自定义组件容器需具有高度,否则会出现无法滚动或回弹问题。更多使用的常见问题请查看附录)
代码演示
基础 添加内容
请在移动设备中扫码预览
<template>
<div class="md-example-child md-example-child-scroll-view md-example-child-scroll-view-0">
<md-scroll-view
ref="scrollView"
@scroll="$_onScroll"
>
<div
v-for="i in list"
class="scroll-view-item"
:key="i"
@click="$_onItemClick(i)"
>
{{i}}
</div>
</md-scroll-view>
</div>
</template>
<script>
import {ScrollView, Toast} from 'mand-mobile'
export default {
name: 'scroll-view-demo-0',
components: {
[ScrollView.name]: ScrollView,
},
data() {
return {
list: 5,
}
},
mounted() {
window.ScrollViewTrigger0 = () => {
this.addItems()
}
},
methods: {
$_onItemClick(i) {
Toast.info(`Click ${i}`)
},
$_onScroll({scrollLeft, scrollTop}) {
console.log(`[Mand Mobile ScrollView - demo0 - onScroll] scrollLeft: ${scrollLeft}, scrollTop: ${scrollTop}`)
},
addItems() {
this.list += 5
// 如果把autoReflow设置为false, 需调用reflowScroller
this.$refs.scrollView.reflowScroller()
},
},
}
</script>
<style lang="stylus">
.md-example-child-scroll-view-0
height 400px
background #FFF
.scroll-view-item
padding 30px 0
text-align center
font-size 28px
font-family DINAlternate-Bold
border-bottom .5px solid #efefef
</style>
下拉刷新 触发下拉刷新
请在移动设备中扫码预览
<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>
粘性标题
请在移动设备中扫码预览
<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>
手动初始化
请在移动设备中扫码预览
<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
<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>
加载更多
请在移动设备中扫码预览
<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
请在移动设备中扫码预览
<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 | 水平滚动 | Boolean | true | - |
scrolling-y | 垂直滚动 | Boolean | true | - |
bouncing | 可回弹 | Boolean | true | - |
auto-reflow | 内容发生变化时自动重置滚动区域尺寸 | Boolean | false | 当设置为false 时,内容发生变化需手动调用reflowScroller |
manual-init | 手动初始化 | Boolean | false | 一般用于异步初始化的场景,需手动调用init 方法完成初始化 |
end-reached-threshold | 触发到达底部的提前量 | Number | 0 | 单位px |
immediate-check-end-reaching 2.1.0+ | 初始化时立即触发是否到达底部检查 | Boolean | false | - |
touch-angle 2.1.0+ | 触发滚动的角度范围 | Number | 45 | 单位deg |
is-prevent 2.3.0+ | 阻止浏览器默认滚动 | Boolean | true | 如果设置为false ,当在非可滚动角度范围触发滚动时会触发浏览器默认滚动 |
ScrollView TouchAngle
ScrollViewRefresh Props
属性 | 说明 | 类型 | 默认值 | 备注 |
---|---|---|---|---|
scroll-top | 距离顶部距离 | Number | 0 | 单位px |
is-refresh-active | 释放可刷新状态 | Boolean | false | - |
is-refreshing | 刷新中状态 | Boolean | false | - |
refresh-text | 待刷新文案 | String | 下拉刷新 | - |
refresh-active-text | 释放可刷新文案 | String | 释放刷新 | - |
refreshing-text | 刷新中文案 | String | 刷新中… | - |
roller-color 2.2.0+ | 进度条颜色 | String | #2F86F6 | - |
ScrollViewMore Props
属性 | 说明 | 类型 | 默认值 | 备注 |
---|---|---|---|---|
is-finished | 全部已加载 | Boolean | false | - |
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
吸顶区域插槽
footer
吸底区域插槽
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()
}