mand-mobile/components/scroll-view/index.vue

341 lines
8.5 KiB
Vue
Raw Normal View History

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"
@mouseleave="$_onScollerMouseUp"
2018-07-01 01:42:39 +08:00
>
<div class="scroll-view-header" v-if="$slots.header">
<slot name="header"></slot>
</div>
2018-07-01 01:42:39 +08:00
<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-top="scrollY"
2018-07-01 01:42:39 +08:00
:is-refreshing="isRefreshing"
:is-refresh-active="isRefreshActive"
></slot>
</div>
<slot></slot>
<div
v-if="hasMore"
:is-end-reaching="isEndReaching"
:class="{active: isEndReaching}"
class="scroll-view-more"
>
<slot name="more"></slot>
</div>
2018-07-01 01:42:39 +08:00
</div>
<div class="scroll-view-footer" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
2018-07-01 01:42:39 +08:00
</div>
</template>
<script> import Scroller from '../_util/scroller'
2018-07-01 01:42:39 +08:00
import {render} from '../_util/render'
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,
},
bouncing: {
type: Boolean,
default: true,
},
autoReflow: {
type: Boolean,
default: false,
},
endReachedThreshold: {
type: Number,
default: 0,
},
2018-07-01 01:42:39 +08:00
},
data() {
return {
container: null,
content: null,
refresher: null,
more: null,
2018-07-01 01:42:39 +08:00
scroller: null,
refreshOffsetY: 0,
2018-07-01 01:42:39 +08:00
isInitialed: false,
isMouseDown: false,
isRefreshing: false,
isRefreshActive: false,
isEndReaching: false,
2018-07-01 01:42:39 +08:00
scrollX: null,
scrollY: null,
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)
},
hasMore() {
return !!(this.$slots.more || this.$scopedSlots.more)
2018-07-01 01:42:39 +08:00
},
},
mounted() {
this.$_initScroller()
this.autoReflow && this.$_initAutoReflow()
},
destroyed() {
this.reflowTimer && clearInterval(this.reflowTimer)
2018-07-01 01:42:39 +08:00
},
methods: {
$_initScroller() {
this.container = this.$el
this.refresher = this.$el.querySelector('.scroll-view-refresh')
this.more = this.$el.querySelector('.scroll-view-more')
this.content = this.$el.querySelector('.scroll-view-container')
this.refreshOffsetY = this.refresher ? this.refresher.clientHeight : 0
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,
bouncing: this.bouncing,
2018-07-01 01:42:39 +08:00
zooming: false,
animationDuration: 200,
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
this.reflowScroller(true)
setTimeout(() => {
this.isInitialed = true
}, 50)
2018-07-01 01:42:39 +08:00
},
$_initAutoReflow() {
this.reflowTimer = setInterval(() => {
this.reflowScroller()
}, 100)
},
2018-07-01 01:42:39 +08:00
// MARK: events handler
$_onScollerTouchStart(event) {
// 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
}
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) {
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
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
) {
this.isEndReaching = true
this.$emit('endReached')
}
this.$emit('scroll', {scrollLeft: left, scrollTop: top})
2018-07-01 01:42:39 +08:00
},
scrollTo(left, top, animate = false) {
if (!this.scroller) {
return
}
this.scroller.scrollTo(left, top, animate)
},
reflowScroller(force = false) {
const container = this.container
const content = this.content
if (!this.scroller || !container || !content) {
return
}
this.$nextTick(() => {
const containerW = container.clientWidth
const containerH = container.clientHeight
const contentW = content.offsetWidth
const contentH = content.offsetHeight
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 01:42:39 +08:00
triggerRefresh() {
if (!this.scroller) {
return
}
this.scroller.triggerPullToRefresh()
},
finishRefresh() {
if (!this.scroller) {
return
}
this.scroller.finishPullToRefresh()
this.reflowScroller()
},
finishLoadMore() {
if (!this.scroller) {
return
}
this.isEndReaching = false
this.reflowScroller()
2018-07-01 01:42:39 +08:00
},
},
}
</script>
2018-07-01 01:42:39 +08:00
<style lang="stylus">
.md-scroll-view
position relative
2018-07-01 01:42:39 +08:00
display block
// width 100%
height 100%
background #fff
overflow hidden
user-select none
.scroll-view-header, .scroll-view-footer
position absolute
left 0
right 0
.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
.scroll-view-more
visibility hidden
&.active
visibility visible
</style>