blob: 035443da99f6082defa60fd3776615b5debf7e76 [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.
*/
import config from '../config'
const utils = {}
function getParentScroller (vnode) {
if (!vnode) return null
const vm = vnode.$el
? vnode : vnode.elm
? vnode.componentInstance || vnode.context : null
if (!vm) return null
const type = vm.$el && vm.$el.getAttribute('weex-type')
if (config.scrollableTypes.indexOf(type) > -1) {
return vm
}
return getParentScroller(vm.$parent)
}
function now () {
const now = window.performance && window.performance.now
? window.performance.now.bind(window.performance) : Date.now
return now()
}
function scrollElement (dSuffix, position) {
this[`scroll${dSuffix}`] = position
}
/**
* self invoked function that, given a context, steps through scrolling
* @method step
* @param {Object} context
*/
function step (context) {
// call method again on next available frame
context.frame = window.requestAnimationFrame(step.bind(window, context))
const time = now()
let elapsed = (time - context.startTime) / 468
// avoid elapsed times higher than one
elapsed = elapsed > 1 ? 1 : elapsed
// apply easing to elapsed time
const value = ease(elapsed)
const currentPosition = context.startPosition + (context.position - context.startPosition) * value
context.method.call(context.scrollable, context.dSuffix, currentPosition)
// return when end points have been reached
/**
* NOTE: should use ~~ to parse position number into integer. Otherwise
* this two float numbers maybe have a slicely little difference, which
* will cause this function never to stop.
*/
if (~~currentPosition === ~~context.position) {
window.cancelAnimationFrame(context.frame)
return
}
}
/**
* returns result of applying ease math function to a number
* @method ease
* @param {Number} k
* @returns {Number}
*/
function ease (k) {
return 0.5 * (1 - Math.cos(Math.PI * k))
}
const dom = {
/**
* scrollToElement
* @param {Vnode | VComponent} vnode
* @param {Object} options {offset:Number}
* ps: scroll-to has 'ease' and 'duration'(ms) as options.
*/
scrollToElement: function (vnode, options) {
const {
isArray
} = utils
if (isArray(vnode)) {
if (process.env.NODE_ENV === 'development') {
console.warn('[vue-render] the ref passed to animation.transitionOnce is a array.')
}
vnode = vnode[0]
}
const scroller = getParentScroller(vnode)
const scrollDirection = scroller && scroller.scrollDirection || 'vertical'
const isWindow = !scroller
const ct = isWindow ? document.body : scroller.$el
const el = vnode.$el || vnode.elm
if (ct && el) {
// if it's a list, then the listVnode.scrollDirection is undefined. just
// assum it is the default value 'vertical'.
const dSuffix = ({
horizontal: 'Left',
vertical: 'Top'
})[scrollDirection]
const ctRect = ct.getBoundingClientRect()
let elRect = el.getBoundingClientRect()
/**
* if it's a waterfall, and you want to scroll to a header, then just
* scroll to the top.
*/
if (scroller && scroller.weexType === 'waterfall' && scroller._headers && scroller._headers.indexOf(vnode.$vnode || vnode) > -1) {
// it's in waterfall. just scroll to the top.
elRect = ct.firstElementChild.getBoundingClientRect()
}
const dir = dSuffix.toLowerCase()
let offset = (isWindow ? 0 : ct[`scroll${dSuffix}`]) + elRect[dir] - ctRect[dir]
if (options) {
offset += options.offset && options.offset * weex.config.env.scale || 0
// offset *= weex.config.env.scale /* adapt offset to different screen scales. */
}
else if (process.env.NODE_ENV === 'development') {
console.warn('[Vue Render] The second parameter of "scrollToElement" is required, '
+ 'otherwise it may not works well on native.')
}
if (options && options.animated === false) {
return scrollElement.call(ct, dSuffix, offset)
}
step({
scrollable: ct,
startTime: now(),
frame: null,
startPosition: ct[`scroll${dSuffix}`],
position: offset,
method: scrollElement,
dSuffix: dSuffix
})
}
},
/**
* getComponentRect
* @param {String} vnode
* @param {Function} callback
*/
getComponentRect: function (vnode, callback) {
const {
isArray
} = utils
if (isArray(vnode)) {
if (process.env.NODE_ENV === 'development') {
console.warn('[vue-render] the ref passed to animation.transitionOnce is a array.')
}
vnode = vnode[0]
}
const scale = window.weex.config.env.scale
const info = {
result: false
}
const rectKeys = ['width', 'height', 'top', 'bottom', 'left', 'right']
function recalc (rect) {
const res = {}
rectKeys.forEach(key => {
if (rect[key]) {
res[key] = rect[key] / scale
}
})
return res
}
if (vnode && vnode === 'viewport') {
info.result = true
info.size = recalc({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
top: 0,
left: 0,
right: document.documentElement.clientWidth,
bottom: document.documentElement.clientHeight
})
info.contentSize = recalc({
width: document.documentElement.offsetWidth,
height: document.documentElement.offsetHeight
})
}
else if (vnode && vnode.$el) {
info.result = true
info.size = recalc(vnode.$el.getBoundingClientRect())
if (vnode.$refs.inner) {
info.contentSize = recalc({
width: vnode.$refs.inner.offsetWidth,
height: vnode.$refs.inner.offsetHeight
})
}
else {
info.contentSize = recalc({
width: vnode.$el.offsetWidth,
height: vnode.$el.offsetHeight
})
}
}
const message = info.result ? info : {
result: false,
errMsg: 'Illegal parameter'
}
callback && callback(message)
return message
},
/**
* for adding fontFace
* @param {string} key fontFace
* @param {object} styles rules
*/
addRule: function (key, styles) {
const {
camelToKebab,
appendCss
} = utils
key = camelToKebab(key)
let stylesText = ''
for (const k in styles) {
if (styles.hasOwnProperty(k)) {
stylesText += camelToKebab(k) + ':' + styles[k] + ';'
}
}
const styleText = `@${key}{${stylesText}}`
appendCss(styleText, 'dom-added-rules')
}
}
export default {
init (weex) {
const extendKeys = weex.utils.extendKeys
extendKeys(utils, weex.utils, ['camelToKebab', 'appendCss', 'isArray'])
weex.registerModule('dom', dom)
}
}