UNPKG

@egjs/view360

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,837 lines (1,512 loc) 180 kB
/* Copyright (c) 2017 NAVER Corp. @egjs/view360 project is licensed under the MIT license @egjs/view360 JavaScript library https://github.com/naver/egjs-view360 @version 3.2.2-rc All-in-one packaged file for ease use of '@egjs/view360' with below dependencies. - @egjs/agent ^2.1.5, @egjs/axes ^2.5.8, @egjs/component ^2.1.2, es6-promise ^4.2.5, webvr-polyfill ^0.9.16 */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (factory((global.eg = global.eg || {}, global.eg.view360 = {}))); }(this, (function (exports) { 'use strict'; 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; } /* Copyright (c) 2017 NAVER Corp. @egjs/component project is licensed under the MIT license @egjs/component JavaScript library https://naver.github.io/egjs-component @version 2.1.2 */ /** * 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 () { var Component = /*#__PURE__*/ function () { /** * Version info string * @ko 버전정보 문자열 * @name VERSION * @static * @type {String} * @example * eg.Component.VERSION; // ex) 2.0.0 * @memberof eg.Component */ /** * @support {"ie": "7+", "ch" : "latest", "ff" : "latest", "sf" : "latest", "edge" : "latest", "ios" : "7+", "an" : "2.1+ (except 3.x)"} */ function Component() { this._eventHandler = {}; this.options = {}; } /** * 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> * @return {Boolean} 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 trigger(eventName, customEvent) { if (customEvent === void 0) { customEvent = {}; } var handlerList = this._eventHandler[eventName] || []; var hasHandlerList = handlerList.length > 0; if (!hasHandlerList) { return true; } // If detach method call in handler in first time then handler list calls. handlerList = handlerList.concat(); customEvent.eventType = eventName; var isCanceled = false; var arg = [customEvent]; var i = 0; customEvent.stop = function () { isCanceled = true; }; customEvent.currentTarget = this; for (var _len = arguments.length, restParam = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { restParam[_key - 2] = arguments[_key]; } if (restParam.length >= 1) { arg = arg.concat(restParam); } for (i = 0; handlerList[i]; i++) { handlerList[i].apply(this, arg); } return !isCanceled; }; /** * Executed event just one time. * @ko 이벤트가 한번만 실행된다. * @param {eventName} 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 {eg.Component} 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 once(eventName, handlerToAttach) { if (typeof eventName === "object" && isUndefined(handlerToAttach)) { var eventHash = eventName; var i; for (i in eventHash) { this.once(i, eventHash[i]); } return this; } else if (typeof eventName === "string" && typeof handlerToAttach === "function") { var self = this; this.on(eventName, function listener() { for (var _len2 = arguments.length, arg = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { arg[_key2] = arguments[_key2]; } handlerToAttach.apply(self, arg); self.off(eventName, listener); }); } 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 hasOn(eventName) { return !!this._eventHandler[eventName]; }; /** * Attaches an event to a component. * @ko 컴포넌트에 이벤트를 등록한다. * @param {eventName} 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 {eg.Component} 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 on(eventName, handlerToAttach) { if (typeof eventName === "object" && isUndefined(handlerToAttach)) { var eventHash = eventName; var name; for (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 {eventName} 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 {eg.Component} 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 off(eventName, handlerToDetach) { // All event detach. if (isUndefined(eventName)) { this._eventHandler = {}; return this; } // All handler of specific event detach. if (isUndefined(handlerToDetach)) { if (typeof eventName === "string") { this._eventHandler[eventName] = undefined; return this; } else { var eventHash = eventName; var name; for (name in eventHash) { this.off(name, eventHash[name]); } return this; } } // The handler of specific event detach. var handlerList = this._eventHandler[eventName]; if (handlerList) { var k; var handlerFunction; for (k = 0; (handlerFunction = handlerList[k]) !== undefined; k++) { if (handlerFunction === handlerToDetach) { handlerList = handlerList.splice(k, 1); break; } } } return this; }; return Component; }(); Component.VERSION = "2.1.2"; return Component; }(); /*! Hammer.JS - v2.0.13 - 2018-10-29 * http://naver.github.io/egjs * * Forked By Naver egjs * Copyright (c) hammerjs * Licensed under the MIT license */ function _extends$1() { _extends$1 = 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$1.apply(this, arguments); } function _inheritsLoose$1(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } function _assertThisInitialized$1(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; if (hasParent(input.srcEvent.target, target)) { target = input.srcEvent.target; } 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$1(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$1(TouchInput, _Input); function TouchInput() { TouchInput.prototype.evTarget = TOUCH_TARGET_EVENTS; TouchInput.prototype.targetIds = {}; return _Input.apply(this, arguments) || this; // this.evTarget = TOUCH_TARGET_EVENTS; // this.targetIds = {}; } 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$1(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$1(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$1(_assertThisInitialized$1(_this)), inputEvent, inputData); } else if (isMouse && isSyntheticEvent.call(_assertThisInitialized$1(_assertThisInitialized$1(_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 * remove the event listeners */ _proto.destroy = function destroy() { this.touch.destroy(); this.mouse.destroy(); }; return TouchMouseInput; }(Input); return TouchMouseInput; }(); /** * @private * create new input type manager * called by the Manager constructor * @param {Hammer} manager * @returns {Input} */ function createInputInstance(manager) { var Type; // let inputClass = manager.options.inputClass; var inputClass = manager.options.inputClass; if (inputClass) { Type = inputClass; } else if (SUPPORT_POINTER_EVENTS) { Type = PointerEventInput; } else if (SUPPORT_ONLY_TOUCH) { Type = TouchInput; } else if (!SUPPORT_TOUCH) { Type = MouseInput; } else { Type = TouchMouseInput; } return new Type(manager, inputHandler); } /** * @private * if the argument is an array, we want to execute the fn on each entry * if it aint an array we don't want to do a thing. * this is used by all the methods that accept a single and array argument. * @param {*|Array} arg * @param {String} fn * @param {Object} [context] * @returns {Boolean} */ function invokeArrayArg(arg, fn, context) { if (Array.isArray(arg)) { each(arg, context[fn], context); return true; } return false; } var STATE_POSSIBLE = 1; var STATE_BEGAN = 2; var STATE_CHANGED = 4; var STATE_ENDED = 8; var STATE_RECOGNIZED = STATE_ENDED; var STATE_CANCELLED = 16; var STATE_FAILED = 32; /** * @private * get a unique id * @returns {number} uniqueId */ var _uniqueId = 1; function uniqueId() { return _uniqueId++; } /** * @private * get a recognizer by name if it is bound to a manager * @param {Recognizer|String} otherRecognizer * @param {Recognizer} recognizer * @returns {Recognizer} */ function getRecognizerByNameIfManager(otherRecognizer, recognizer) { var manager = recognizer.manager; if (manager) { return manager.get(otherRecognizer); } return otherRecognizer; } /** * @private * get a usable string, used as event postfix * @param {constant} state * @returns {String} state */ function stateStr(state) { if (state & STATE_CANCELLED) { return 'cancel'; } else if (state & STATE_ENDED) { return 'end'; } else if (state & STATE_CHANGED) { return 'move'; } else if (state & STATE_BEGAN) { return 'start'; } return ''; } /** * @private * Recognizer flow explained; * * All recognizers have the initial state of POSSIBLE when a input session starts. * The definition of a input session is from the first input until the last input, with all it's movement in it. * * Example session for mouse-input: mousedown -> mousemove -> mouseup * * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed * which determines with state it should be. * * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to * POSSIBLE to give it another change on the next cycle. * * Possible * | * +-----+---------------+ * | | * +-----+-----+ | * | | | * Failed Cancelled | * +-------+------+ * | | * Recognized Began * | * Changed * | * Ended/Recognized */ /** * @private * Recognizer * Every recognizer needs to extend from this class. * @constructor * @param {Object} options */ var Recognizer = /*#__PURE__*/ function () { function Recognizer(options) { if (options === void 0) { options = {}; } this.options = _extends$1({ enable: true }, options); this.id = uniqueId(); this.manager = null; // default is enable true this.state = STATE_POSSIBLE; this.simultaneous = {}; this.requireFail = []; } /** * @private * set options * @param {Object} options * @return {Recognizer} */ var _proto = Recognizer.prototype; _proto.set = function set(options) { assign$1(this.options, options); // also update the touchAction, in case something changed about the directions/enabled state this.manager && this.manager.touchAction.update(); return this; }; /** * @private * recognize simultaneous with an other recognizer. * @param {Recognizer} otherRecognizer * @returns {Recognizer} this */ _proto.recognizeWith = function recognizeWith(otherRecognizer) { if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) { return this; } var simultaneous = this.simultaneous; otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); if (!simultaneous[otherRecognizer.id]) { simultaneous[otherRecognizer.id] = otherRecognizer; otherRecognizer.recognizeWith(this); } return this; }; /** * @private * drop the simulta