/** * mui gestures * @param {type} $ * @param {type} window * @returns {undefined} */ (function($, window) { $.gestures = { session: {} }; /** * Gesture preventDefault * @param {type} e * @returns {undefined} */ $.preventDefault = function(e) { e.preventDefault(); }; /** * Gesture stopPropagation * @param {type} e * @returns {undefined} */ $.stopPropagation = function(e) { e.stopPropagation(); }; /** * register gesture * @param {type} gesture * @returns {$.gestures} */ $.addGesture = function(gesture) { return $.addAction('gestures', gesture); }; var round = Math.round; var abs = Math.abs; var sqrt = Math.sqrt; var atan = Math.atan; var atan2 = Math.atan2; /** * distance * @param {type} p1 * @param {type} p2 * @returns {Number} */ var getDistance = function(p1, p2, props) { if (!props) { props = ['x', 'y']; } var x = p2[props[0]] - p1[props[0]]; var y = p2[props[1]] - p1[props[1]]; return sqrt((x * x) + (y * y)); }; /** * scale * @param {Object} starts * @param {Object} moves */ var getScale = function(starts, moves) { if (starts.length >= 2 && moves.length >= 2) { var props = ['pageX', 'pageY']; return getDistance(moves[1], moves[0], props) / getDistance(starts[1], starts[0], props); } return 1; }; /** * angle * @param {type} p1 * @param {type} p2 * @returns {Number} */ var getAngle = function(p1, p2, props) { if (!props) { props = ['x', 'y']; } var x = p2[props[0]] - p1[props[0]]; var y = p2[props[1]] - p1[props[1]]; return atan2(y, x) * 180 / Math.PI; }; /** * direction * @param {Object} x * @param {Object} y */ var getDirection = function(x, y) { if (x === y) { return ''; } if (abs(x) >= abs(y)) { return x > 0 ? 'left' : 'right'; } return y > 0 ? 'up' : 'down'; }; /** * rotation * @param {Object} start * @param {Object} end */ var getRotation = function(start, end) { var props = ['pageX', 'pageY']; return getAngle(end[1], end[0], props) - getAngle(start[1], start[0], props); }; /** * px per ms * @param {Object} deltaTime * @param {Object} x * @param {Object} y */ var getVelocity = function(deltaTime, x, y) { return { x: x / deltaTime || 0, y: y / deltaTime || 0 }; }; /** * detect gestures * @param {type} event * @param {type} touch * @returns {undefined} */ var detect = function(event, touch) { if ($.gestures.stoped) { return; } $.doAction('gestures', function(index, gesture) { if (!$.gestures.stoped) { if ($.options.gestureConfig[gesture.name] !== false) { gesture.handle(event, touch); } } }); }; /** * 暂时无用 * @param {Object} node * @param {Object} parent */ var hasParent = function(node, parent) { while (node) { if (node == parent) { return true; } node = node.parentNode; } return false; }; var uniqueArray = function(src, key, sort) { var results = []; var values = []; var i = 0; while (i < src.length) { var val = key ? src[i][key] : src[i]; if (values.indexOf(val) < 0) { results.push(src[i]); } values[i] = val; i++; } if (sort) { if (!key) { results = results.sort(); } else { results = results.sort(function sortUniqueArray(a, b) { return a[key] > b[key]; }); } } return results; }; var getMultiCenter = function(touches) { var touchesLength = touches.length; if (touchesLength === 1) { return { x: round(touches[0].pageX), y: round(touches[0].pageY) }; } var x = 0; var y = 0; var i = 0; while (i < touchesLength) { x += touches[i].pageX; y += touches[i].pageY; i++; } return { x: round(x / touchesLength), y: round(y / touchesLength) }; }; var multiTouch = function() { return $.options.gestureConfig.pinch; }; var copySimpleTouchData = function(touch) { var touches = []; var i = 0; while (i < touch.touches.length) { touches[i] = { pageX: round(touch.touches[i].pageX), pageY: round(touch.touches[i].pageY) }; i++; } return { timestamp: $.now(), gesture: touch.gesture, touches: touches, center: getMultiCenter(touch.touches), deltaX: touch.deltaX, deltaY: touch.deltaY }; }; var calDelta = function(touch) { var session = $.gestures.session; var center = touch.center; var offset = session.offsetDelta || {}; var prevDelta = session.prevDelta || {}; var prevTouch = session.prevTouch || {}; if (touch.gesture.type === $.EVENT_START || touch.gesture.type === $.EVENT_END) { prevDelta = session.prevDelta = { x: prevTouch.deltaX || 0, y: prevTouch.deltaY || 0 }; offset = session.offsetDelta = { x: center.x, y: center.y }; } touch.deltaX = prevDelta.x + (center.x - offset.x); touch.deltaY = prevDelta.y + (center.y - offset.y); }; var calTouchData = function(touch) { var session = $.gestures.session; var touches = touch.touches; var touchesLength = touches.length; if (!session.firstTouch) { session.firstTouch = copySimpleTouchData(touch); } if (multiTouch() && touchesLength > 1 && !session.firstMultiTouch) { session.firstMultiTouch = copySimpleTouchData(touch); } else if (touchesLength === 1) { session.firstMultiTouch = false; } var firstTouch = session.firstTouch; var firstMultiTouch = session.firstMultiTouch; var offsetCenter = firstMultiTouch ? firstMultiTouch.center : firstTouch.center; var center = touch.center = getMultiCenter(touches); touch.timestamp = $.now(); touch.deltaTime = touch.timestamp - firstTouch.timestamp; touch.angle = getAngle(offsetCenter, center); touch.distance = getDistance(offsetCenter, center); calDelta(touch); touch.offsetDirection = getDirection(touch.deltaX, touch.deltaY); touch.scale = firstMultiTouch ? getScale(firstMultiTouch.touches, touches) : 1; touch.rotation = firstMultiTouch ? getRotation(firstMultiTouch.touches, touches) : 0; calIntervalTouchData(touch); }; var CAL_INTERVAL = 25; var calIntervalTouchData = function(touch) { var session = $.gestures.session; var last = session.lastInterval || touch; var deltaTime = touch.timestamp - last.timestamp; var velocity; var velocityX; var velocityY; var direction; if (touch.gesture.type != $.EVENT_CANCEL && (deltaTime > CAL_INTERVAL || last.velocity === undefined)) { var deltaX = last.deltaX - touch.deltaX; var deltaY = last.deltaY - touch.deltaY; var v = getVelocity(deltaTime, deltaX, deltaY); velocityX = v.x; velocityY = v.y; velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y; direction = getDirection(deltaX, deltaY) || last.direction; session.lastInterval = touch; } else { velocity = last.velocity; velocityX = last.velocityX; velocityY = last.velocityY; direction = last.direction; } touch.velocity = velocity; touch.velocityX = velocityX; touch.velocityY = velocityY; touch.direction = direction; }; var targetIds = {}; var convertTouches = function(touches) { for (var i = 0; i < touches.length; i++) { !touches['identifier'] && (touches['identifier'] = 0); } return touches; }; var getTouches = function(event, touch) { var allTouches = convertTouches($.slice.call(event.touches || [event])); var type = event.type; var targetTouches = []; var changedTargetTouches = []; //当touchstart或touchmove且touches长度为1,直接获得all和changed if ((type === $.EVENT_START || type === $.EVENT_MOVE) && allTouches.length === 1) { targetIds[allTouches[0].identifier] = true; targetTouches = allTouches; changedTargetTouches = allTouches; touch.target = event.target; } else { var i = 0; var targetTouches = []; var changedTargetTouches = []; var changedTouches = convertTouches($.slice.call(event.changedTouches || [event])); touch.target = event.target; var sessionTarget = $.gestures.session.target || event.target; targetTouches = allTouches.filter(function(touch) { return hasParent(touch.target, sessionTarget); }); if (type === $.EVENT_START) { i = 0; while (i < targetTouches.length) { targetIds[targetTouches[i].identifier] = true; i++; } } i = 0; while (i < changedTouches.length) { if (targetIds[changedTouches[i].identifier]) { changedTargetTouches.push(changedTouches[i]); } if (type === $.EVENT_END || type === $.EVENT_CANCEL) { delete targetIds[changedTouches[i].identifier]; } i++; } if (!changedTargetTouches.length) { return false; } } targetTouches = uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true); var touchesLength = targetTouches.length; var changedTouchesLength = changedTargetTouches.length; if (type === $.EVENT_START && touchesLength - changedTouchesLength === 0) { //first touch.isFirst = true; $.gestures.touch = $.gestures.session = { target: event.target }; } touch.isFinal = ((type === $.EVENT_END || type === $.EVENT_CANCEL) && (touchesLength - changedTouchesLength === 0)); touch.touches = targetTouches; touch.changedTouches = changedTargetTouches; return true; }; var handleTouchEvent = function(event) { var touch = { gesture: event }; var touches = getTouches(event, touch); if (!touches) { return; } calTouchData(touch); detect(event, touch); $.gestures.session.prevTouch = touch; if (event.type === $.EVENT_END && !$.isTouchable) { $.gestures.touch = $.gestures.session = {}; } }; window.addEventListener($.EVENT_START, handleTouchEvent); window.addEventListener($.EVENT_MOVE, handleTouchEvent); window.addEventListener($.EVENT_END, handleTouchEvent); window.addEventListener($.EVENT_CANCEL, handleTouchEvent); //fixed hashchange(android) window.addEventListener($.EVENT_CLICK, function(e) { //TODO 应该判断当前target是不是在targets.popover内部,而不是非要相等 if (($.os.android || $.os.ios) && (($.targets.popover && e.target === $.targets.popover) || ($.targets.tab) || $.targets.offcanvas || $.targets.modal)) { e.preventDefault(); } }, true); //增加原生滚动识别 $.isScrolling = false; var scrollingTimeout = null; window.addEventListener('scroll', function() { $.isScrolling = true; scrollingTimeout && clearTimeout(scrollingTimeout); scrollingTimeout = setTimeout(function() { $.isScrolling = false; }, 250); }); })(mui, window);