UNPKG

svg-pan-zoom-m

Version:

JavaScript library for panning and zooming an SVG image from the mouse, touches and programmatically.

297 lines (274 loc) 8.16 kB
module.exports = { /** * Extends an object * * @param {Object} target object to extend * @param {Object} source object to take properties from * @return {Object} extended object */ extend: function(target, source) { target = target || {}; for (var prop in source) { // Go recursively if (this.isObject(source[prop])) { target[prop] = this.extend(target[prop], source[prop]); } else { target[prop] = source[prop]; } } return target; }, /** * Checks if an object is a DOM element * * @param {Object} o HTML element or String * @return {Boolean} returns true if object is a DOM element */ isElement: function(o) { return ( o instanceof HTMLElement || o instanceof SVGElement || o instanceof SVGSVGElement || //DOM2 (o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string") ); }, /** * Checks if an object is an Object * * @param {Object} o Object * @return {Boolean} returns true if object is an Object */ isObject: function(o) { return Object.prototype.toString.call(o) === "[object Object]"; }, /** * Checks if variable is Number * * @param {Integer|Float} n * @return {Boolean} returns true if variable is Number */ isNumber: function(n) { return !isNaN(parseFloat(n)) && isFinite(n); }, /** * Search for an SVG element * * @param {Object|String} elementOrSelector DOM Element or selector String * @return {Object|Null} SVG or null */ getSvg: function(elementOrSelector) { var element, svg; if (!this.isElement(elementOrSelector)) { // If selector provided if ( typeof elementOrSelector === "string" || elementOrSelector instanceof String ) { // Try to find the element element = document.querySelector(elementOrSelector); if (!element) { throw new Error( "Provided selector did not find any elements. Selector: " + elementOrSelector ); return null; } } else { throw new Error("Provided selector is not an HTML object nor String"); return null; } } else { element = elementOrSelector; } if (element.tagName.toLowerCase() === "svg") { svg = element; } else { if (element.tagName.toLowerCase() === "object") { svg = element.contentDocument.documentElement; } else { if (element.tagName.toLowerCase() === "embed") { svg = element.getSVGDocument().documentElement; } else { if (element.tagName.toLowerCase() === "img") { throw new Error( 'Cannot script an SVG in an "img" element. Please use an "object" element or an in-line SVG.' ); } else { throw new Error("Cannot get SVG."); } return null; } } } return svg; }, /** * Attach a given context to a function * @param {Function} fn Function * @param {Object} context Context * @return {Function} Function with certain context */ proxy: function(fn, context) { return function() { return fn.apply(context, arguments); }; }, /** * Returns object type * Uses toString that returns [object SVGPoint] * And than parses object type from string * * @param {Object} o Any object * @return {String} Object type */ getType: function(o) { return Object.prototype.toString .apply(o) .replace(/^\[object\s/, "") .replace(/\]$/, ""); }, /** * If it is a touch event than add clientX and clientY to event object * * @param {Event} evt * @param {SVGSVGElement} svg */ mouseAndTouchNormalize: function(evt, svg) { // If no clientX then fallback if (evt.clientX === void 0 || evt.clientX === null) { // Fallback evt.clientX = 0; evt.clientY = 0; // If it is a touch event if (evt.touches !== void 0 && evt.touches.length) { if (evt.touches[0].clientX !== void 0) { evt.clientX = evt.touches[0].clientX; evt.clientY = evt.touches[0].clientY; } else if (evt.touches[0].pageX !== void 0) { var rect = svg.getBoundingClientRect(); evt.clientX = evt.touches[0].pageX - rect.left; evt.clientY = evt.touches[0].pageY - rect.top; } // If it is a custom event } else if (evt.originalEvent !== void 0) { if (evt.originalEvent.clientX !== void 0) { evt.clientX = evt.originalEvent.clientX; evt.clientY = evt.originalEvent.clientY; } } } }, /** * Check if an event is a double click/tap * TODO: For touch gestures use a library (hammer.js) that takes in account other events * (touchmove and touchend). It should take in account tap duration and traveled distance * * @param {Event} evt * @param {Event} prevEvt Previous Event * @return {Boolean} */ isDblClick: function(evt, prevEvt) { // Double click detected by browser if (evt.detail === 2) { return true; } // Try to compare events else if (prevEvt !== void 0 && prevEvt !== null) { var timeStampDiff = evt.timeStamp - prevEvt.timeStamp, // should be lower than 250 ms touchesDistance = Math.sqrt( Math.pow(evt.clientX - prevEvt.clientX, 2) + Math.pow(evt.clientY - prevEvt.clientY, 2) ); return timeStampDiff < 250 && touchesDistance < 10; } // Nothing found return false; }, /** * Returns current timestamp as an integer * * @return {Number} */ now: Date.now || function() { return new Date().getTime(); }, // From underscore. // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. Normally, the throttled function will run // as much as it can, without ever going more than once per `wait` duration; // but if you'd like to disable the execution on the leading edge, pass // `{leading: false}`. To disable execution on the trailing edge, ditto. throttle: function(func, wait, options) { var that = this; var context, args, result; var timeout = null; var previous = 0; if (!options) { options = {}; } var later = function() { previous = options.leading === false ? 0 : that.now(); timeout = null; result = func.apply(context, args); if (!timeout) { context = args = null; } }; return function() { var now = that.now(); if (!previous && options.leading === false) { previous = now; } var remaining = wait - (now - previous); context = this; // eslint-disable-line consistent-this args = arguments; if (remaining <= 0 || remaining > wait) { clearTimeout(timeout); timeout = null; previous = now; result = func.apply(context, args); if (!timeout) { context = args = null; } } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; }, /** * Create a requestAnimationFrame simulation * * @param {Number|String} refreshRate * @return {Function} */ createRequestAnimationFrame: function(refreshRate) { var timeout = null; // Convert refreshRate to timeout if (refreshRate !== "auto" && refreshRate < 60 && refreshRate > 1) { timeout = Math.floor(1000 / refreshRate); } if (timeout === null) { return window.requestAnimationFrame || requestTimeout(33); } else { return requestTimeout(timeout); } } }; /** * Create a callback that will execute after a given timeout * * @param {Function} timeout * @return {Function} */ function requestTimeout(timeout) { return function(callback) { window.setTimeout(callback, timeout); }; }