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"
|
|
|
|
>
|
|
|
|
<div class="scroll-view-container">
|
|
|
|
<div
|
|
|
|
v-if="hasRefresher"
|
|
|
|
class="scroll-view-refresh"
|
|
|
|
:class="{'refreshing': isRefreshing, 'refresh-active': isRefreshActive}"
|
|
|
|
:style="{top: `-${refreshOffsetY}px`}"
|
|
|
|
>
|
|
|
|
<slot
|
|
|
|
name="refresh"
|
|
|
|
:scroll-y="scrollY"
|
|
|
|
: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"
|
|
|
|
class="scroll-view-more"
|
|
|
|
>
|
|
|
|
<slot name="more"></slot>
|
|
|
|
</div>
|
2018-07-01 01:42:39 +08:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
import Scroller from '../_util/scroller'
|
|
|
|
import {render} from '../_util/render'
|
|
|
|
|
|
|
|
export default {
|
|
|
|
name: 'md-scroll-view',
|
|
|
|
|
|
|
|
props: {
|
|
|
|
scrollingX: {
|
|
|
|
type: Boolean,
|
|
|
|
default: true,
|
|
|
|
},
|
|
|
|
scrollingY: {
|
|
|
|
type: Boolean,
|
|
|
|
default: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
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() {
|
2018-07-01 21:37:18 +08:00
|
|
|
window.addEventListener(
|
|
|
|
'resize',
|
|
|
|
() => {
|
|
|
|
this.$_initScroller()
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
)
|
2018-07-01 01:42:39 +08:00
|
|
|
this.$_initScroller()
|
|
|
|
},
|
|
|
|
|
|
|
|
methods: {
|
|
|
|
$_initScroller() {
|
2018-07-01 21:37:18 +08:00
|
|
|
this.container = this.$el
|
|
|
|
this.refresher = this.$el.querySelector('.scroll-view-refresh')
|
|
|
|
this.content = this.$el.querySelector('.scroll-view-container')
|
|
|
|
this.refreshOffsetY = this.refresher ? this.refresher.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,
|
|
|
|
zooming: false,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
this.$nextTick(() => {
|
|
|
|
this.isInitialed = true
|
|
|
|
})
|
2018-07-01 21:37:18 +08:00
|
|
|
this.reflowScroller()
|
2018-07-01 01:42:39 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
// MARK: events handler
|
|
|
|
$_onScollerTouchStart(event) {
|
|
|
|
if (
|
|
|
|
!this.scroller ||
|
|
|
|
(event.touches[0] && event.touches[0].target && event.touches[0].target.tagName.match(/input|textarea|select/i))
|
|
|
|
) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.scroller.doTouchStart(event.touches, event.timeStamp)
|
|
|
|
event.preventDefault()
|
|
|
|
},
|
|
|
|
$_onScollerTouchMove(event) {
|
|
|
|
if (!this.scroller) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.scroller.doTouchMove(event.touches, event.timeStamp, event.scale)
|
|
|
|
},
|
|
|
|
$_onScollerTouchEnd(event) {
|
|
|
|
if (!this.scroller) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.scroller.doTouchEnd(event.timeStamp)
|
|
|
|
},
|
|
|
|
$_onScollerMouseDown(event) {
|
|
|
|
if (!this.scroller || event.target.tagName.match(/input|textarea|select/i)) {
|
|
|
|
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-01 21:37:18 +08:00
|
|
|
|
|
|
|
const containerHeight = this.container.clientHeight
|
|
|
|
const content = this.content.offsetHeight
|
|
|
|
if (top > 0 && !this.isEndReaching && content > containerHeight && content - containerHeight <= top) {
|
|
|
|
console.log('xxxxxx', top)
|
|
|
|
this.isEndReaching = true
|
|
|
|
this.$emit('endReached')
|
|
|
|
}
|
2018-07-01 01:42:39 +08:00
|
|
|
this.$emit('scroll', {scrollX: left, scrollY: top})
|
|
|
|
},
|
|
|
|
|
2018-07-01 21:37:18 +08:00
|
|
|
reflowScroller() {
|
|
|
|
const container = this.container
|
|
|
|
const content = this.content
|
|
|
|
|
|
|
|
if (!this.scroller || !container || !content) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
this.scroller.setDimensions(
|
|
|
|
container.clientWidth,
|
|
|
|
container.clientHeight,
|
|
|
|
content.offsetWidth,
|
|
|
|
content.offsetHeight,
|
|
|
|
)
|
|
|
|
this.isEndReaching = false
|
|
|
|
})
|
|
|
|
},
|
2018-07-01 01:42:39 +08:00
|
|
|
triggerRefresh() {
|
|
|
|
if (!this.scroller) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.scroller.triggerPullToRefresh()
|
|
|
|
},
|
|
|
|
finishRefresh() {
|
|
|
|
if (!this.scroller) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.scroller.finishPullToRefresh()
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="stylus">
|
|
|
|
.md-scroll-view
|
|
|
|
display block
|
|
|
|
// width 100%
|
|
|
|
height 100%
|
|
|
|
background #fff
|
|
|
|
overflow hidden
|
|
|
|
user-select none
|
|
|
|
.scroll-view-container
|
|
|
|
clearfix()
|
|
|
|
position relative
|
|
|
|
// display inline-block
|
|
|
|
.scroll-view-refresh
|
|
|
|
clearfix()
|
|
|
|
position absolute
|
|
|
|
left 0
|
|
|
|
right 0
|
|
|
|
</style>
|