asia-weex-vue-render4light
Version:
Web renderer for weex project written in Vue DSL.
350 lines (324 loc) • 10.7 kB
JavaScript
/*
* 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()
const evtObj = {
contentSize: { width: rect.width, height: rect.height },
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
}
}
if (context.$el) {
weex.utils.dispatchNativeEvent(context.$el, 'weex$scroll', evtObj)
}
}
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
},
mounted () {
this.reloadStickyChildren()
},
updated () {
this.reloadStickyChildren()
},
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.
*/
const stickyChildren = this._stickyChildren
const len = stickyChildren && stickyChildren.length || 0
if (len <= 0) { return }
const origSticky = weex.utils.supportSticky()
// current only support list and vertical scroller.
if (this.scrollDirection === 'horizontal') {
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 (origSticky) {
this.addSticky(stickyChild, origSticky)
}
else if (stickyChild._initOffsetTop < scrollTop) {
this.addSticky(stickyChild)
}
else {
this.removeSticky(stickyChild)
}
}
},
addSticky (el, supportSticky) {
if (supportSticky) {
el.classList.add('weex-ios-sticky')
}
else {
if (el._sticky === true) return
if (process.env.NODE_ENV !== 'production') {
console.log(`[vue-render] header add sticky`, el)
}
el._sticky = true
if (!el._placeholder) {
const placeholder = el.cloneNode(true)
placeholder._origNode = el
placeholder.classList.add('weex-sticky-placeholder')
el._placeholder = placeholder
}
el.parentNode.insertBefore(el._placeholder, el)
el.style.width = window.getComputedStyle(el).width
el.classList.add('weex-sticky')
}
},
removeSticky (el) {
if (
typeof el._sticky === 'undefined'
|| el._sticky === false
) {
return
}
if (process.env.NODE_ENV !== 'production') {
console.log(`[vue-render] header remove sticky`, el)
}
el._sticky = false
el.parentNode.removeChild(el._placeholder)
el.classList.remove('weex-sticky')
},
reloadStickyChildren () {
const container = this.$el
if (!container) return
const stickyChildren = []
const children = container.querySelectorAll('[sticky]')
for (let i = 0, l = children.length; i < l; i++) {
const child = children[i]
if (/weex-sticky-placeholder/.test(child.className)) { // is a placeholder.
const origNode = child._origNode
if (
!origNode
|| !origNode.parentNode
|| origNode.parentNode !== child.parentNode
) {
child.parentNode.removeChild(child)
}
}
else { // is a sticky node.
stickyChildren.push(child)
if (!child._sticky) {
child._initOffsetTop = child.offsetTop
}
}
}
this._stickyChildren = stickyChildren
},
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
const el = this.$el
if (el) {
weex.utils.dispatchNativeEvent(el, 'loadmore')
}
}
}
},
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
}
}
}