2018-07-01 01:42:39 +08:00
|
|
|
<template>
|
|
|
|
<div
|
|
|
|
class="md-scroll-view"
|
|
|
|
@touchstart="$_onScollerTouchStart"
|
|
|
|
@touchmove="$_onScollerTouchMove"
|
|
|
|
@touchend="$_onScollerTouchEnd"
|
|
|
|
@touchcancel="$_onScollerTouchEnd"
|
|
|
|
@mousedown="$_onScollerMouseDown"
|
|
|
|
@mousemove="$_onScollerMouseMove"
|
|
|
|
@mouseup="$_onScollerMouseUp"
|
2018-07-12 17:59:30 +08:00
|
|
|
@mouseleave="$_onScollerMouseUp"
|
2018-07-01 01:42:39 +08:00
|
|
|
>
|
2018-08-28 11:23:44 +08:00
|
|
|
<div class="scroll-view-header" v-if="$slots.header">
|
|
|
|
<slot name="header"></slot>
|
|
|
|
</div>
|
2018-12-06 17:19:17 +08:00
|
|
|
<div
|
|
|
|
class="scroll-view-container"
|
|
|
|
:class="{
|
|
|
|
'horizon': scrollingX && !scrollingY
|
|
|
|
}"
|
|
|
|
>
|
2018-07-01 01:42:39 +08:00
|
|
|
<div
|
|
|
|
v-if="hasRefresher"
|
|
|
|
class="scroll-view-refresh"
|
2018-12-06 17:19:17 +08:00
|
|
|
:class="{
|
|
|
|
'refreshing': isRefreshing,
|
|
|
|
'refresh-active': isRefreshActive,
|
|
|
|
}"
|
2018-07-01 01:42:39 +08:00
|
|
|
:style="{top: `-${refreshOffsetY}px`}"
|
|
|
|
>
|
|
|
|
<slot
|
|
|
|
name="refresh"
|
2018-07-12 17:44:42 +08:00
|
|
|
:scroll-top="scrollY"
|
2018-07-01 01:42:39 +08:00
|
|
|
:is-refreshing="isRefreshing"
|
|
|
|
:is-refresh-active="isRefreshActive"
|
|
|
|
></slot>
|
|
|
|
</div>
|
|
|
|
<slot></slot>
|
2018-07-01 21:37:18 +08:00
|
|
|
<div
|
|
|
|
v-if="hasMore"
|
|
|
|
:is-end-reaching="isEndReaching"
|
2018-07-04 20:15:39 +08:00
|
|
|
:class="{active: isEndReaching}"
|
2018-07-01 21:37:18 +08:00
|
|
|
class="scroll-view-more"
|
|
|
|
>
|
|
|
|
<slot name="more"></slot>
|
|
|
|
</div>
|
2018-07-01 01:42:39 +08:00
|
|
|
</div>
|
2018-08-28 11:23:44 +08:00
|
|
|
<div class="scroll-view-footer" v-if="$slots.footer">
|
|
|
|
<slot name="footer"></slot>
|
|
|
|
</div>
|
2018-07-01 01:42:39 +08:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
2018-07-20 17:45:21 +08:00
|
|
|
<script>
import Scroller from '../_util/scroller'
|
2018-07-01 01:42:39 +08:00
|
|
|
import {render} from '../_util/render'
|
2018-08-01 10:58:34 +08:00
|
|
|
|
2018-07-01 01:42:39 +08:00
|
|
|
export default {
|
|
|
|
name: 'md-scroll-view',
|
|
|
|
props: {
|
|
|
|
scrollingX: {
|
|
|
|
type: Boolean,
|
|
|
|
default: true,
|
|
|
|
},
|
|
|
|
scrollingY: {
|
|
|
|
type: Boolean,
|
|
|
|
default: true,
|
|
|
|
},
|
2018-07-12 17:44:42 +08:00
|
|
|
bouncing: {
|
|
|
|
type: Boolean,
|
|
|
|
default: true,
|
|
|
|
},
|
2018-08-01 10:58:34 +08:00
|
|
|
autoReflow: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false,
|
|
|
|
},
|
2018-07-04 20:15:39 +08:00
|
|
|
endReachedThreshold: {
|
|
|
|
type: Number,
|
|
|
|
default: 0,
|
|
|
|
},
|
2018-07-01 01:42:39 +08:00
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
container: null,
|
|
|
|
content: null,
|
|
|
|
refresher: null,
|
2018-07-01 21:37:18 +08:00
|
|
|
more: null,
|
2018-07-01 01:42:39 +08:00
|
|
|
scroller: null,
|
2018-07-01 21:37:18 +08:00
|
|
|
refreshOffsetY: 0,
|
2018-07-01 01:42:39 +08:00
|
|
|
isInitialed: false,
|
|
|
|
isMouseDown: false,
|
|
|
|
isRefreshing: false,
|
|
|
|
isRefreshActive: false,
|
2018-07-01 21:37:18 +08:00
|
|
|
isEndReaching: false,
|
2018-07-01 01:42:39 +08:00
|
|
|
scrollX: null,
|
|
|
|
scrollY: null,
|
2018-08-01 10:58:34 +08:00
|
|
|
containerW: 0,
|
|
|
|
containerH: 0,
|
|
|
|
contentW: 0,
|
|
|
|
contentH: 0,
|
|
|
|
reflowTimer: null,
|
2018-07-01 01:42:39 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
hasRefresher() {
|
|
|
|
return !!(this.$slots.refresh || this.$scopedSlots.refresh)
|
|
|
|
},
|
2018-07-01 21:37:18 +08:00
|
|
|
hasMore() {
|
|
|
|
return !!(this.$slots.more || this.$scopedSlots.more)
|
2018-07-01 01:42:39 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
mounted() {
|
|
|
|
this.$_initScroller()
|
2018-08-01 10:58:34 +08:00
|
|
|
this.autoReflow && this.$_initAutoReflow()
|
|
|
|
},
|
|
|
|
destroyed() {
|
|
|
|
this.reflowTimer && clearInterval(this.reflowTimer)
|
2018-07-01 01:42:39 +08:00
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
$_initScroller() {
|
2018-07-01 21:37:18 +08:00
|
|
|
this.container = this.$el
|
|
|
|
this.refresher = this.$el.querySelector('.scroll-view-refresh')
|
2018-07-04 20:15:39 +08:00
|
|
|
this.more = this.$el.querySelector('.scroll-view-more')
|
2018-07-01 21:37:18 +08:00
|
|
|
this.content = this.$el.querySelector('.scroll-view-container')
|
|
|
|
this.refreshOffsetY = this.refresher ? this.refresher.clientHeight : 0
|
2018-07-04 20:15:39 +08:00
|
|
|
this.moreOffsetY = this.more ? this.more.clientHeight : 0
|
2018-07-01 01:42:39 +08:00
|
|
|
const container = this.container
|
|
|
|
const content = this.content
|
|
|
|
const rect = container.getBoundingClientRect()
|
|
|
|
const scroller = new Scroller(
|
|
|
|
(left, top) => {
|
|
|
|
render(content, left, top)
|
|
|
|
if (this.isInitialed) {
|
|
|
|
this.$_onScroll(left, top)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
scrollingX: this.scrollingX,
|
|
|
|
scrollingY: this.scrollingY,
|
2018-07-12 17:44:42 +08:00
|
|
|
bouncing: this.bouncing,
|
2018-07-01 01:42:39 +08:00
|
|
|
zooming: false,
|
2018-07-10 18:29:07 +08:00
|
|
|
animationDuration: 200,
|
2018-07-20 17:45:21 +08:00
|
|
|
inRequestAnimationFrame: true,
|
2018-07-01 01:42:39 +08:00
|
|
|
},
|
|
|
|
)
|
|
|
|
scroller.setPosition(rect.left + container.clientLeft, rect.top + container.clientTop)
|
|
|
|
if (this.hasRefresher) {
|
|
|
|
scroller.activatePullToRefresh(
|
|
|
|
this.refreshOffsetY,
|
|
|
|
() => {
|
|
|
|
this.isRefreshActive = true
|
|
|
|
this.isRefreshing = false
|
|
|
|
},
|
|
|
|
() => {
|
|
|
|
this.isRefreshActive = false
|
|
|
|
this.isRefreshing = false
|
|
|
|
this.$emit('refreshActive')
|
|
|
|
},
|
|
|
|
() => {
|
|
|
|
this.isRefreshActive = false
|
|
|
|
this.isRefreshing = true
|
|
|
|
this.$emit('refreshing')
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
this.scroller = scroller
|
2018-08-15 20:38:02 +08:00
|
|
|
this.reflowScroller(true)
|
2018-07-04 20:15:39 +08:00
|
|
|
setTimeout(() => {
|
|
|
|
this.isInitialed = true
|
|
|
|
}, 50)
|
2018-07-01 01:42:39 +08:00
|
|
|
},
|
2018-08-01 10:58:34 +08:00
|
|
|
$_initAutoReflow() {
|
|
|
|
this.reflowTimer = setInterval(() => {
|
|
|
|
this.reflowScroller()
|
|
|
|
}, 100)
|
|
|
|
},
|
2018-07-01 01:42:39 +08:00
|
|
|
// MARK: events handler
|
|
|
|
$_onScollerTouchStart(event) {
|
2018-08-15 14:38:55 +08:00
|
|
|
// event.target.tagName && event.target.tagName.match(/input|textarea|select/i)
|
|
|
|
if (!this.scroller) {
|
2018-07-01 01:42:39 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
this.scroller.doTouchStart(event.touches, event.timeStamp)
|
|
|
|
},
|
|
|
|
$_onScollerTouchMove(event) {
|
|
|
|
if (!this.scroller) {
|
|
|
|
return
|
|
|
|
}
|
2018-07-24 13:16:28 +08:00
|
|
|
event.preventDefault()
|
2018-07-01 01:42:39 +08:00
|
|
|
this.scroller.doTouchMove(event.touches, event.timeStamp, event.scale)
|
|
|
|
},
|
|
|
|
$_onScollerTouchEnd(event) {
|
|
|
|
if (!this.scroller) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.scroller.doTouchEnd(event.timeStamp)
|
|
|
|
},
|
|
|
|
$_onScollerMouseDown(event) {
|
2018-08-15 14:38:55 +08:00
|
|
|
if (!this.scroller) {
|
2018-07-01 01:42:39 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
this.scroller.doTouchStart(
|
|
|
|
[
|
|
|
|
{
|
|
|
|
pageX: event.pageX,
|
|
|
|
pageY: event.pageY,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
event.timeStamp,
|
|
|
|
)
|
|
|
|
this.isMouseDown = true
|
|
|
|
},
|
|
|
|
$_onScollerMouseMove(event) {
|
|
|
|
if (!this.scroller || !this.isMouseDown) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.scroller.doTouchMove(
|
|
|
|
[
|
|
|
|
{
|
|
|
|
pageX: event.pageX,
|
|
|
|
pageY: event.pageY,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
event.timeStamp,
|
|
|
|
)
|
|
|
|
this.isMouseDown = true
|
|
|
|
},
|
|
|
|
$_onScollerMouseUp(event) {
|
|
|
|
if (!this.scroller || !this.isMouseDown) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.scroller.doTouchEnd(event.timeStamp)
|
|
|
|
this.isMouseDown = false
|
|
|
|
},
|
|
|
|
$_onScroll(left, top) {
|
|
|
|
left = +left.toFixed(2)
|
|
|
|
top = +top.toFixed(2)
|
|
|
|
if (this.scrollX === left && this.scrollY === top) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.scrollX = left
|
|
|
|
this.scrollY = top
|
2018-07-04 20:15:39 +08:00
|
|
|
const containerHeight = this.scroller._clientHeight
|
|
|
|
const content = this.scroller._contentHeight
|
|
|
|
const moreOffsetY = this.moreOffsetY
|
|
|
|
const moreThreshold = this.endReachedThreshold
|
|
|
|
if (
|
|
|
|
top > 0 &&
|
|
|
|
!this.isEndReaching &&
|
|
|
|
content > containerHeight &&
|
|
|
|
content - containerHeight <= top + moreOffsetY + moreThreshold
|
|
|
|
) {
|
2018-07-01 21:37:18 +08:00
|
|
|
this.isEndReaching = true
|
|
|
|
this.$emit('endReached')
|
|
|
|
}
|
2018-07-12 17:44:42 +08:00
|
|
|
this.$emit('scroll', {scrollLeft: left, scrollTop: top})
|
2018-07-01 01:42:39 +08:00
|
|
|
},
|
2018-07-10 18:29:07 +08:00
|
|
|
scrollTo(left, top, animate = false) {
|
|
|
|
if (!this.scroller) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.scroller.scrollTo(left, top, animate)
|
|
|
|
},
|
2018-08-15 20:38:02 +08:00
|
|
|
reflowScroller(force = false) {
|
2018-07-01 21:37:18 +08:00
|
|
|
const container = this.container
|
|
|
|
const content = this.content
|
|
|
|
if (!this.scroller || !container || !content) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.$nextTick(() => {
|
2018-08-01 10:58:34 +08:00
|
|
|
const containerW = container.clientWidth
|
|
|
|
const containerH = container.clientHeight
|
|
|
|
const contentW = content.offsetWidth
|
|
|
|
const contentH = content.offsetHeight
|
|
|
|
|
2018-08-15 20:38:02 +08:00
|
|
|
if (
|
|
|
|
force ||
|
|
|
|
this.containerW !== containerW ||
|
|
|
|
this.containerH !== containerH ||
|
|
|
|
this.contentW !== contentW ||
|
|
|
|
this.contentH !== contentH
|
|
|
|
) {
|
|
|
|
this.scroller.setDimensions(
|
|
|
|
container.clientWidth,
|
|
|
|
container.clientHeight,
|
|
|
|
content.offsetWidth,
|
|
|
|
content.offsetHeight,
|
|
|
|
)
|
|
|
|
this.containerW = containerW
|
|
|
|
this.containerH = containerH
|
|
|
|
this.contentW = contentW
|
|
|
|
this.contentH = contentH
|
|
|
|
}
|
2018-07-01 21:37:18 +08:00
|
|
|
})
|
|
|
|
},
|
2018-07-01 01:42:39 +08:00
|
|
|
triggerRefresh() {
|
|
|
|
if (!this.scroller) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.scroller.triggerPullToRefresh()
|
|
|
|
},
|
|
|
|
finishRefresh() {
|
|
|
|
if (!this.scroller) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.scroller.finishPullToRefresh()
|
2018-07-04 20:15:39 +08:00
|
|
|
this.reflowScroller()
|
|
|
|
},
|
|
|
|
finishLoadMore() {
|
|
|
|
if (!this.scroller) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.isEndReaching = false
|
|
|
|
this.reflowScroller()
|
2018-07-01 01:42:39 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2018-07-20 17:45:21 +08:00
|
|
|
</script>
|
2018-07-01 01:42:39 +08:00
|
|
|
|
|
|
|
<style lang="stylus">
|
|
|
|
.md-scroll-view
|
2018-08-28 11:23:44 +08:00
|
|
|
position relative
|
2018-07-01 01:42:39 +08:00
|
|
|
display block
|
|
|
|
height 100%
|
|
|
|
overflow hidden
|
|
|
|
user-select none
|
2018-08-28 11:23:44 +08:00
|
|
|
.scroll-view-header, .scroll-view-footer
|
|
|
|
position absolute
|
|
|
|
left 0
|
|
|
|
right 0
|
2018-10-25 00:38:31 +08:00
|
|
|
z-index 2
|
2018-08-28 11:23:44 +08:00
|
|
|
.scroll-view-header
|
|
|
|
top 0
|
|
|
|
.scroll-view-footer
|
|
|
|
bottom 0
|
2018-07-01 01:42:39 +08:00
|
|
|
.scroll-view-container
|
|
|
|
clearfix()
|
|
|
|
position relative
|
|
|
|
// display inline-block
|
|
|
|
.scroll-view-refresh
|
|
|
|
clearfix()
|
|
|
|
position absolute
|
|
|
|
left 0
|
|
|
|
right 0
|
2018-07-04 20:15:39 +08:00
|
|
|
.scroll-view-more
|
|
|
|
visibility hidden
|
|
|
|
&.active
|
|
|
|
visibility visible
|
2018-12-06 17:19:17 +08:00
|
|
|
&.horizon
|
|
|
|
display inline-block
|
2018-07-12 17:44:42 +08:00
|
|
|
</style>
|