(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.panzoom = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 0) { transform.x += diff; adjusted = true; } // check the other side: diff = boundingBox.right - clientRect.left; if (diff < 0) { transform.x += diff; adjusted = true; } // y axis: diff = boundingBox.top - clientRect.bottom; if (diff > 0) { // we adjust transform, so that it matches exactly our bounding box: // transform.y = boundingBox.top - (boundingBox.height + boundingBox.y) * transform.scale => // transform.y = boundingBox.top - (clientRect.bottom - transform.y) => // transform.y = diff + transform.y => transform.y += diff; adjusted = true; } diff = boundingBox.bottom - clientRect.top; if (diff < 0) { transform.y += diff; adjusted = true; } return adjusted; } /** * Returns bounding box that should be used to restrict scene movement. */ function getBoundingBox() { if (!bounds) return; // client does not want to restrict movement if (typeof bounds === 'boolean') { // for boolean type we use parent container bounds var ownerRect = owner.getBoundingClientRect(); var sceneWidth = ownerRect.width; var sceneHeight = ownerRect.height; return { left: sceneWidth * boundsPadding, top: sceneHeight * boundsPadding, right: sceneWidth * (1 - boundsPadding), bottom: sceneHeight * (1 - boundsPadding) }; } return bounds; } function getClientRect() { var bbox = panController.getBBox(); var leftTop = client(bbox.left, bbox.top); return { left: leftTop.x, top: leftTop.y, right: bbox.width * transform.scale + leftTop.x, bottom: bbox.height * transform.scale + leftTop.y }; } function client(x, y) { return { x: x * transform.scale + transform.x, y: y * transform.scale + transform.y }; } function makeDirty() { isDirty = true; frameAnimation = window.requestAnimationFrame(frame); } function zoomByRatio(clientX, clientY, ratio) { if (isNaN(clientX) || isNaN(clientY) || isNaN(ratio)) { throw new Error('zoom requires valid numbers'); } var newScale = transform.scale * ratio; if (newScale < minZoom) { if (transform.scale === minZoom) return; ratio = minZoom / transform.scale; } if (newScale > maxZoom) { if (transform.scale === maxZoom) return; ratio = maxZoom / transform.scale; } var size = transformToScreen(clientX, clientY); transform.x = size.x - ratio * (size.x - transform.x); transform.y = size.y - ratio * (size.y - transform.y); if (bounds && boundsPadding === 1 && minZoom === 1) { transform.scale *= ratio; keepTransformInsideBounds(); } else { var transformAdjusted = keepTransformInsideBounds(); if (!transformAdjusted) transform.scale *= ratio; } triggerEvent('zoom'); makeDirty(); } function zoomAbs(clientX, clientY, zoomLevel) { var ratio = zoomLevel / transform.scale; zoomByRatio(clientX, clientY, ratio); } function centerOn(ui) { var parent = ui.ownerSVGElement; if (!parent) throw new Error('ui element is required to be within the scene'); var clientRect = ui.getBoundingClientRect(); var cx = clientRect.left + clientRect.width / 2; var cy = clientRect.top + clientRect.height / 2; var container = parent.getBoundingClientRect(); var dx = container.width / 2 - cx; var dy = container.height / 2 - cy; internalMoveBy(dx, dy, true); } function smoothMoveTo(x, y){ internalMoveBy(x - transform.x, y - transform.y, true) } function internalMoveBy(dx, dy, smooth) { if (!smooth) { return moveBy(dx, dy); } if (moveByAnimation) moveByAnimation.cancel(); var from = { x: 0, y: 0 }; var to = { x: dx, y: dy }; var lastX = 0; var lastY = 0; moveByAnimation = animate(from, to, { step: function (v) { moveBy(v.x - lastX, v.y - lastY); lastX = v.x; lastY = v.y; } }); } function scroll(x, y) { cancelZoomAnimation(); moveTo(x, y); } function dispose() { releaseEvents(); } function listenForEvents() { owner.addEventListener('mousedown', onMouseDown, { passive: false }); owner.addEventListener('dblclick', onDoubleClick, { passive: false }); owner.addEventListener('touchstart', onTouch, { passive: false }); owner.addEventListener('keydown', onKeyDown, { passive: false }); // Need to listen on the owner container, so that we are not limited // by the size of the scrollable domElement wheel.addWheelListener(owner, onMouseWheel, { passive: false }); makeDirty(); } function releaseEvents() { wheel.removeWheelListener(owner, onMouseWheel); owner.removeEventListener('mousedown', onMouseDown); owner.removeEventListener('keydown', onKeyDown); owner.removeEventListener('dblclick', onDoubleClick); owner.removeEventListener('touchstart', onTouch); if (frameAnimation) { window.cancelAnimationFrame(frameAnimation); frameAnimation = 0; } smoothScroll.cancel(); releaseDocumentMouse(); releaseTouches(); textSelection.release(); triggerPanEnd(); } function frame() { if (isDirty) applyTransform(); } function applyTransform() { isDirty = false; panController.applyTransform(transform); triggerEvent('transform'); frameAnimation = 0; } function onKeyDown(e) { var x = 0, y = 0, z = 0; if (e.keyCode === 38) { y = 1; // up } else if (e.keyCode === 40) { y = -1; // down } else if (e.keyCode === 37) { x = 1; // left } else if (e.keyCode === 39) { x = -1; // right } else if (e.keyCode === 189 || e.keyCode === 109) { // DASH or SUBTRACT z = 1; // `-` - zoom out } else if (e.keyCode === 187 || e.keyCode === 107) { // EQUAL SIGN or ADD z = -1; // `=` - zoom in (equal sign on US layout is under `+`) } if (filterKey(e, x, y, z)) { // They don't want us to handle the key: https://github.com/anvaka/panzoom/issues/45 return; } if (x || y) { e.preventDefault(); e.stopPropagation(); var clientRect = owner.getBoundingClientRect(); // movement speed should be the same in both X and Y direction: var offset = Math.min(clientRect.width, clientRect.height); var moveSpeedRatio = 0.05; var dx = offset * moveSpeedRatio * x; var dy = offset * moveSpeedRatio * y; internalMoveBy(dx, dy); } if (z) { var scaleMultiplier = getScaleMultiplier(z * 100); var offset = transformOrigin ? getTransformOriginOffset() : midPoint(); publicZoomTo(offset.x, offset.y, scaleMultiplier); } } function midPoint() { var ownerRect = owner.getBoundingClientRect(); return { x: ownerRect.width / 2, y: ownerRect.height / 2 }; } function onTouch(e) { // let the override the touch behavior beforeTouch(e); if (e.touches.length === 1) { return handleSingleFingerTouch(e, e.touches[0]); } else if (e.touches.length === 2) { // handleTouchMove() will care about pinch zoom. pinchZoomLength = getPinchZoomLength(e.touches[0], e.touches[1]); multiTouch = true; startTouchListenerIfNeeded(); } } function beforeTouch(e) { if (options.onTouch && !options.onTouch(e)) { // if they return `false` from onTouch, we don't want to stop // events propagation. Fixes https://github.com/anvaka/panzoom/issues/12 return; } e.stopPropagation(); e.preventDefault(); } function beforeDoubleClick(e) { if (options.onDoubleClick && !options.onDoubleClick(e)) { // if they return `false` from onTouch, we don't want to stop // events propagation. Fixes https://github.com/anvaka/panzoom/issues/46 return; } e.preventDefault(); e.stopPropagation(); } function handleSingleFingerTouch(e) { var touch = e.touches[0]; var offset = getOffsetXY(touch); lastSingleFingerOffset = offset; var point = transformToScreen(offset.x, offset.y); mouseX = point.x; mouseY = point.y; smoothScroll.cancel(); startTouchListenerIfNeeded(); } function startTouchListenerIfNeeded() { if (touchInProgress) { // no need to do anything, as we already listen to events; return; } touchInProgress = true; document.addEventListener('touchmove', handleTouchMove); document.addEventListener('touchend', handleTouchEnd); document.addEventListener('touchcancel', handleTouchEnd); } function handleTouchMove(e) { if (e.touches.length === 1) { e.stopPropagation(); var touch = e.touches[0]; var offset = getOffsetXY(touch); var point = transformToScreen(offset.x, offset.y); var dx = point.x - mouseX; var dy = point.y - mouseY; if (dx !== 0 && dy !== 0) { triggerPanStart(); } mouseX = point.x; mouseY = point.y; internalMoveBy(dx, dy); } else if (e.touches.length === 2) { // it's a zoom, let's find direction multiTouch = true; var t1 = e.touches[0]; var t2 = e.touches[1]; var currentPinchLength = getPinchZoomLength(t1, t2); // since the zoom speed is always based on distance from 1, we need to apply // pinch speed only on that distance from 1: var scaleMultiplier = 1 + (currentPinchLength / pinchZoomLength - 1) * pinchSpeed; var firstTouchPoint = getOffsetXY(t1); var secondTouchPoint = getOffsetXY(t2); mouseX = (firstTouchPoint.x + secondTouchPoint.x) / 2; mouseY = (firstTouchPoint.y + secondTouchPoint.y) / 2; if (transformOrigin) { var offset = getTransformOriginOffset(); mouseX = offset.x; mouseY = offset.y; } publicZoomTo(mouseX, mouseY, scaleMultiplier); pinchZoomLength = currentPinchLength; e.stopPropagation(); e.preventDefault(); } } function handleTouchEnd(e) { if (e.touches.length > 0) { var offset = getOffsetXY(e.touches[0]); var point = transformToScreen(offset.x, offset.y); mouseX = point.x; mouseY = point.y; } else { var now = new Date(); if (now - lastTouchEndTime < doubleTapSpeedInMS) { if (transformOrigin) { var offset = getTransformOriginOffset(); smoothZoom(offset.x, offset.y, zoomDoubleClickSpeed); } else { // We want untransformed x/y here. smoothZoom(lastSingleFingerOffset.x, lastSingleFingerOffset.y, zoomDoubleClickSpeed); } } lastTouchEndTime = now; triggerPanEnd(); releaseTouches(); } } function getPinchZoomLength(finger1, finger2) { var dx = finger1.clientX - finger2.clientX; var dy = finger1.clientY - finger2.clientY; return Math.sqrt(dx * dx + dy * dy); } function onDoubleClick(e) { beforeDoubleClick(e); var offset = getOffsetXY(e); if (transformOrigin) { // Need to refactor offset = getTransformOriginOffset(); } smoothZoom(offset.x, offset.y, zoomDoubleClickSpeed); } function onMouseDown(e) { // if client does not want to handle this event - just ignore the call if (beforeMouseDown(e)) return; if (touchInProgress) { // modern browsers will fire mousedown for touch events too // we do not want this: touch is handled separately. e.stopPropagation(); return false; } // for IE, left click == 1 // for Firefox, left click == 0 var isLeftButton = (e.button === 1 && window.event !== null) || e.button === 0; if (!isLeftButton) return; smoothScroll.cancel(); var offset = getOffsetXY(e); var point = transformToScreen(offset.x, offset.y); mouseX = point.x; mouseY = point.y; // We need to listen on document itself, since mouse can go outside of the // window, and we will loose it document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); textSelection.capture(e.target || e.srcElement); return false; } function onMouseMove(e) { // no need to worry about mouse events when touch is happening if (touchInProgress) return; triggerPanStart(); var offset = getOffsetXY(e); var point = transformToScreen(offset.x, offset.y); var dx = point.x - mouseX; var dy = point.y - mouseY; mouseX = point.x; mouseY = point.y; internalMoveBy(dx, dy); } function onMouseUp() { textSelection.release(); triggerPanEnd(); releaseDocumentMouse(); } function releaseDocumentMouse() { document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); panstartFired = false; } function releaseTouches() { document.removeEventListener('touchmove', handleTouchMove); document.removeEventListener('touchend', handleTouchEnd); document.removeEventListener('touchcancel', handleTouchEnd); panstartFired = false; multiTouch = false; touchInProgress = false; } function onMouseWheel(e) { // if client does not want to handle this event - just ignore the call if (beforeWheel(e)) return; smoothScroll.cancel(); var delta = e.deltaY; if (e.deltaMode > 0) delta *= 100; var scaleMultiplier = getScaleMultiplier(delta); if (scaleMultiplier !== 1) { var offset = transformOrigin ? getTransformOriginOffset() : getOffsetXY(e); publicZoomTo(offset.x, offset.y, scaleMultiplier); e.preventDefault(); } } function getOffsetXY(e) { var offsetX, offsetY; // I tried using e.offsetX, but that gives wrong results for svg, when user clicks on a path. var ownerRect = owner.getBoundingClientRect(); offsetX = e.clientX - ownerRect.left; offsetY = e.clientY - ownerRect.top; return { x: offsetX, y: offsetY }; } function smoothZoom(clientX, clientY, scaleMultiplier) { var fromValue = transform.scale; var from = { scale: fromValue }; var to = { scale: scaleMultiplier * fromValue }; smoothScroll.cancel(); cancelZoomAnimation(); zoomToAnimation = animate(from, to, { step: function (v) { zoomAbs(clientX, clientY, v.scale); }, done: triggerZoomEnd }); } function smoothZoomAbs(clientX, clientY, toScaleValue) { var fromValue = transform.scale; var from = { scale: fromValue }; var to = { scale: toScaleValue }; smoothScroll.cancel(); cancelZoomAnimation(); zoomToAnimation = animate(from, to, { step: function (v) { zoomAbs(clientX, clientY, v.scale); } }); } function getTransformOriginOffset() { var ownerRect = owner.getBoundingClientRect(); return { x: ownerRect.width * transformOrigin.x, y: ownerRect.height * transformOrigin.y }; } function publicZoomTo(clientX, clientY, scaleMultiplier) { smoothScroll.cancel(); cancelZoomAnimation(); return zoomByRatio(clientX, clientY, scaleMultiplier); } function cancelZoomAnimation() { if (zoomToAnimation) { zoomToAnimation.cancel(); zoomToAnimation = null; } } function getScaleMultiplier(delta) { var sign = Math.sign(delta); var deltaAdjustedSpeed = Math.min(0.25, Math.abs(speed * delta / 128)); return 1 - sign * deltaAdjustedSpeed; } function triggerPanStart() { if (!panstartFired) { triggerEvent('panstart'); panstartFired = true; smoothScroll.start(); } } function triggerPanEnd() { if (panstartFired) { // we should never run smooth scrolling if it was multiTouch (pinch zoom animation): if (!multiTouch) smoothScroll.stop(); triggerEvent('panend'); } } function triggerZoomEnd() { triggerEvent('zoomend'); } function triggerEvent(name) { api.fire(name, api); } } function parseTransformOrigin(options) { if (!options) return; if (typeof options === 'object') { if (!isNumber(options.x) || !isNumber(options.y)) failTransformOrigin(options); return options; } failTransformOrigin(); } function failTransformOrigin(options) { console.error(options); throw new Error( [ 'Cannot parse transform origin.', 'Some good examples:', ' "center center" can be achieved with {x: 0.5, y: 0.5}', ' "top center" can be achieved with {x: 0.5, y: 0}', ' "bottom right" can be achieved with {x: 1, y: 1}' ].join('\n') ); } function noop() { } function validateBounds(bounds) { var boundsType = typeof bounds; if (boundsType === 'undefined' || boundsType === 'boolean') return; // this is okay // otherwise need to be more thorough: var validBounds = isNumber(bounds.left) && isNumber(bounds.top) && isNumber(bounds.bottom) && isNumber(bounds.right); if (!validBounds) throw new Error( 'Bounds object is not valid. It can be: ' + 'undefined, boolean (true|false) or an object {left, top, right, bottom}' ); } function isNumber(x) { return Number.isFinite(x); } // IE 11 does not support isNaN: function isNaN(value) { if (Number.isNaN) { return Number.isNaN(value); } return value !== value; } function rigidScroll() { return { start: noop, stop: noop, cancel: noop }; } function autoRun() { if (typeof document === 'undefined') return; var scripts = document.getElementsByTagName('script'); if (!scripts) return; var panzoomScript; for (var i = 0; i < scripts.length; ++i) { var x = scripts[i]; if (x.src && x.src.match(/\bpanzoom(\.min)?\.js/)) { panzoomScript = x; break; } } if (!panzoomScript) return; var query = panzoomScript.getAttribute('query'); if (!query) return; var globalName = panzoomScript.getAttribute('name') || 'pz'; var started = Date.now(); tryAttach(); function tryAttach() { var el = document.querySelector(query); if (!el) { var now = Date.now(); var elapsed = now - started; if (elapsed < 2000) { // Let's wait a bit setTimeout(tryAttach, 100); return; } // If we don't attach within 2 seconds to the target element, consider it a failure console.error('Cannot find the panzoom element', globalName); return; } var options = collectOptions(panzoomScript); log(options); window[globalName] = createPanZoom(el, options); } function collectOptions(script) { var attrs = script.attributes; var options = {}; for (var i = 0; i < attrs.length; ++i) { var attr = attrs[i]; var nameValue = getPanzoomAttributeNameValue(attr); if (nameValue) { options[nameValue.name] = nameValue.value; } } return options; } function getPanzoomAttributeNameValue(attr) { if (!attr.name) return; var isPanZoomAttribute = attr.name[0] === 'p' && attr.name[1] === 'z' && attr.name[2] === '-'; if (!isPanZoomAttribute) return; var name = attr.name.substr(3); var value = JSON.parse(attr.value); return { name: name, value: value }; } } autoRun(); },{"./lib/createTextSelectionInterceptor.js":2,"./lib/domController.js":3,"./lib/kinetic.js":4,"./lib/svgController.js":5,"./lib/transform.js":6,"amator":7,"ngraph.events":9,"wheel":10}],2:[function(require,module,exports){ /** * Disallows selecting text. */ module.exports = createTextSelectionInterceptor; function createTextSelectionInterceptor(useFake) { if (useFake) { return { capture: noop, release: noop }; } var dragObject; var prevSelectStart; var prevDragStart; var wasCaptured = false; return { capture: capture, release: release }; function capture(domObject) { wasCaptured = true; prevSelectStart = window.document.onselectstart; prevDragStart = window.document.ondragstart; window.document.onselectstart = disabled; dragObject = domObject; dragObject.ondragstart = disabled; } function release() { if (!wasCaptured) return; wasCaptured = false; window.document.onselectstart = prevSelectStart; if (dragObject) dragObject.ondragstart = prevDragStart; } } function disabled(e) { e.stopPropagation(); return false; } function noop() {} },{}],3:[function(require,module,exports){ module.exports = makeDomController module.exports.canAttach = isDomElement; function makeDomController(domElement, options) { var elementValid = isDomElement(domElement); if (!elementValid) { throw new Error('panzoom requires DOM element to be attached to the DOM tree') } var owner = domElement.parentElement; domElement.scrollTop = 0; if (!options.disableKeyboardInteraction) { owner.setAttribute('tabindex', 0); } var api = { getBBox: getBBox, getOwner: getOwner, applyTransform: applyTransform, } return api function getOwner() { return owner } function getBBox() { // TODO: We should probably cache this? return { left: 0, top: 0, width: domElement.clientWidth, height: domElement.clientHeight } } function applyTransform(transform) { // TODO: Should we cache this? domElement.style.transformOrigin = '0 0 0'; domElement.style.transform = 'matrix(' + transform.scale + ', 0, 0, ' + transform.scale + ', ' + transform.x + ', ' + transform.y + ')' } } function isDomElement(element) { return element && element.parentElement && element.style; } },{}],4:[function(require,module,exports){ /** * Allows smooth kinetic scrolling of the surface */ module.exports = kinetic; function kinetic(getPoint, scroll, settings) { if (typeof settings !== 'object') { // setting could come as boolean, we should ignore it, and use an object. settings = {}; } var minVelocity = typeof settings.minVelocity === 'number' ? settings.minVelocity : 5; var amplitude = typeof settings.amplitude === 'number' ? settings.amplitude : 0.25; var cancelAnimationFrame = typeof settings.cancelAnimationFrame === 'function' ? settings.cancelAnimationFrame : getCancelAnimationFrame(); var requestAnimationFrame = typeof settings.requestAnimationFrame === 'function' ? settings.requestAnimationFrame : getRequestAnimationFrame(); var lastPoint; var timestamp; var timeConstant = 342; var ticker; var vx, targetX, ax; var vy, targetY, ay; var raf; return { start: start, stop: stop, cancel: dispose }; function dispose() { cancelAnimationFrame(ticker); cancelAnimationFrame(raf); } function start() { lastPoint = getPoint(); ax = ay = vx = vy = 0; timestamp = new Date(); cancelAnimationFrame(ticker); cancelAnimationFrame(raf); // we start polling the point position to accumulate velocity // Once we stop(), we will use accumulated velocity to keep scrolling // an object. ticker = requestAnimationFrame(track); } function track() { var now = Date.now(); var elapsed = now - timestamp; timestamp = now; var currentPoint = getPoint(); var dx = currentPoint.x - lastPoint.x; var dy = currentPoint.y - lastPoint.y; lastPoint = currentPoint; var dt = 1000 / (1 + elapsed); // moving average vx = 0.8 * dx * dt + 0.2 * vx; vy = 0.8 * dy * dt + 0.2 * vy; ticker = requestAnimationFrame(track); } function stop() { cancelAnimationFrame(ticker); cancelAnimationFrame(raf); var currentPoint = getPoint(); targetX = currentPoint.x; targetY = currentPoint.y; timestamp = Date.now(); if (vx < -minVelocity || vx > minVelocity) { ax = amplitude * vx; targetX += ax; } if (vy < -minVelocity || vy > minVelocity) { ay = amplitude * vy; targetY += ay; } raf = requestAnimationFrame(autoScroll); } function autoScroll() { var elapsed = Date.now() - timestamp; var moving = false; var dx = 0; var dy = 0; if (ax) { dx = -ax * Math.exp(-elapsed / timeConstant); if (dx > 0.5 || dx < -0.5) moving = true; else dx = ax = 0; } if (ay) { dy = -ay * Math.exp(-elapsed / timeConstant); if (dy > 0.5 || dy < -0.5) moving = true; else dy = ay = 0; } if (moving) { scroll(targetX + dx, targetY + dy); raf = requestAnimationFrame(autoScroll); } } } function getCancelAnimationFrame() { if (typeof cancelAnimationFrame === 'function') return cancelAnimationFrame; return clearTimeout; } function getRequestAnimationFrame() { if (typeof requestAnimationFrame === 'function') return requestAnimationFrame; return function (handler) { return setTimeout(handler, 16); } } },{}],5:[function(require,module,exports){ module.exports = makeSvgController module.exports.canAttach = isSVGElement; function makeSvgController(svgElement, options) { if (!isSVGElement(svgElement)) { throw new Error('svg element is required for svg.panzoom to work') } var owner = svgElement.ownerSVGElement if (!owner) { throw new Error( 'Do not apply panzoom to the root element. ' + 'Use its child instead (e.g. ). ' + 'As of March 2016 only FireFox supported transform on the root element') } if (!options.disableKeyboardInteraction) { owner.setAttribute('tabindex', 0); } var api = { getBBox: getBBox, getScreenCTM: getScreenCTM, getOwner: getOwner, applyTransform: applyTransform, initTransform: initTransform } return api function getOwner() { return owner } function getBBox() { var bbox = svgElement.getBBox() return { left: bbox.x, top: bbox.y, width: bbox.width, height: bbox.height, } } function getScreenCTM() { var ctm = owner.getCTM(); if (!ctm) { // This is likely firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=873106 // The code below is not entirely correct, but still better than nothing return owner.getScreenCTM(); } return ctm; } function initTransform(transform) { var screenCTM = svgElement.getCTM() // The above line returns null on Firefox if (screenCTM === null) { screenCTM = document.createElementNS("http://www.w3.org/2000/svg", "svg").createSVGMatrix() } transform.x = screenCTM.e; transform.y = screenCTM.f; transform.scale = screenCTM.a; owner.removeAttributeNS(null, 'viewBox'); } function applyTransform(transform) { svgElement.setAttribute('transform', 'matrix(' + transform.scale + ' 0 0 ' + transform.scale + ' ' + transform.x + ' ' + transform.y + ')') } } function isSVGElement(element) { return element && element.ownerSVGElement && element.getCTM; } },{}],6:[function(require,module,exports){ module.exports = Transform; function Transform() { this.x = 0; this.y = 0; this.scale = 1; } },{}],7:[function(require,module,exports){ var BezierEasing = require('bezier-easing') // Predefined set of animations. Similar to CSS easing functions var animations = { ease: BezierEasing(0.25, 0.1, 0.25, 1), easeIn: BezierEasing(0.42, 0, 1, 1), easeOut: BezierEasing(0, 0, 0.58, 1), easeInOut: BezierEasing(0.42, 0, 0.58, 1), linear: BezierEasing(0, 0, 1, 1) } module.exports = animate; module.exports.makeAggregateRaf = makeAggregateRaf; module.exports.sharedScheduler = makeAggregateRaf(); function animate(source, target, options) { var start = Object.create(null) var diff = Object.create(null) options = options || {} // We let clients specify their own easing function var easing = (typeof options.easing === 'function') ? options.easing : animations[options.easing] // if nothing is specified, default to ease (similar to CSS animations) if (!easing) { if (options.easing) { console.warn('Unknown easing function in amator: ' + options.easing); } easing = animations.ease } var step = typeof options.step === 'function' ? options.step : noop var done = typeof options.done === 'function' ? options.done : noop var scheduler = getScheduler(options.scheduler) var keys = Object.keys(target) keys.forEach(function(key) { start[key] = source[key] diff[key] = target[key] - source[key] }) var durationInMs = typeof options.duration === 'number' ? options.duration : 400 var durationInFrames = Math.max(1, durationInMs * 0.06) // 0.06 because 60 frames pers 1,000 ms var previousAnimationId var frame = 0 previousAnimationId = scheduler.next(loop) return { cancel: cancel } function cancel() { scheduler.cancel(previousAnimationId) previousAnimationId = 0 } function loop() { var t = easing(frame/durationInFrames) frame += 1 setValues(t) if (frame <= durationInFrames) { previousAnimationId = scheduler.next(loop) step(source) } else { previousAnimationId = 0 setTimeout(function() { done(source) }, 0) } } function setValues(t) { keys.forEach(function(key) { source[key] = diff[key] * t + start[key] }) } } function noop() { } function getScheduler(scheduler) { if (!scheduler) { var canRaf = typeof window !== 'undefined' && window.requestAnimationFrame return canRaf ? rafScheduler() : timeoutScheduler() } if (typeof scheduler.next !== 'function') throw new Error('Scheduler is supposed to have next(cb) function') if (typeof scheduler.cancel !== 'function') throw new Error('Scheduler is supposed to have cancel(handle) function') return scheduler } function rafScheduler() { return { next: window.requestAnimationFrame.bind(window), cancel: window.cancelAnimationFrame.bind(window) } } function timeoutScheduler() { return { next: function(cb) { return setTimeout(cb, 1000/60) }, cancel: function (id) { return clearTimeout(id) } } } function makeAggregateRaf() { var frontBuffer = new Set(); var backBuffer = new Set(); var frameToken = 0; return { next: next, cancel: next, clearAll: clearAll } function clearAll() { frontBuffer.clear(); backBuffer.clear(); cancelAnimationFrame(frameToken); frameToken = 0; } function next(callback) { backBuffer.add(callback); renderNextFrame(); } function renderNextFrame() { if (!frameToken) frameToken = requestAnimationFrame(renderFrame); } function renderFrame() { frameToken = 0; var t = backBuffer; backBuffer = frontBuffer; frontBuffer = t; frontBuffer.forEach(function(callback) { callback(); }); frontBuffer.clear(); } function cancel(callback) { backBuffer.delete(callback); } } },{"bezier-easing":8}],8:[function(require,module,exports){ /** * https://github.com/gre/bezier-easing * BezierEasing - use bezier curve for transition easing function * by Gaëtan Renaudeau 2014 - 2015 – MIT License */ // These values are established by empiricism with tests (tradeoff: performance VS precision) var NEWTON_ITERATIONS = 4; var NEWTON_MIN_SLOPE = 0.001; var SUBDIVISION_PRECISION = 0.0000001; var SUBDIVISION_MAX_ITERATIONS = 10; var kSplineTableSize = 11; var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); var float32ArraySupported = typeof Float32Array === 'function'; function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; } function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; } function C (aA1) { return 3.0 * aA1; } // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. function calcBezier (aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; } // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. function getSlope (aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); } function binarySubdivide (aX, aA, aB, mX1, mX2) { var currentX, currentT, i = 0; do { currentT = aA + (aB - aA) / 2.0; currentX = calcBezier(currentT, mX1, mX2) - aX; if (currentX > 0.0) { aB = currentT; } else { aA = currentT; } } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); return currentT; } function newtonRaphsonIterate (aX, aGuessT, mX1, mX2) { for (var i = 0; i < NEWTON_ITERATIONS; ++i) { var currentSlope = getSlope(aGuessT, mX1, mX2); if (currentSlope === 0.0) { return aGuessT; } var currentX = calcBezier(aGuessT, mX1, mX2) - aX; aGuessT -= currentX / currentSlope; } return aGuessT; } function LinearEasing (x) { return x; } module.exports = function bezier (mX1, mY1, mX2, mY2) { if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) { throw new Error('bezier x values must be in [0, 1] range'); } if (mX1 === mY1 && mX2 === mY2) { return LinearEasing; } // Precompute samples table var sampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize); for (var i = 0; i < kSplineTableSize; ++i) { sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); } function getTForX (aX) { var intervalStart = 0.0; var currentSample = 1; var lastSample = kSplineTableSize - 1; for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) { intervalStart += kSampleStepSize; } --currentSample; // Interpolate to provide an initial guess for t var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]); var guessForT = intervalStart + dist * kSampleStepSize; var initialSlope = getSlope(guessForT, mX1, mX2); if (initialSlope >= NEWTON_MIN_SLOPE) { return newtonRaphsonIterate(aX, guessForT, mX1, mX2); } else if (initialSlope === 0.0) { return guessForT; } else { return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2); } } return function BezierEasing (x) { // Because JavaScript number are imprecise, we should guarantee the extremes are right. if (x === 0) { return 0; } if (x === 1) { return 1; } return calcBezier(getTForX(x), mY1, mY2); }; }; },{}],9:[function(require,module,exports){ module.exports = function eventify(subject) { validateSubject(subject); var eventsStorage = createEventsStorage(subject); subject.on = eventsStorage.on; subject.off = eventsStorage.off; subject.fire = eventsStorage.fire; return subject; }; function createEventsStorage(subject) { // Store all event listeners to this hash. Key is event name, value is array // of callback records. // // A callback record consists of callback function and its optional context: // { 'eventName' => [{callback: function, ctx: object}] } var registeredEvents = Object.create(null); return { on: function (eventName, callback, ctx) { if (typeof callback !== 'function') { throw new Error('callback is expected to be a function'); } var handlers = registeredEvents[eventName]; if (!handlers) { handlers = registeredEvents[eventName] = []; } handlers.push({callback: callback, ctx: ctx}); return subject; }, off: function (eventName, callback) { var wantToRemoveAll = (typeof eventName === 'undefined'); if (wantToRemoveAll) { // Killing old events storage should be enough in this case: registeredEvents = Object.create(null); return subject; } if (registeredEvents[eventName]) { var deleteAllCallbacksForEvent = (typeof callback !== 'function'); if (deleteAllCallbacksForEvent) { delete registeredEvents[eventName]; } else { var callbacks = registeredEvents[eventName]; for (var i = 0; i < callbacks.length; ++i) { if (callbacks[i].callback === callback) { callbacks.splice(i, 1); } } } } return subject; }, fire: function (eventName) { var callbacks = registeredEvents[eventName]; if (!callbacks) { return subject; } var fireArguments; if (arguments.length > 1) { fireArguments = Array.prototype.splice.call(arguments, 1); } for(var i = 0; i < callbacks.length; ++i) { var callbackInfo = callbacks[i]; callbackInfo.callback.apply(callbackInfo.ctx, fireArguments); } return subject; } }; } function validateSubject(subject) { if (!subject) { throw new Error('Eventify cannot use falsy object as events subject'); } var reservedWords = ['on', 'fire', 'off']; for (var i = 0; i < reservedWords.length; ++i) { if (subject.hasOwnProperty(reservedWords[i])) { throw new Error("Subject cannot be eventified, since it already has property '" + reservedWords[i] + "'"); } } } },{}],10:[function(require,module,exports){ /** * This module used to unify mouse wheel behavior between different browsers in 2014 * Now it's just a wrapper around addEventListener('wheel'); * * Usage: * var addWheelListener = require('wheel').addWheelListener; * var removeWheelListener = require('wheel').removeWheelListener; * addWheelListener(domElement, function (e) { * // mouse wheel event * }); * removeWheelListener(domElement, function); */ module.exports = addWheelListener; // But also expose "advanced" api with unsubscribe: module.exports.addWheelListener = addWheelListener; module.exports.removeWheelListener = removeWheelListener; function addWheelListener(element, listener, useCapture) { element.addEventListener('wheel', listener, useCapture); } function removeWheelListener( element, listener, useCapture ) { element.removeEventListener('wheel', listener, useCapture); } },{}]},{},[1])(1) });