| /* |
| * 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) |
| } |
| } |