blob: 19ffe7409e01c043be3b2a5419be513e9f2bd480 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
const DEFAULT_OFFSET_ACCURACY = 10
const DEFAULT_LOADMORE_OFFSET = 0
function getThrottledScroll (context) {
const scale = weex.config.env.scale
if (!context._throttleScroll) {
const wrapper = context.$refs.wrapper
const inner = context.$refs.inner
let preOffset = (context.scrollDirection === 'horizontal'
? wrapper.scrollLeft
: wrapper.scrollTop)
|| 0
context._throttleScroll = weex.utils.throttle(function (evt) {
const offset = context.scrollDirection === 'horizontal'
? wrapper.scrollLeft
: wrapper.scrollTop
const indent = parseInt(context.offsetAccuracy) * scale
function triggerScroll () {
const rect = inner.getBoundingClientRect()
evt.contentSize = { width: rect.width, height: rect.height }
evt.contentOffset = {
x: wrapper.scrollLeft,
/**
* positive direciton for y-axis is down.
* so should use negative operation on scrollTop.
*
* (0,0)---------------> x
* |
* |
* |
* |
* v y
*
*/
y: -wrapper.scrollTop
}
context.$emit('scroll', evt)
}
if (Math.abs(offset - preOffset) >= indent) {
triggerScroll()
preOffset = offset
}
}, 16, true)
}
return context._throttleScroll
}
export default {
props: {
loadmoreoffset: {
type: [String, Number],
default: DEFAULT_LOADMORE_OFFSET,
validator (value) {
const val = parseInt(value)
return !isNaN(val) && val >= DEFAULT_LOADMORE_OFFSET
}
},
offsetAccuracy: {
type: [Number, String],
default: DEFAULT_OFFSET_ACCURACY,
validator (value) {
const val = parseInt(value)
return !isNaN(val) && val >= DEFAULT_OFFSET_ACCURACY
}
}
},
created () {
// should call resetLoadmore() to enable loadmore event.
this._loadmoreReset = true
},
methods: {
updateLayout () {
const wrapper = this.$refs.wrapper
if (wrapper) {
const rect = wrapper.getBoundingClientRect()
this._wrapperWidth = rect.width
this._wrapperHeight = rect.height
}
const inner = this.$refs.inner
const children = inner && inner.children
if (inner) {
const rect = inner.getBoundingClientRect()
this._innerWidth = rect.width
this._innerHeight = rect.height
}
const loadingEl = this._loading && this._loading.$el
const refreshEl = this._refresh && this._refresh.$el
if (loadingEl) {
this._innerHeight -= loadingEl.getBoundingClientRect().height
}
if (refreshEl) {
this._innerHeight -= refreshEl.getBoundingClientRect().height
}
// inner width is always the viewport width somehow in horizontal
// scoller, therefore the inner width should be reclaculated.
if (this.scrollDirection === 'horizontal' && children) {
this._innerWidth = weex.utils.getRangeWidth(inner)
}
},
resetLoadmore () {
this._loadmoreReset = true
},
/**
* process sticky children in scrollable components.
* current only support list and vertical scroller.
*/
processSticky () {
/**
* current browser support 'sticky' or '-webkit-sticky', so there's no need
* to do further more.
*/
if (weex.utils.supportSticky()) {
return
}
// current only support list and vertical scroller.
if (this.scrollDirection === 'horizontal') {
return
}
const stickyChildren = this._stickyChildren
const len = stickyChildren && stickyChildren.length || 0
if (len <= 0) { return }
const container = this.$el
if (!container) { return }
const scrollTop = container.scrollTop
let stickyChild
for (let i = 0; i < len; i++) {
stickyChild = stickyChildren[i]
if (stickyChild._initOffsetTop < scrollTop) {
stickyChild._addSticky()
}
else {
stickyChild._removeSticky()
}
}
},
handleScroll (event) {
weex.utils.getThrottleLazyload(25, this.$el, 'scroll')()
getThrottledScroll(this)(event)
this.processSticky()
// fire loadmore event.
const inner = this.$refs.inner
if (inner) {
const innerLength = this.scrollDirection === 'horizontal'
? this._innerWidth
: this._innerHeight
if (!this._innerLength) {
this._innerLength = innerLength
}
if (this._innerLength !== innerLength) {
this._innerLength = innerLength
this._loadmoreReset = true
}
if (this._loadmoreReset && this.reachBottom(this.loadmoreoffset)) {
this._loadmoreReset = false
this.$emit('loadmore', event)
}
}
},
reachTop () {
const wrapper = this.$refs.wrapper
return (!!wrapper) && (wrapper.scrollTop <= 0)
},
reachBottom (offset) {
const wrapper = this.$refs.wrapper
const inner = this.$refs.inner
offset = parseInt(offset || 0) * weex.config.env.scale
if (wrapper && inner) {
const key = this.scrollDirection === 'horizontal'
? 'width'
: 'height'
const innerLength = this[`_inner${key[0].toUpperCase()}${key.substr(1)}`]
const wrapperLength = this[`_wrapper${key[0].toUpperCase()}${key.substr(1)}`]
const scrollOffset = this.scrollDirection === 'horizontal'
? wrapper.scrollLeft
: wrapper.scrollTop
return scrollOffset >= innerLength - wrapperLength - offset
}
return false
},
handleTouchStart (event) {
if (this._loading || this._refresh) {
const touch = event.changedTouches[0]
this._touchParams = {
reachTop: this.reachTop(),
reachBottom: this.reachBottom(),
startTouchEvent: touch,
startX: touch.pageX,
startY: touch.pageY,
timeStamp: event.timeStamp
}
}
},
handleTouchMove (event) {
if (!this._touchParams || !this._refresh && !this._loading) {
return
}
const inner = this.$refs.inner
const { startY, reachTop, reachBottom } = this._touchParams
if (inner) {
const touch = event.changedTouches[0]
const offsetY = touch.pageY - startY
const dir = offsetY > 0 ? 'down' : 'up'
this._touchParams.offsetY = offsetY
if (this._refresh && (dir === 'down') && reachTop) {
this._refresh.pullingDown(offsetY)
}
else if (this._loading && (dir === 'up') && reachBottom) {
this._loading.pullingUp(-offsetY)
}
}
},
handleTouchEnd (event) {
if (!this._touchParams || !this._refresh && !this._loading) {
return
}
const inner = this.$refs.inner
const { startY, reachTop, reachBottom } = this._touchParams
if (inner) {
const touch = event.changedTouches[0]
const offsetY = touch.pageY - startY
const dir = offsetY > 0 ? 'down' : 'up'
this._touchParams.offsetY = offsetY
if (this._refresh && (dir === 'down') && reachTop) {
this._refresh.pullingEnd()
}
else if (this._loading && (dir === 'up') && reachBottom) {
this._loading.pullingEnd()
}
}
delete this._touchParams
}
}
}