UNPKG

asia-weex-vue-render4light

Version:

Web renderer for weex project written in Vue DSL.

518 lines (457 loc) 14.1 kB
/* * 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. */ /* eslint-disable */ 'use strict' var isInitialized = false // major events supported: // panstart // panmove // panend // swipe // longpress // extra events supported: // dualtouchstart // dualtouch // dualtouchend // tap // doubletap // pressend var doc = window.document var docEl = doc.documentElement var slice = Array.prototype.slice var gestures = {} var lastTap = null /** * find the closest common ancestor * if there's no one, return null * * @param {Element} el1 first element * @param {Element} el2 second element * @return {Element} common ancestor */ function getCommonAncestor(el1, el2) { var el = el1 while (el) { if (el.contains(el2) || el == el2) { return el } el = el.parentNode } return null } /** * fire a HTMLEvent * * @param {Element} element which element to fire a event on * @param {string} type type of event * @param {object} extra extra data for the event object */ function fireEvent(element, type, extra) { var event = doc.createEvent('HTMLEvents') event.initEvent(type, true, true) if (typeof extra === 'object') { for (var p in extra) { event[p] = extra[p] } } /** * A flag to distinguish with other events with the same name generated * by another library in the same page. */ event._for = 'weex' element.dispatchEvent(event) } /** * calc the transform * assume 4 points ABCD on the coordinate system * > rotate:angle rotating from AB to CD * > scale:scale ratio from AB to CD * > translate:translate shift from A to C * * @param {number} x1 abscissa of A * @param {number} y1 ordinate of A * @param {number} x2 abscissa of B * @param {number} y2 ordinate of B * @param {number} x3 abscissa of C * @param {number} y3 ordinate of C * @param {number} x4 abscissa of D * @param {number} y4 ordinate of D * @return {object} transform object like * {rotate, scale, translate[2], matrix[3][3]} */ function calc(x1, y1, x2, y2, x3, y3, x4, y4) { var rotate = Math.atan2(y4 - y3, x4 - x3) - Math.atan2(y2 - y1, x2 - x1) var scale = Math.sqrt((Math.pow(y4 - y3, 2) + Math.pow(x4 - x3, 2)) / (Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2))) var translate = [ x3 - scale * x1 * Math.cos(rotate) + scale * y1 * Math.sin(rotate), y3 - scale * y1 * Math.cos(rotate) - scale * x1 * Math.sin(rotate)] return { rotate: rotate, scale: scale, translate: translate, matrix: [ [scale * Math.cos(rotate), -scale * Math.sin(rotate), translate[0]], [scale * Math.sin(rotate), scale * Math.cos(rotate), translate[1]], [0, 0, 1] ] } } /** * take over the touchstart events. Add new touches to the gestures. * If there is no previous records, then bind touchmove, tochend * and touchcancel events. * new touches initialized with state 'tapping', and within 500 milliseconds * if the state is still tapping, then trigger gesture 'press'. * If there are two touche points, then the 'dualtouchstart' is triggerd. The * node of the touch gesture is their cloest common ancestor. * * @event * @param {event} event */ function touchstartHandler(event) { if (Object.keys(gestures).length === 0) { docEl.addEventListener('touchmove', touchmoveHandler, true) docEl.addEventListener('touchend', touchendHandler, true) docEl.addEventListener('touchcancel', touchcancelHandler, true) } // record every touch for (var i = 0; i < event.changedTouches.length; i++) { var touch = event.changedTouches[i] var touchRecord = {} for (var p in touch) { touchRecord[p] = touch[p] } var gesture = { startTouch: touchRecord, startTime: Date.now(), status: 'tapping', element: event.srcElement || event.target, pressingHandler: setTimeout(function (element, touch) { return function () { if (gesture.status === 'tapping') { gesture.status = 'pressing' fireEvent(element, 'longpress', { // add touch data for weex touch: touch, touches: event.touches, changedTouches: event.changedTouches, touchEvent: event }) } clearTimeout(gesture.pressingHandler) gesture.pressingHandler = null } }(event.srcElement || event.target, event.changedTouches[i]), 500) } gestures[touch.identifier] = gesture } if (Object.keys(gestures).length == 2) { var elements = [] for (var p in gestures) { elements.push(gestures[p].element) } fireEvent(getCommonAncestor(elements[0], elements[1]), 'dualtouchstart', { touches: slice.call(event.touches), touchEvent: event }) } } /** * take over touchmove events, and handle pan and dual related gestures. * * 1. traverse every touch point: * > if 'tapping' and the shift is over 10 pixles, then it's a 'panning'. * 2. if there are two touch points, then calc the tranform and trigger * 'dualtouch'. * * @event * @param {event} event */ function touchmoveHandler(event) { for (var i = 0; i < event.changedTouches.length; i++) { var touch = event.changedTouches[i] var gesture = gestures[touch.identifier] if (!gesture) { return } if (!gesture.lastTouch) { gesture.lastTouch = gesture.startTouch } if (!gesture.lastTime) { gesture.lastTime = gesture.startTime } if (!gesture.velocityX) { gesture.velocityX = 0 } if (!gesture.velocityY) { gesture.velocityY = 0 } if (!gesture.duration) { gesture.duration = 0 } var time = Date.now() - gesture.lastTime var vx = (touch.clientX - gesture.lastTouch.clientX) / time var vy = (touch.clientY - gesture.lastTouch.clientY) / time var RECORD_DURATION = 70 if (time > RECORD_DURATION) { time = RECORD_DURATION } if (gesture.duration + time > RECORD_DURATION) { gesture.duration = RECORD_DURATION - time } gesture.velocityX = (gesture.velocityX * gesture.duration + vx * time) / (gesture.duration + time) gesture.velocityY = (gesture.velocityY * gesture.duration + vy * time) / (gesture.duration + time) gesture.duration += time gesture.lastTouch = {} for (var p in touch) { gesture.lastTouch[p] = touch[p] } gesture.lastTime = Date.now() var displacementX = touch.clientX - gesture.startTouch.clientX var displacementY = touch.clientY - gesture.startTouch.clientY var distance = Math.sqrt(Math.pow(displacementX, 2) + Math.pow(displacementY, 2)) var isVertical = !(Math.abs(displacementX) > Math.abs(displacementY)) var direction = isVertical ? displacementY >= 0 ? 'down' : 'up' : displacementX >= 0 ? 'right' : 'left' // magic number 10: moving 10px means pan, not tap if ((gesture.status === 'tapping' || gesture.status === 'pressing') && distance > 10) { gesture.status = 'panning' gesture.isVertical = isVertical gesture.direction = direction fireEvent(gesture.element, 'panstart', { touch: touch, touches: event.touches, changedTouches: event.changedTouches, touchEvent: event, isVertical: gesture.isVertical, direction: direction }) } if (gesture.status === 'panning') { gesture.panTime = Date.now() fireEvent(gesture.element, 'panmove', { displacementX: displacementX, displacementY: displacementY, touch: touch, touches: event.touches, changedTouches: event.changedTouches, touchEvent: event, isVertical: gesture.isVertical, direction: direction }) } } if (Object.keys(gestures).length == 2) { var position = [] var current = [] var elements = [] var transform for (var i = 0; i < event.touches.length; i++) { var touch = event.touches[i] var gesture = gestures[touch.identifier] position.push([gesture.startTouch.clientX, gesture.startTouch.clientY]) current.push([touch.clientX, touch.clientY]) } for (var p in gestures) { elements.push(gestures[p].element) } transform = calc( position[0][0], position[0][1], position[1][0], position[1][1], current[0][0], current[0][1], current[1][0], current[1][1] ) fireEvent(getCommonAncestor(elements[0], elements[1]), 'dualtouch', { transform: transform, touches: event.touches, touchEvent: event }) } } /** * handle touchend event * * 1. if there are tow touch points, then trigger 'dualtouchend'如 * * 2. traverse every touch piont: * > if tapping, then trigger 'tap'. * If there is a tap 300 milliseconds before, then it's a 'doubletap'. * > if padding, then decide to trigger 'panend' or 'swipe' * > if pressing, then trigger 'pressend'. * * 3. remove listeners. * * @event * @param {event} event */ function touchendHandler(event) { if (Object.keys(gestures).length == 2) { var elements = [] for (var p in gestures) { elements.push(gestures[p].element) } fireEvent(getCommonAncestor(elements[0], elements[1]), 'dualtouchend', { touches: slice.call(event.touches), touchEvent: event }) } for (var i = 0; i < event.changedTouches.length; i++) { var touch = event.changedTouches[i] var id = touch.identifier var gesture = gestures[id] if (!gesture) { continue } if (gesture.pressingHandler) { clearTimeout(gesture.pressingHandler) gesture.pressingHandler = null } if (gesture.status === 'tapping') { gesture.timestamp = Date.now() // fire click, not tap. fireEvent(gesture.element, 'weex$tap', { touch: touch, touchEvent: event }) if (lastTap && gesture.timestamp - lastTap.timestamp < 300) { fireEvent(gesture.element, 'doubletap', { touch: touch, touchEvent: event }) } lastTap = gesture } if (gesture.status === 'panning') { var now = Date.now() var duration = now - gesture.startTime var displacementX = touch.clientX - gesture.startTouch.clientX var displacementY = touch.clientY - gesture.startTouch.clientY var velocity = Math.sqrt(gesture.velocityY * gesture.velocityY + gesture.velocityX * gesture.velocityX) var isSwipe = velocity > 0.5 && (now - gesture.lastTime) < 100 var extra = { duration: duration, isSwipe: isSwipe, velocityX: gesture.velocityX, velocityY: gesture.velocityY, displacementX: displacementX, displacementY: displacementY, touch: touch, touches: event.touches, changedTouches: event.changedTouches, touchEvent: event, isVertical: gesture.isVertical, direction: gesture.direction } fireEvent(gesture.element, 'panend', extra) if (isSwipe) { fireEvent(gesture.element, 'swipe', extra) } } if (gesture.status === 'pressing') { fireEvent(gesture.element, 'pressend', { touch: touch, touchEvent: event }) } delete gestures[id] } if (Object.keys(gestures).length === 0) { docEl.removeEventListener('touchmove', touchmoveHandler, false) docEl.removeEventListener('touchend', touchendHandler, false) docEl.removeEventListener('touchcancel', touchcancelHandler, false) } } /** * handle touchcancel * * 1. if there are two touch points, then trigger 'dualtouchend' * * 2. traverse everty touch point: * > if pannnig, then trigger 'panend' * > if pressing, then trigger 'pressend' * * 3. remove listeners * * @event * @param {event} event */ function touchcancelHandler(event) { if (Object.keys(gestures).length == 2) { var elements = [] for (var p in gestures) { elements.push(gestures[p].element) } fireEvent(getCommonAncestor(elements[0], elements[1]), 'dualtouchend', { touches: slice.call(event.touches), touchEvent: event }) } for (var i = 0; i < event.changedTouches.length; i++) { var touch = event.changedTouches[i] var id = touch.identifier var gesture = gestures[id] if (!gesture) { continue } if (gesture.pressingHandler) { clearTimeout(gesture.pressingHandler) gesture.pressingHandler = null } if (gesture.status === 'panning') { fireEvent(gesture.element, 'panend', { touch: touch, touches: event.touches, changedTouches: event.changedTouches, touchEvent: event }) } if (gesture.status === 'pressing') { fireEvent(gesture.element, 'pressend', { touch: touch, touchEvent: event }) } delete gestures[id] } if (Object.keys(gestures).length === 0) { docEl.removeEventListener('touchmove', touchmoveHandler, true) docEl.removeEventListener('touchend', touchendHandler, true) docEl.removeEventListener('touchcancel', touchcancelHandler, true) } } if (!isInitialized) { docEl.addEventListener('touchstart', touchstartHandler, true) isInitialized = true }