UNPKG

view360-canex

Version:

360 integrated viewing solution from inside-out view to outside-in view. It provides user-friendly service by rotating 360 degrees through various user interaction such as motion sensor and touch.

1,706 lines (1,405 loc) 191 kB
/* Copyright (c) 2017 NAVER Corp. name: view360-canex license: MIT author: Canex repository: https://github.com/Can-ExploreInc/egjs-view360 version: 3.4.9 */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, (global.eg = global.eg || {}, global.eg.view360 = factory())); }(this, (function () { 'use strict'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; } || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function () { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; /* Copyright (c) NAVER Corp. name: @egjs/component license: MIT author: NAVER Corp. repository: https://github.com/naver/egjs-component version: 2.2.2 */ /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __values(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); } /* * Copyright (c) 2015 NAVER Corp. * egjs projects are licensed under the MIT license */ function isUndefined(value) { return typeof value === "undefined"; } /** * A class used to manage events in a component * @ko 컴포넌트의 이벤트을 관리할 수 있게 하는 클래스 * @alias eg.Component */ var Component = /*#__PURE__*/ function () { /** * @support {"ie": "7+", "ch" : "latest", "ff" : "latest", "sf" : "latest", "edge" : "latest", "ios" : "7+", "an" : "2.1+ (except 3.x)"} */ function Component() { /** * @deprecated * @private */ this.options = {}; this._eventHandler = {}; } /** * Triggers a custom event. * @ko 커스텀 이벤트를 발생시킨다 * @param {string} eventName The name of the custom event to be triggered <ko>발생할 커스텀 이벤트의 이름</ko> * @param {object} customEvent Event data to be sent when triggering a custom event <ko>커스텀 이벤트가 발생할 때 전달할 데이터</ko> * @param {any[]} restParam Additional parameters when triggering a custom event <ko>커스텀 이벤트가 발생할 때 필요시 추가적으로 전달할 데이터</ko> * @return Indicates whether the event has occurred. If the stop() method is called by a custom event handler, it will return false and prevent the event from occurring. <a href="https://github.com/naver/egjs-component/wiki/How-to-make-Component-event-design%3F">Ref</a> <ko>이벤트 발생 여부. 커스텀 이벤트 핸들러에서 stop() 메서드를 호출하면 'false'를 반환하고 이벤트 발생을 중단한다. <a href="https://github.com/naver/egjs-component/wiki/How-to-make-Component-event-design%3F">참고</a></ko> * @example * ``` * class Some extends eg.Component { * some(){ * if(this.trigger("beforeHi")){ // When event call to stop return false. * this.trigger("hi");// fire hi event. * } * } * } * * const some = new Some(); * some.on("beforeHi", (e) => { * if(condition){ * e.stop(); // When event call to stop, `hi` event not call. * } * }); * some.on("hi", (e) => { * // `currentTarget` is component instance. * console.log(some === e.currentTarget); // true * }); * // If you want to more know event design. You can see article. * // https://github.com/naver/egjs-component/wiki/How-to-make-Component-event-design%3F * ``` */ var __proto = Component.prototype; __proto.trigger = function (eventName) { var _this = this; var params = []; for (var _i = 1; _i < arguments.length; _i++) { params[_i - 1] = arguments[_i]; } var handlerList = this._eventHandler[eventName] || []; var hasHandlerList = handlerList.length > 0; if (!hasHandlerList) { return true; } var customEvent = params[0] || {}; var restParams = params.slice(1); // If detach method call in handler in first time then handler list calls. handlerList = handlerList.concat(); var isCanceled = false; // This should be done like this to pass previous tests customEvent.eventType = eventName; customEvent.stop = function () { isCanceled = true; }; customEvent.currentTarget = this; var arg = [customEvent]; if (restParams.length >= 1) { arg = arg.concat(restParams); } handlerList.forEach(function (handler) { handler.apply(_this, arg); }); return !isCanceled; }; /** * Executed event just one time. * @ko 이벤트가 한번만 실행된다. * @param {string} eventName The name of the event to be attached <ko>등록할 이벤트의 이름</ko> * @param {function} handlerToAttach The handler function of the event to be attached <ko>등록할 이벤트의 핸들러 함수</ko> * @return An instance of a component itself<ko>컴포넌트 자신의 인스턴스</ko> * @example * ``` * class Some extends eg.Component { * hi() { * alert("hi"); * } * thing() { * this.once("hi", this.hi); * } * * var some = new Some(); * some.thing(); * some.trigger("hi"); * // fire alert("hi"); * some.trigger("hi"); * // Nothing happens * ``` */ __proto.once = function (eventName, handlerToAttach) { var _this = this; if (typeof eventName === "object" && isUndefined(handlerToAttach)) { var eventHash = eventName; for (var key in eventHash) { this.once(key, eventHash[key]); } return this; } else if (typeof eventName === "string" && typeof handlerToAttach === "function") { var listener_1 = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } handlerToAttach.apply(_this, args); _this.off(eventName, listener_1); }; this.on(eventName, listener_1); } return this; }; /** * Checks whether an event has been attached to a component. * @ko 컴포넌트에 이벤트가 등록됐는지 확인한다. * @param {string} eventName The name of the event to be attached <ko>등록 여부를 확인할 이벤트의 이름</ko> * @return {boolean} Indicates whether the event is attached. <ko>이벤트 등록 여부</ko> * @example * ``` * class Some extends eg.Component { * some() { * this.hasOn("hi");// check hi event. * } * } * ``` */ __proto.hasOn = function (eventName) { return !!this._eventHandler[eventName]; }; /** * Attaches an event to a component. * @ko 컴포넌트에 이벤트를 등록한다. * @param {string} eventName The name of the event to be attached <ko>등록할 이벤트의 이름</ko> * @param {function} handlerToAttach The handler function of the event to be attached <ko>등록할 이벤트의 핸들러 함수</ko> * @return An instance of a component itself<ko>컴포넌트 자신의 인스턴스</ko> * @example * ``` * class Some extends eg.Component { * hi() { * console.log("hi"); * } * some() { * this.on("hi",this.hi); //attach event * } * } * ``` */ __proto.on = function (eventName, handlerToAttach) { if (typeof eventName === "object" && isUndefined(handlerToAttach)) { var eventHash = eventName; for (var name in eventHash) { this.on(name, eventHash[name]); } return this; } else if (typeof eventName === "string" && typeof handlerToAttach === "function") { var handlerList = this._eventHandler[eventName]; if (isUndefined(handlerList)) { this._eventHandler[eventName] = []; handlerList = this._eventHandler[eventName]; } handlerList.push(handlerToAttach); } return this; }; /** * Detaches an event from the component. * @ko 컴포넌트에 등록된 이벤트를 해제한다 * @param {string} eventName The name of the event to be detached <ko>해제할 이벤트의 이름</ko> * @param {function} handlerToDetach The handler function of the event to be detached <ko>해제할 이벤트의 핸들러 함수</ko> * @return An instance of a component itself <ko>컴포넌트 자신의 인스턴스</ko> * @example * ``` * class Some extends eg.Component { * hi() { * console.log("hi"); * } * some() { * this.off("hi",this.hi); //detach event * } * } * ``` */ __proto.off = function (eventName, handlerToDetach) { var e_1, _a; // Detach all event handlers. if (isUndefined(eventName)) { this._eventHandler = {}; return this; } // Detach all handlers for eventname or detach event handlers by object. if (isUndefined(handlerToDetach)) { if (typeof eventName === "string") { delete this._eventHandler[eventName]; return this; } else { var eventHash = eventName; for (var name in eventHash) { this.off(name, eventHash[name]); } return this; } } // Detach single event handler var handlerList = this._eventHandler[eventName]; if (handlerList) { var idx = 0; try { for (var handlerList_1 = __values(handlerList), handlerList_1_1 = handlerList_1.next(); !handlerList_1_1.done; handlerList_1_1 = handlerList_1.next()) { var handlerFunction = handlerList_1_1.value; if (handlerFunction === handlerToDetach) { handlerList.splice(idx, 1); break; } idx++; } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (handlerList_1_1 && !handlerList_1_1.done && (_a = handlerList_1.return)) _a.call(handlerList_1); } finally { if (e_1) throw e_1.error; } } } return this; }; /** * Version info string * @ko 버전정보 문자열 * @name VERSION * @static * @example * eg.Component.VERSION; // ex) 2.0.0 * @memberof eg.Component */ Component.VERSION = "2.2.2"; return Component; }(); /*! Hammer.JS - v2.0.17-rc - 2019-12-16 * http://naver.github.io/egjs * * Forked By Naver egjs * Copyright (c) hammerjs * Licensed under the MIT license */ function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } /** * @private * extend object. * means that properties in dest will be overwritten by the ones in src. * @param {Object} target * @param {...Object} objects_to_assign * @returns {Object} target */ var assign; if (typeof Object.assign !== 'function') { assign = function assign(target) { if (target === undefined || target === null) { throw new TypeError('Cannot convert undefined or null to object'); } var output = Object(target); for (var index = 1; index < arguments.length; index++) { var source = arguments[index]; if (source !== undefined && source !== null) { for (var nextKey in source) { if (source.hasOwnProperty(nextKey)) { output[nextKey] = source[nextKey]; } } } } return output; }; } else { assign = Object.assign; } var assign$1 = assign; var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o']; var TEST_ELEMENT = typeof document === "undefined" ? { style: {} } : document.createElement('div'); var TYPE_FUNCTION = 'function'; var round = Math.round, abs = Math.abs; var now = Date.now; /** * @private * get the prefixed property * @param {Object} obj * @param {String} property * @returns {String|Undefined} prefixed */ function prefixed(obj, property) { var prefix; var prop; var camelProp = property[0].toUpperCase() + property.slice(1); var i = 0; while (i < VENDOR_PREFIXES.length) { prefix = VENDOR_PREFIXES[i]; prop = prefix ? prefix + camelProp : property; if (prop in obj) { return prop; } i++; } return undefined; } /* eslint-disable no-new-func, no-nested-ternary */ var win; if (typeof window === "undefined") { // window is undefined in node.js win = {}; } else { win = window; } var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction'); var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined; function getTouchActionProps() { if (!NATIVE_TOUCH_ACTION) { return false; } var touchMap = {}; var cssSupports = win.CSS && win.CSS.supports; ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function (val) { // If css.supports is not supported but there is native touch-action assume it supports // all values. This is the case for IE 10 and 11. return touchMap[val] = cssSupports ? win.CSS.supports('touch-action', val) : true; }); return touchMap; } var TOUCH_ACTION_COMPUTE = 'compute'; var TOUCH_ACTION_AUTO = 'auto'; var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented var TOUCH_ACTION_NONE = 'none'; var TOUCH_ACTION_PAN_X = 'pan-x'; var TOUCH_ACTION_PAN_Y = 'pan-y'; var TOUCH_ACTION_MAP = getTouchActionProps(); var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i; var SUPPORT_TOUCH = 'ontouchstart' in win; var SUPPORT_POINTER_EVENTS = prefixed(win, 'PointerEvent') !== undefined; var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent); var INPUT_TYPE_TOUCH = 'touch'; var INPUT_TYPE_PEN = 'pen'; var INPUT_TYPE_MOUSE = 'mouse'; var INPUT_TYPE_KINECT = 'kinect'; var COMPUTE_INTERVAL = 25; var INPUT_START = 1; var INPUT_MOVE = 2; var INPUT_END = 4; var INPUT_CANCEL = 8; var DIRECTION_NONE = 1; var DIRECTION_LEFT = 2; var DIRECTION_RIGHT = 4; var DIRECTION_UP = 8; var DIRECTION_DOWN = 16; var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT; var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN; var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL; var PROPS_XY = ['x', 'y']; var PROPS_CLIENT_XY = ['clientX', 'clientY']; /** * @private * walk objects and arrays * @param {Object} obj * @param {Function} iterator * @param {Object} context */ function each(obj, iterator, context) { var i; if (!obj) { return; } if (obj.forEach) { obj.forEach(iterator, context); } else if (obj.length !== undefined) { i = 0; while (i < obj.length) { iterator.call(context, obj[i], i, obj); i++; } } else { for (i in obj) { obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj); } } } /** * @private * let a boolean value also be a function that must return a boolean * this first item in args will be used as the context * @param {Boolean|Function} val * @param {Array} [args] * @returns {Boolean} */ function boolOrFn(val, args) { if (typeof val === TYPE_FUNCTION) { return val.apply(args ? args[0] || undefined : undefined, args); } return val; } /** * @private * small indexOf wrapper * @param {String} str * @param {String} find * @returns {Boolean} found */ function inStr(str, find) { return str.indexOf(find) > -1; } /** * @private * when the touchActions are collected they are not a valid value, so we need to clean things up. * * @param {String} actions * @returns {*} */ function cleanTouchActions(actions) { // none if (inStr(actions, TOUCH_ACTION_NONE)) { return TOUCH_ACTION_NONE; } var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X); var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y); // if both pan-x and pan-y are set (different recognizers // for different directions, e.g. horizontal pan but vertical swipe?) // we need none (as otherwise with pan-x pan-y combined none of these // recognizers will work, since the browser would handle all panning if (hasPanX && hasPanY) { return TOUCH_ACTION_NONE; } // pan-x OR pan-y if (hasPanX || hasPanY) { return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y; } // manipulation if (inStr(actions, TOUCH_ACTION_MANIPULATION)) { return TOUCH_ACTION_MANIPULATION; } return TOUCH_ACTION_AUTO; } /** * @private * Touch Action * sets the touchAction property or uses the js alternative * @param {Manager} manager * @param {String} value * @constructor */ var TouchAction = /*#__PURE__*/ function () { function TouchAction(manager, value) { this.manager = manager; this.set(value); } /** * @private * set the touchAction value on the element or enable the polyfill * @param {String} value */ var _proto = TouchAction.prototype; _proto.set = function set(value) { // find out the touch-action by the event handlers if (value === TOUCH_ACTION_COMPUTE) { value = this.compute(); } if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) { this.manager.element.style[PREFIXED_TOUCH_ACTION] = value; } this.actions = value.toLowerCase().trim(); }; /** * @private * just re-set the touchAction value */ _proto.update = function update() { this.set(this.manager.options.touchAction); }; /** * @private * compute the value for the touchAction property based on the recognizer's settings * @returns {String} value */ _proto.compute = function compute() { var actions = []; each(this.manager.recognizers, function (recognizer) { if (boolOrFn(recognizer.options.enable, [recognizer])) { actions = actions.concat(recognizer.getTouchAction()); } }); return cleanTouchActions(actions.join(' ')); }; /** * @private * this method is called on each input cycle and provides the preventing of the browser behavior * @param {Object} input */ _proto.preventDefaults = function preventDefaults(input) { var srcEvent = input.srcEvent; var direction = input.offsetDirection; // if the touch action did prevented once this session if (this.manager.session.prevented) { srcEvent.preventDefault(); return; } var actions = this.actions; var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE]; var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y]; var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X]; if (hasNone) { // do not prevent defaults if this is a tap gesture var isTapPointer = input.pointers.length === 1; var isTapMovement = input.distance < 2; var isTapTouchTime = input.deltaTime < 250; if (isTapPointer && isTapMovement && isTapTouchTime) { return; } } if (hasPanX && hasPanY) { // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent return; } if (hasNone || hasPanY && direction & DIRECTION_HORIZONTAL || hasPanX && direction & DIRECTION_VERTICAL) { return this.preventSrc(srcEvent); } }; /** * @private * call preventDefault to prevent the browser's default behavior (scrolling in most cases) * @param {Object} srcEvent */ _proto.preventSrc = function preventSrc(srcEvent) { this.manager.session.prevented = true; srcEvent.preventDefault(); }; return TouchAction; }(); /** * @private * find if a node is in the given parent * @method hasParent * @param {HTMLElement} node * @param {HTMLElement} parent * @return {Boolean} found */ function hasParent(node, parent) { while (node) { if (node === parent) { return true; } node = node.parentNode; } return false; } /** * @private * get the center of all the pointers * @param {Array} pointers * @return {Object} center contains `x` and `y` properties */ function getCenter(pointers) { var pointersLength = pointers.length; // no need to loop when only one touch if (pointersLength === 1) { return { x: round(pointers[0].clientX), y: round(pointers[0].clientY) }; } var x = 0; var y = 0; var i = 0; while (i < pointersLength) { x += pointers[i].clientX; y += pointers[i].clientY; i++; } return { x: round(x / pointersLength), y: round(y / pointersLength) }; } /** * @private * create a simple clone from the input used for storage of firstInput and firstMultiple * @param {Object} input * @returns {Object} clonedInputData */ function simpleCloneInputData(input) { // make a simple copy of the pointers because we will get a reference if we don't // we only need clientXY for the calculations var pointers = []; var i = 0; while (i < input.pointers.length) { pointers[i] = { clientX: round(input.pointers[i].clientX), clientY: round(input.pointers[i].clientY) }; i++; } return { timeStamp: now(), pointers: pointers, center: getCenter(pointers), deltaX: input.deltaX, deltaY: input.deltaY }; } /** * @private * calculate the absolute distance between two points * @param {Object} p1 {x, y} * @param {Object} p2 {x, y} * @param {Array} [props] containing x and y keys * @return {Number} distance */ function getDistance(p1, p2, props) { if (!props) { props = PROPS_XY; } var x = p2[props[0]] - p1[props[0]]; var y = p2[props[1]] - p1[props[1]]; return Math.sqrt(x * x + y * y); } /** * @private * calculate the angle between two coordinates * @param {Object} p1 * @param {Object} p2 * @param {Array} [props] containing x and y keys * @return {Number} angle */ function getAngle(p1, p2, props) { if (!props) { props = PROPS_XY; } var x = p2[props[0]] - p1[props[0]]; var y = p2[props[1]] - p1[props[1]]; return Math.atan2(y, x) * 180 / Math.PI; } /** * @private * get the direction between two points * @param {Number} x * @param {Number} y * @return {Number} direction */ function getDirection(x, y) { if (x === y) { return DIRECTION_NONE; } if (abs(x) >= abs(y)) { return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT; } return y < 0 ? DIRECTION_UP : DIRECTION_DOWN; } function computeDeltaXY(session, input) { var center = input.center; // let { offsetDelta:offset = {}, prevDelta = {}, prevInput = {} } = session; // jscs throwing error on defalut destructured values and without defaults tests fail var offset = session.offsetDelta || {}; var prevDelta = session.prevDelta || {}; var prevInput = session.prevInput || {}; if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) { prevDelta = session.prevDelta = { x: prevInput.deltaX || 0, y: prevInput.deltaY || 0 }; offset = session.offsetDelta = { x: center.x, y: center.y }; } input.deltaX = prevDelta.x + (center.x - offset.x); input.deltaY = prevDelta.y + (center.y - offset.y); } /** * @private * calculate the velocity between two points. unit is in px per ms. * @param {Number} deltaTime * @param {Number} x * @param {Number} y * @return {Object} velocity `x` and `y` */ function getVelocity(deltaTime, x, y) { return { x: x / deltaTime || 0, y: y / deltaTime || 0 }; } /** * @private * calculate the scale factor between two pointersets * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out * @param {Array} start array of pointers * @param {Array} end array of pointers * @return {Number} scale */ function getScale(start, end) { return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY); } /** * @private * calculate the rotation degrees between two pointersets * @param {Array} start array of pointers * @param {Array} end array of pointers * @return {Number} rotation */ function getRotation(start, end) { return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY); } /** * @private * velocity is calculated every x ms * @param {Object} session * @param {Object} input */ function computeIntervalInputData(session, input) { var last = session.lastInterval || input; var deltaTime = input.timeStamp - last.timeStamp; var velocity; var velocityX; var velocityY; var direction; if (input.eventType !== INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) { var deltaX = input.deltaX - last.deltaX; var deltaY = input.deltaY - last.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); session.lastInterval = input; } else { // use latest velocity info if it doesn't overtake a minimum period velocity = last.velocity; velocityX = last.velocityX; velocityY = last.velocityY; direction = last.direction; } input.velocity = velocity; input.velocityX = velocityX; input.velocityY = velocityY; input.direction = direction; } /** * @private * extend the data with some usable properties like scale, rotate, velocity etc * @param {Object} manager * @param {Object} input */ function computeInputData(manager, input) { var session = manager.session; var pointers = input.pointers; var pointersLength = pointers.length; // store the first input to calculate the distance and direction if (!session.firstInput) { session.firstInput = simpleCloneInputData(input); } // to compute scale and rotation we need to store the multiple touches if (pointersLength > 1 && !session.firstMultiple) { session.firstMultiple = simpleCloneInputData(input); } else if (pointersLength === 1) { session.firstMultiple = false; } var firstInput = session.firstInput, firstMultiple = session.firstMultiple; var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center; var center = input.center = getCenter(pointers); input.timeStamp = now(); input.deltaTime = input.timeStamp - firstInput.timeStamp; input.angle = getAngle(offsetCenter, center); input.distance = getDistance(offsetCenter, center); computeDeltaXY(session, input); input.offsetDirection = getDirection(input.deltaX, input.deltaY); var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY); input.overallVelocityX = overallVelocity.x; input.overallVelocityY = overallVelocity.y; input.overallVelocity = abs(overallVelocity.x) > abs(overallVelocity.y) ? overallVelocity.x : overallVelocity.y; input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1; input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0; input.maxPointers = !session.prevInput ? input.pointers.length : input.pointers.length > session.prevInput.maxPointers ? input.pointers.length : session.prevInput.maxPointers; computeIntervalInputData(session, input); // find the correct target var target = manager.element; var srcEvent = input.srcEvent; var srcEventTarget; if (srcEvent.composedPath) { srcEventTarget = srcEvent.composedPath()[0]; } else if (srcEvent.path) { srcEventTarget = srcEvent.path[0]; } else { srcEventTarget = srcEvent.target; } if (hasParent(srcEventTarget, target)) { target = srcEventTarget; } input.target = target; } /** * @private * handle input events * @param {Manager} manager * @param {String} eventType * @param {Object} input */ function inputHandler(manager, eventType, input) { var pointersLen = input.pointers.length; var changedPointersLen = input.changedPointers.length; var isFirst = eventType & INPUT_START && pointersLen - changedPointersLen === 0; var isFinal = eventType & (INPUT_END | INPUT_CANCEL) && pointersLen - changedPointersLen === 0; input.isFirst = !!isFirst; input.isFinal = !!isFinal; if (isFirst) { manager.session = {}; } // source event is the normalized value of the domEvents // like 'touchstart, mouseup, pointerdown' input.eventType = eventType; // compute scale, rotation etc computeInputData(manager, input); // emit secret event manager.emit('hammer.input', input); manager.recognize(input); manager.session.prevInput = input; } /** * @private * split string on whitespace * @param {String} str * @returns {Array} words */ function splitStr(str) { return str.trim().split(/\s+/g); } /** * @private * addEventListener with multiple events at once * @param {EventTarget} target * @param {String} types * @param {Function} handler */ function addEventListeners(target, types, handler) { each(splitStr(types), function (type) { target.addEventListener(type, handler, false); }); } /** * @private * removeEventListener with multiple events at once * @param {EventTarget} target * @param {String} types * @param {Function} handler */ function removeEventListeners(target, types, handler) { each(splitStr(types), function (type) { target.removeEventListener(type, handler, false); }); } /** * @private * get the window object of an element * @param {HTMLElement} element * @returns {DocumentView|Window} */ function getWindowForElement(element) { var doc = element.ownerDocument || element; return doc.defaultView || doc.parentWindow || window; } /** * @private * create new input type manager * @param {Manager} manager * @param {Function} callback * @returns {Input} * @constructor */ var Input = /*#__PURE__*/ function () { function Input(manager, callback) { var self = this; this.manager = manager; this.callback = callback; this.element = manager.element; this.target = manager.options.inputTarget; // smaller wrapper around the handler, for the scope and the enabled state of the manager, // so when disabled the input events are completely bypassed. this.domHandler = function (ev) { if (boolOrFn(manager.options.enable, [manager])) { self.handler(ev); } }; this.init(); } /** * @private * should handle the inputEvent data and trigger the callback * @virtual */ var _proto = Input.prototype; _proto.handler = function handler() {}; /** * @private * bind the events */ _proto.init = function init() { this.evEl && addEventListeners(this.element, this.evEl, this.domHandler); this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler); this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler); }; /** * @private * unbind the events */ _proto.destroy = function destroy() { this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler); this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler); this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler); }; return Input; }(); /** * @private * find if a array contains the object using indexOf or a simple polyFill * @param {Array} src * @param {String} find * @param {String} [findByKey] * @return {Boolean|Number} false when not found, or the index */ function inArray(src, find, findByKey) { if (src.indexOf && !findByKey) { return src.indexOf(find); } else { var i = 0; while (i < src.length) { if (findByKey && src[i][findByKey] == find || !findByKey && src[i] === find) { // do not use === here, test fails return i; } i++; } return -1; } } var POINTER_INPUT_MAP = { pointerdown: INPUT_START, pointermove: INPUT_MOVE, pointerup: INPUT_END, pointercancel: INPUT_CANCEL, pointerout: INPUT_CANCEL }; // in IE10 the pointer types is defined as an enum var IE10_POINTER_TYPE_ENUM = { 2: INPUT_TYPE_TOUCH, 3: INPUT_TYPE_PEN, 4: INPUT_TYPE_MOUSE, 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816 }; var POINTER_ELEMENT_EVENTS = 'pointerdown'; var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel'; // IE10 has prefixed support, and case-sensitive if (win.MSPointerEvent && !win.PointerEvent) { POINTER_ELEMENT_EVENTS = 'MSPointerDown'; POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel'; } /** * @private * Pointer events input * @constructor * @extends Input */ var PointerEventInput = /*#__PURE__*/ function (_Input) { _inheritsLoose(PointerEventInput, _Input); function PointerEventInput() { var _this; var proto = PointerEventInput.prototype; proto.evEl = POINTER_ELEMENT_EVENTS; proto.evWin = POINTER_WINDOW_EVENTS; _this = _Input.apply(this, arguments) || this; _this.store = _this.manager.session.pointerEvents = []; return _this; } /** * @private * handle mouse events * @param {Object} ev */ var _proto = PointerEventInput.prototype; _proto.handler = function handler(ev) { var store = this.store; var removePointer = false; var eventTypeNormalized = ev.type.toLowerCase().replace('ms', ''); var eventType = POINTER_INPUT_MAP[eventTypeNormalized]; var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType; var isTouch = pointerType === INPUT_TYPE_TOUCH; // get index of the event in the store var storeIndex = inArray(store, ev.pointerId, 'pointerId'); // start and mouse must be down if (eventType & INPUT_START && (ev.button === 0 || isTouch)) { if (storeIndex < 0) { store.push(ev); storeIndex = store.length - 1; } } else if (eventType & (INPUT_END | INPUT_CANCEL)) { removePointer = true; } // it not found, so the pointer hasn't been down (so it's probably a hover) if (storeIndex < 0) { return; } // update the event in the store store[storeIndex] = ev; this.callback(this.manager, eventType, { pointers: store, changedPointers: [ev], pointerType: pointerType, srcEvent: ev }); if (removePointer) { // remove from the store store.splice(storeIndex, 1); } }; return PointerEventInput; }(Input); /** * @private * convert array-like objects to real arrays * @param {Object} obj * @returns {Array} */ function toArray(obj) { return Array.prototype.slice.call(obj, 0); } /** * @private * unique array with objects based on a key (like 'id') or just by the array's value * @param {Array} src [{id:1},{id:2},{id:1}] * @param {String} [key] * @param {Boolean} [sort=False] * @returns {Array} [{id:1},{id:2}] */ function uniqueArray(src, key, sort) { var results = []; var values = []; var i = 0; while (i < src.length) { var val = key ? src[i][key] : src[i]; if (inArray(values, val) < 0) { results.push(src[i]); } values[i] = val; i++; } if (sort) { if (!key) { results = results.sort(); } else { results = results.sort(function (a, b) { return a[key] > b[key]; }); } } return results; } var TOUCH_INPUT_MAP = { touchstart: INPUT_START, touchmove: INPUT_MOVE, touchend: INPUT_END, touchcancel: INPUT_CANCEL }; var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel'; /** * @private * Multi-user touch events input * @constructor * @extends Input */ var TouchInput = /*#__PURE__*/ function (_Input) { _inheritsLoose(TouchInput, _Input); function TouchInput() { var _this; TouchInput.prototype.evTarget = TOUCH_TARGET_EVENTS; _this = _Input.apply(this, arguments) || this; _this.targetIds = {}; // this.evTarget = TOUCH_TARGET_EVENTS; return _this; } var _proto = TouchInput.prototype; _proto.handler = function handler(ev) { var type = TOUCH_INPUT_MAP[ev.type]; var touches = getTouches.call(this, ev, type); if (!touches) { return; } this.callback(this.manager, type, { pointers: touches[0], changedPointers: touches[1], pointerType: INPUT_TYPE_TOUCH, srcEvent: ev }); }; return TouchInput; }(Input); function getTouches(ev, type) { var allTouches = toArray(ev.touches); var targetIds = this.targetIds; // when there is only one touch, the process can be simplified if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) { targetIds[allTouches[0].identifier] = true; return [allTouches, allTouches]; } var i; var targetTouches; var changedTouches = toArray(ev.changedTouches); var changedTargetTouches = []; var target = this.target; // get target touches from touches targetTouches = allTouches.filter(function (touch) { return hasParent(touch.target, target); }); // collect touches if (type === INPUT_START) { i = 0; while (i < targetTouches.length) { targetIds[targetTouches[i].identifier] = true; i++; } } // filter changed touches to only contain touches that exist in the collected target ids i = 0; while (i < changedTouches.length) { if (targetIds[changedTouches[i].identifier]) { changedTargetTouches.push(changedTouches[i]); } // cleanup removed touches if (type & (INPUT_END | INPUT_CANCEL)) { delete targetIds[changedTouches[i].identifier]; } i++; } if (!changedTargetTouches.length) { return; } return [// merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel' uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true), changedTargetTouches]; } var MOUSE_INPUT_MAP = { mousedown: INPUT_START, mousemove: INPUT_MOVE, mouseup: INPUT_END }; var MOUSE_ELEMENT_EVENTS = 'mousedown'; var MOUSE_WINDOW_EVENTS = 'mousemove mouseup'; /** * @private * Mouse events input * @constructor * @extends Input */ var MouseInput = /*#__PURE__*/ function (_Input) { _inheritsLoose(MouseInput, _Input); function MouseInput() { var _this; var proto = MouseInput.prototype; proto.evEl = MOUSE_ELEMENT_EVENTS; proto.evWin = MOUSE_WINDOW_EVENTS; _this = _Input.apply(this, arguments) || this; _this.pressed = false; // mousedown state return _this; } /** * @private * handle mouse events * @param {Object} ev */ var _proto = MouseInput.prototype; _proto.handler = function handler(ev) { var eventType = MOUSE_INPUT_MAP[ev.type]; // on start we want to have the left mouse button down if (eventType & INPUT_START && ev.button === 0) { this.pressed = true; } if (eventType & INPUT_MOVE && ev.which !== 1) { eventType = INPUT_END; } // mouse must be down if (!this.pressed) { return; } if (eventType & INPUT_END) { this.pressed = false; } this.callback(this.manager, eventType, { pointers: [ev], changedPointers: [ev], pointerType: INPUT_TYPE_MOUSE, srcEvent: ev }); }; return MouseInput; }(Input); /** * @private * Combined touch and mouse input * * Touch has a higher priority then mouse, and while touching no mouse events are allowed. * This because touch devices also emit mouse events while doing a touch. * * @constructor * @extends Input */ var DEDUP_TIMEOUT = 2500; var DEDUP_DISTANCE = 25; function setLastTouch(eventData) { var _eventData$changedPoi = eventData.changedPointers, touch = _eventData$changedPoi[0]; if (touch.identifier === this.primaryTouch) { var lastTouch = { x: touch.clientX, y: touch.clientY }; var lts = this.lastTouches; this.lastTouches.push(lastTouch); var removeLastTouch = function removeLastTouch() { var i = lts.indexOf(lastTouch); if (i > -1) { lts.splice(i, 1); } }; setTimeout(removeLastTouch, DEDUP_TIMEOUT); } } function recordTouches(eventType, eventData) { if (eventType & INPUT_START) { this.primaryTouch = eventData.changedPointers[0].identifier; setLastTouch.call(this, eventData); } else if (eventType & (INPUT_END | INPUT_CANCEL)) { setLastTouch.call(this, eventData); } } function isSyntheticEvent(eventData) { var x = eventData.srcEvent.clientX; var y = eventData.srcEvent.clientY; for (var i = 0; i < this.lastTouches.length; i++) { var t = this.lastTouches[i]; var dx = Math.abs(x - t.x); var dy = Math.abs(y - t.y); if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) { return true; } } return false; } var TouchMouseInput = /*#__PURE__*/ function () { var TouchMouseInput = /*#__PURE__*/ function (_Input) { _inheritsLoose(TouchMouseInput, _Input); function TouchMouseInput(_manager, callback) { var _this; _this = _Input.call(this, _manager, callback) || this; _this.handler = function (manager, inputEvent, inputData) { var isTouch = inputData.pointerType === INPUT_TYPE_TOUCH; var isMouse = inputData.pointerType === INPUT_TYPE_MOUSE; if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) { return; } // when we're in a touch event, record touches to de-dupe synthetic mouse event if (isTouch) { recordTouches.call(_assertThisInitialized(_assertThisInitialized(_this)), inputEvent, inputData); } else if (isMouse && isSyntheticEvent.call(_assertThisInitialized(_assertThisInitialized(_this)), inputData)) { return; } _this.callback(manager, inputEvent, inputData); }; _this.touch = new TouchInput(_this.manager, _this.handler); _this.mouse = new MouseInput(_this.manager, _this.handler); _this.primaryTouch = null; _this.lastTouches = []; return _this; } /** * @private * handle mouse and touch events * @param {Hammer} manager * @param {String} inputEvent * @param {Object} inputData */ var _proto = TouchMouseInput.prototype; /** * @private