2018-10-31 03:22:05 +08:00
|
|
|
|
(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<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
/* globals SVGElement */
|
|
|
|
|
/**
|
|
|
|
|
* Allows to drag and zoom svg elements
|
|
|
|
|
*/
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var wheel = require('wheel');
|
|
|
|
|
var animate = require('amator');
|
2018-10-31 03:22:05 +08:00
|
|
|
|
var eventify = require('ngraph.events');
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var kinetic = require('./lib/kinetic.js');
|
|
|
|
|
var createTextSelectionInterceptor = require('./lib/createTextSelectionInterceptor.js');
|
|
|
|
|
var domTextSelectionInterceptor = createTextSelectionInterceptor();
|
|
|
|
|
var fakeTextSelectorInterceptor = createTextSelectionInterceptor(true);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
var Transform = require('./lib/transform.js');
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var makeSvgController = require('./lib/svgController.js');
|
|
|
|
|
var makeDomController = require('./lib/domController.js');
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var defaultZoomSpeed = 1;
|
|
|
|
|
var defaultDoubleTapZoomSpeed = 1.75;
|
|
|
|
|
var doubleTapSpeedInMS = 300;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
module.exports = createPanZoom;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a new instance of panzoom, so that an object can be panned and zoomed
|
|
|
|
|
*
|
|
|
|
|
* @param {DOMElement} domElement where panzoom should be attached.
|
|
|
|
|
* @param {Object} options that configure behavior.
|
|
|
|
|
*/
|
|
|
|
|
function createPanZoom(domElement, options) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
options = options || {};
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var panController = options.controller;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
if (!panController) {
|
|
|
|
|
if (domElement instanceof SVGElement) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
panController = makeSvgController(domElement, options);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (domElement instanceof HTMLElement) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
panController = makeDomController(domElement, options);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!panController) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
throw new Error(
|
|
|
|
|
'Cannot create panzoom for the current type of dom element'
|
|
|
|
|
);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var owner = panController.getOwner();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
// just to avoid GC pressure, every time we do intermediate transform
|
|
|
|
|
// we return this object. For internal use only. Never give it back to the consumer of this library
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var storedCTMResult = { x: 0, y: 0 };
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var isDirty = false;
|
|
|
|
|
var transform = new Transform();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
if (panController.initTransform) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
panController.initTransform(transform);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var filterKey = typeof options.filterKey === 'function' ? options.filterKey : noop;
|
2019-05-16 03:50:27 +08:00
|
|
|
|
// TODO: likely need to unite pinchSpeed with zoomSpeed
|
|
|
|
|
var pinchSpeed = typeof options.pinchSpeed === 'number' ? options.pinchSpeed : 1;
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var bounds = options.bounds;
|
|
|
|
|
var maxZoom = typeof options.maxZoom === 'number' ? options.maxZoom : Number.POSITIVE_INFINITY;
|
|
|
|
|
var minZoom = typeof options.minZoom === 'number' ? options.minZoom : 0;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var boundsPadding = typeof options.boundsPadding === 'number' ? options.boundsPadding : 0.05;
|
|
|
|
|
var zoomDoubleClickSpeed = typeof options.zoomDoubleClickSpeed === 'number' ? options.zoomDoubleClickSpeed : defaultDoubleTapZoomSpeed;
|
|
|
|
|
var beforeWheel = options.beforeWheel || noop;
|
|
|
|
|
var beforeMouseDown = options.beforeMouseDown || noop;
|
|
|
|
|
var speed = typeof options.zoomSpeed === 'number' ? options.zoomSpeed : defaultZoomSpeed;
|
|
|
|
|
var transformOrigin = parseTransformOrigin(options.transformOrigin);
|
|
|
|
|
var textSelection = options.enableTextSelection ? fakeTextSelectorInterceptor : domTextSelectionInterceptor;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
validateBounds(bounds);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
if (options.autocenter) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
autocenter();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var frameAnimation;
|
|
|
|
|
var lastTouchEndTime = 0;
|
|
|
|
|
var lastSingleFingerOffset;
|
|
|
|
|
var touchInProgress = false;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
// We only need to fire panstart when actual move happens
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var panstartFired = false;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
// cache mouse coordinates here
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var mouseX;
|
|
|
|
|
var mouseY;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var pinchZoomLength;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var smoothScroll;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
if ('smoothScroll' in options && !options.smoothScroll) {
|
|
|
|
|
// If user explicitly asked us not to use smooth scrolling, we obey
|
2020-03-08 03:41:03 +08:00
|
|
|
|
smoothScroll = rigidScroll();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
} else {
|
|
|
|
|
// otherwise we use forward smoothScroll settings to kinetic API
|
|
|
|
|
// which makes scroll smoothing.
|
2020-03-08 03:41:03 +08:00
|
|
|
|
smoothScroll = kinetic(getPoint, scroll, options.smoothScroll);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var moveByAnimation;
|
|
|
|
|
var zoomToAnimation;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var multiTouch;
|
|
|
|
|
var paused = false;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
listenForEvents();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
var api = {
|
|
|
|
|
dispose: dispose,
|
|
|
|
|
moveBy: internalMoveBy,
|
|
|
|
|
moveTo: moveTo,
|
|
|
|
|
centerOn: centerOn,
|
|
|
|
|
zoomTo: publicZoomTo,
|
|
|
|
|
zoomAbs: zoomAbs,
|
|
|
|
|
smoothZoom: smoothZoom,
|
2020-03-08 03:41:03 +08:00
|
|
|
|
smoothZoomAbs: smoothZoomAbs,
|
2018-10-31 03:22:05 +08:00
|
|
|
|
showRectangle: showRectangle,
|
|
|
|
|
|
|
|
|
|
pause: pause,
|
|
|
|
|
resume: resume,
|
|
|
|
|
isPaused: isPaused,
|
2019-08-29 05:08:05 +08:00
|
|
|
|
|
|
|
|
|
getTransform: getTransformModel,
|
2020-03-08 03:41:03 +08:00
|
|
|
|
|
2019-08-29 05:08:05 +08:00
|
|
|
|
getMinZoom: getMinZoom,
|
2020-03-08 03:41:03 +08:00
|
|
|
|
setMinZoom: setMinZoom,
|
|
|
|
|
|
2019-08-29 05:08:05 +08:00
|
|
|
|
getMaxZoom: getMaxZoom,
|
2020-03-08 03:41:03 +08:00
|
|
|
|
setMaxZoom: setMaxZoom,
|
|
|
|
|
|
|
|
|
|
getTransformOrigin: getTransformOrigin,
|
|
|
|
|
setTransformOrigin: setTransformOrigin,
|
|
|
|
|
|
|
|
|
|
getZoomSpeed: getZoomSpeed,
|
|
|
|
|
setZoomSpeed: setZoomSpeed,
|
|
|
|
|
|
2019-08-29 05:08:05 +08:00
|
|
|
|
getOwner: () => owner
|
2020-03-08 03:41:03 +08:00
|
|
|
|
};
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
eventify(api);
|
|
|
|
|
|
|
|
|
|
return api;
|
|
|
|
|
|
|
|
|
|
function pause() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
releaseEvents();
|
|
|
|
|
paused = true;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resume() {
|
|
|
|
|
if (paused) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
listenForEvents();
|
|
|
|
|
paused = false;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isPaused() {
|
|
|
|
|
return paused;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function showRectangle(rect) {
|
|
|
|
|
// TODO: this duplicates autocenter. I think autocenter should go.
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var clientRect = owner.getBoundingClientRect();
|
|
|
|
|
var size = transformToScreen(clientRect.width, clientRect.height);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var rectWidth = rect.right - rect.left;
|
|
|
|
|
var rectHeight = rect.bottom - rect.top;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
if (!Number.isFinite(rectWidth) || !Number.isFinite(rectHeight)) {
|
|
|
|
|
throw new Error('Invalid rectangle');
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var dw = size.x / rectWidth;
|
|
|
|
|
var dh = size.y / rectHeight;
|
|
|
|
|
var scale = Math.min(dw, dh);
|
|
|
|
|
transform.x = -(rect.left + rectWidth / 2) * scale + size.x / 2;
|
|
|
|
|
transform.y = -(rect.top + rectHeight / 2) * scale + size.y / 2;
|
|
|
|
|
transform.scale = scale;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function transformToScreen(x, y) {
|
|
|
|
|
if (panController.getScreenCTM) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var parentCTM = panController.getScreenCTM();
|
|
|
|
|
var parentScaleX = parentCTM.a;
|
|
|
|
|
var parentScaleY = parentCTM.d;
|
|
|
|
|
var parentOffsetX = parentCTM.e;
|
|
|
|
|
var parentOffsetY = parentCTM.f;
|
|
|
|
|
storedCTMResult.x = x * parentScaleX - parentOffsetX;
|
|
|
|
|
storedCTMResult.y = y * parentScaleY - parentOffsetY;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
} else {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
storedCTMResult.x = x;
|
|
|
|
|
storedCTMResult.y = y;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
return storedCTMResult;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function autocenter() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var w; // width of the parent
|
|
|
|
|
var h; // height of the parent
|
|
|
|
|
var left = 0;
|
|
|
|
|
var top = 0;
|
|
|
|
|
var sceneBoundingBox = getBoundingBox();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
if (sceneBoundingBox) {
|
|
|
|
|
// If we have bounding box - use it.
|
2020-03-08 03:41:03 +08:00
|
|
|
|
left = sceneBoundingBox.left;
|
|
|
|
|
top = sceneBoundingBox.top;
|
|
|
|
|
w = sceneBoundingBox.right - sceneBoundingBox.left;
|
|
|
|
|
h = sceneBoundingBox.bottom - sceneBoundingBox.top;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
} else {
|
|
|
|
|
// otherwise just use whatever space we have
|
|
|
|
|
var ownerRect = owner.getBoundingClientRect();
|
2020-03-08 03:41:03 +08:00
|
|
|
|
w = ownerRect.width;
|
|
|
|
|
h = ownerRect.height;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var bbox = panController.getBBox();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
if (bbox.width === 0 || bbox.height === 0) {
|
|
|
|
|
// we probably do not have any elements in the SVG
|
|
|
|
|
// just bail out;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var dh = h / bbox.height;
|
|
|
|
|
var dw = w / bbox.width;
|
|
|
|
|
var scale = Math.min(dw, dh);
|
|
|
|
|
transform.x = -(bbox.left + bbox.width / 2) * scale + w / 2 + left;
|
|
|
|
|
transform.y = -(bbox.top + bbox.height / 2) * scale + h / 2 + top;
|
|
|
|
|
transform.scale = scale;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getTransformModel() {
|
|
|
|
|
// TODO: should this be read only?
|
2020-03-08 03:41:03 +08:00
|
|
|
|
return transform;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-29 05:08:05 +08:00
|
|
|
|
function getMinZoom() {
|
|
|
|
|
return minZoom;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
function setMinZoom(newMinZoom) {
|
|
|
|
|
minZoom = newMinZoom;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-29 05:08:05 +08:00
|
|
|
|
function getMaxZoom() {
|
|
|
|
|
return maxZoom;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
function setMaxZoom(newMaxZoom) {
|
|
|
|
|
maxZoom = newMaxZoom;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getTransformOrigin() {
|
|
|
|
|
return transformOrigin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setTransformOrigin(newTransformOrigin) {
|
|
|
|
|
transformOrigin = parseTransformOrigin(newTransformOrigin);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getZoomSpeed() {
|
|
|
|
|
return speed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setZoomSpeed(newSpeed) {
|
|
|
|
|
if (!Number.isFinite(newSpeed)) {
|
|
|
|
|
throw new Error('Zoom speed should be a number');
|
|
|
|
|
}
|
|
|
|
|
speed = newSpeed;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-31 03:22:05 +08:00
|
|
|
|
function getPoint() {
|
|
|
|
|
return {
|
|
|
|
|
x: transform.x,
|
|
|
|
|
y: transform.y
|
2020-03-08 03:41:03 +08:00
|
|
|
|
};
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function moveTo(x, y) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
transform.x = x;
|
|
|
|
|
transform.y = y;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
keepTransformInsideBounds();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
triggerEvent('pan');
|
|
|
|
|
makeDirty();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function moveBy(dx, dy) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
moveTo(transform.x + dx, transform.y + dy);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function keepTransformInsideBounds() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var boundingBox = getBoundingBox();
|
|
|
|
|
if (!boundingBox) return;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var adjusted = false;
|
|
|
|
|
var clientRect = getClientRect();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var diff = boundingBox.left - clientRect.right;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
if (diff > 0) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
transform.x += diff;
|
|
|
|
|
adjusted = true;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
// check the other side:
|
2020-03-08 03:41:03 +08:00
|
|
|
|
diff = boundingBox.right - clientRect.left;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
if (diff < 0) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
transform.x += diff;
|
|
|
|
|
adjusted = true;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// y axis:
|
2020-03-08 03:41:03 +08:00
|
|
|
|
diff = boundingBox.top - clientRect.bottom;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
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 =>
|
2020-03-08 03:41:03 +08:00
|
|
|
|
transform.y += diff;
|
|
|
|
|
adjusted = true;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
diff = boundingBox.bottom - clientRect.top;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
if (diff < 0) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
transform.y += diff;
|
|
|
|
|
adjusted = true;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
2020-03-08 03:41:03 +08:00
|
|
|
|
return adjusted;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns bounding box that should be used to restrict scene movement.
|
|
|
|
|
*/
|
|
|
|
|
function getBoundingBox() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
if (!bounds) return; // client does not want to restrict movement
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
if (typeof bounds === 'boolean') {
|
|
|
|
|
// for boolean type we use parent container bounds
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var ownerRect = owner.getBoundingClientRect();
|
|
|
|
|
var sceneWidth = ownerRect.width;
|
|
|
|
|
var sceneHeight = ownerRect.height;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
left: sceneWidth * boundsPadding,
|
|
|
|
|
top: sceneHeight * boundsPadding,
|
|
|
|
|
right: sceneWidth * (1 - boundsPadding),
|
2020-03-08 03:41:03 +08:00
|
|
|
|
bottom: sceneHeight * (1 - boundsPadding)
|
|
|
|
|
};
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
return bounds;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getClientRect() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var bbox = panController.getBBox();
|
|
|
|
|
var leftTop = client(bbox.left, bbox.top);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
left: leftTop.x,
|
|
|
|
|
top: leftTop.y,
|
|
|
|
|
right: bbox.width * transform.scale + leftTop.x,
|
|
|
|
|
bottom: bbox.height * transform.scale + leftTop.y
|
2020-03-08 03:41:03 +08:00
|
|
|
|
};
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function client(x, y) {
|
|
|
|
|
return {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
x: x * transform.scale + transform.x,
|
|
|
|
|
y: y * transform.scale + transform.y
|
|
|
|
|
};
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function makeDirty() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
isDirty = true;
|
|
|
|
|
frameAnimation = window.requestAnimationFrame(frame);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function zoomByRatio(clientX, clientY, ratio) {
|
|
|
|
|
if (isNaN(clientX) || isNaN(clientY) || isNaN(ratio)) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
throw new Error('zoom requires valid numbers');
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var newScale = transform.scale * ratio;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
if (newScale < minZoom) {
|
|
|
|
|
if (transform.scale === minZoom) return;
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
ratio = minZoom / transform.scale;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
if (newScale > maxZoom) {
|
|
|
|
|
if (transform.scale === maxZoom) return;
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
ratio = maxZoom / transform.scale;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var size = transformToScreen(clientX, clientY);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
transform.x = size.x - ratio * (size.x - transform.x);
|
|
|
|
|
transform.y = size.y - ratio * (size.y - transform.y);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2019-08-29 05:08:05 +08:00
|
|
|
|
// TODO: https://github.com/anvaka/panzoom/issues/112
|
|
|
|
|
if (bounds && boundsPadding === 1 && minZoom === 1) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
transform.scale *= ratio;
|
|
|
|
|
keepTransformInsideBounds();
|
2019-08-29 05:08:05 +08:00
|
|
|
|
} else {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var transformAdjusted = keepTransformInsideBounds();
|
|
|
|
|
if (!transformAdjusted) transform.scale *= ratio;
|
2019-08-29 05:08:05 +08:00
|
|
|
|
}
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
triggerEvent('zoom');
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
makeDirty();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function zoomAbs(clientX, clientY, zoomLevel) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var ratio = zoomLevel / transform.scale;
|
|
|
|
|
zoomByRatio(clientX, clientY, ratio);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function centerOn(ui) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var parent = ui.ownerSVGElement;
|
|
|
|
|
if (!parent)
|
|
|
|
|
throw new Error('ui element is required to be within the scene');
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
// TODO: should i use controller's screen CTM?
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var clientRect = ui.getBoundingClientRect();
|
|
|
|
|
var cx = clientRect.left + clientRect.width / 2;
|
|
|
|
|
var cy = clientRect.top + clientRect.height / 2;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var container = parent.getBoundingClientRect();
|
|
|
|
|
var dx = container.width / 2 - cx;
|
|
|
|
|
var dy = container.height / 2 - cy;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
internalMoveBy(dx, dy, true);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function internalMoveBy(dx, dy, smooth) {
|
|
|
|
|
if (!smooth) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
return moveBy(dx, dy);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
if (moveByAnimation) moveByAnimation.cancel();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var from = { x: 0, y: 0 };
|
|
|
|
|
var to = { x: dx, y: dy };
|
|
|
|
|
var lastX = 0;
|
|
|
|
|
var lastY = 0;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
moveByAnimation = animate(from, to, {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
step: function (v) {
|
|
|
|
|
moveBy(v.x - lastX, v.y - lastY);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
lastX = v.x;
|
|
|
|
|
lastY = v.y;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
2020-03-08 03:41:03 +08:00
|
|
|
|
});
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function scroll(x, y) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
cancelZoomAnimation();
|
|
|
|
|
moveTo(x, y);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function dispose() {
|
|
|
|
|
releaseEvents();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function listenForEvents() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
owner.addEventListener('mousedown', onMouseDown, { passive: false });
|
|
|
|
|
owner.addEventListener('dblclick', onDoubleClick, { passive: false });
|
|
|
|
|
owner.addEventListener('touchstart', onTouch, { passive: false });
|
|
|
|
|
owner.addEventListener('keydown', onKeyDown, { passive: false });
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
// Need to listen on the owner container, so that we are not limited
|
|
|
|
|
// by the size of the scrollable domElement
|
2020-03-08 03:41:03 +08:00
|
|
|
|
wheel.addWheelListener(owner, onMouseWheel, { passive: false });
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
makeDirty();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function releaseEvents() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
wheel.removeWheelListener(owner, onMouseWheel);
|
|
|
|
|
owner.removeEventListener('mousedown', onMouseDown);
|
|
|
|
|
owner.removeEventListener('keydown', onKeyDown);
|
|
|
|
|
owner.removeEventListener('dblclick', onDoubleClick);
|
|
|
|
|
owner.removeEventListener('touchstart', onTouch);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
if (frameAnimation) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
window.cancelAnimationFrame(frameAnimation);
|
|
|
|
|
frameAnimation = 0;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
smoothScroll.cancel();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
releaseDocumentMouse();
|
|
|
|
|
releaseTouches();
|
|
|
|
|
textSelection.release();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
triggerPanEnd();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function frame() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
if (isDirty) applyTransform();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyTransform() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
isDirty = false;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
// TODO: Should I allow to cancel this?
|
2020-03-08 03:41:03 +08:00
|
|
|
|
panController.applyTransform(transform);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
triggerEvent('transform');
|
|
|
|
|
frameAnimation = 0;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onKeyDown(e) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var x = 0,
|
|
|
|
|
y = 0,
|
|
|
|
|
z = 0;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
if (e.keyCode === 38) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
y = 1; // up
|
2018-10-31 03:22:05 +08:00
|
|
|
|
} else if (e.keyCode === 40) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
y = -1; // down
|
2018-10-31 03:22:05 +08:00
|
|
|
|
} else if (e.keyCode === 37) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
x = 1; // left
|
2018-10-31 03:22:05 +08:00
|
|
|
|
} else if (e.keyCode === 39) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
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 `+`)
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
e.preventDefault();
|
|
|
|
|
e.stopPropagation();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var clientRect = owner.getBoundingClientRect();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
// movement speed should be the same in both X and Y direction:
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var offset = Math.min(clientRect.width, clientRect.height);
|
|
|
|
|
var moveSpeedRatio = 0.05;
|
|
|
|
|
var dx = offset * moveSpeedRatio * x;
|
|
|
|
|
var dy = offset * moveSpeedRatio * y;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
// TODO: currently we do not animate this. It could be better to have animation
|
2020-03-08 03:41:03 +08:00
|
|
|
|
internalMoveBy(dx, dy);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (z) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var scaleMultiplier = getScaleMultiplier(z * 100);
|
|
|
|
|
var offset = transformOrigin ? getTransformOriginOffset() : midPoint();
|
|
|
|
|
publicZoomTo(offset.x, offset.y, scaleMultiplier);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
function midPoint() {
|
|
|
|
|
var ownerRect = owner.getBoundingClientRect();
|
|
|
|
|
return {
|
|
|
|
|
x: ownerRect.width / 2,
|
|
|
|
|
y: ownerRect.height / 2
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-31 03:22:05 +08:00
|
|
|
|
function onTouch(e) {
|
|
|
|
|
// let the override the touch behavior
|
|
|
|
|
beforeTouch(e);
|
|
|
|
|
|
|
|
|
|
if (e.touches.length === 1) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
return handleSingleFingerTouch(e, e.touches[0]);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
} else if (e.touches.length === 2) {
|
|
|
|
|
// handleTouchMove() will care about pinch zoom.
|
2020-03-08 03:41:03 +08:00
|
|
|
|
pinchZoomLength = getPinchZoomLength(e.touches[0], e.touches[1]);
|
|
|
|
|
multiTouch = true;
|
|
|
|
|
startTouchListenerIfNeeded();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function beforeTouch(e) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
// TODO: Need to unify this filtering names. E.g. use `beforeTouch`
|
2018-10-31 03:22:05 +08:00
|
|
|
|
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
|
2020-03-08 03:41:03 +08:00
|
|
|
|
return;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
e.stopPropagation();
|
|
|
|
|
e.preventDefault();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function beforeDoubleClick(e) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
// TODO: Need to unify this filtering names. E.g. use `beforeDoubleClick``
|
2018-10-31 03:22:05 +08:00
|
|
|
|
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
|
2020-03-08 03:41:03 +08:00
|
|
|
|
return;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
e.preventDefault();
|
|
|
|
|
e.stopPropagation();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleSingleFingerTouch(e) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var touch = e.touches[0];
|
|
|
|
|
var offset = getOffsetXY(touch);
|
|
|
|
|
lastSingleFingerOffset = offset;
|
|
|
|
|
var point = transformToScreen(offset.x, offset.y);
|
|
|
|
|
mouseX = point.x;
|
|
|
|
|
mouseY = point.y;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
smoothScroll.cancel();
|
|
|
|
|
startTouchListenerIfNeeded();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function startTouchListenerIfNeeded() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
if (touchInProgress) {
|
|
|
|
|
// no need to do anything, as we already listen to events;
|
|
|
|
|
return;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
2020-03-08 03:41:03 +08:00
|
|
|
|
|
|
|
|
|
touchInProgress = true;
|
|
|
|
|
document.addEventListener('touchmove', handleTouchMove);
|
|
|
|
|
document.addEventListener('touchend', handleTouchEnd);
|
|
|
|
|
document.addEventListener('touchcancel', handleTouchEnd);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleTouchMove(e) {
|
|
|
|
|
if (e.touches.length === 1) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
e.stopPropagation();
|
|
|
|
|
var touch = e.touches[0];
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var offset = getOffsetXY(touch);
|
|
|
|
|
var point = transformToScreen(offset.x, offset.y);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var dx = point.x - mouseX;
|
|
|
|
|
var dy = point.y - mouseY;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
if (dx !== 0 && dy !== 0) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
triggerPanStart();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
2020-03-08 03:41:03 +08:00
|
|
|
|
mouseX = point.x;
|
|
|
|
|
mouseY = point.y;
|
|
|
|
|
internalMoveBy(dx, dy);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
} else if (e.touches.length === 2) {
|
|
|
|
|
// it's a zoom, let's find direction
|
2020-03-08 03:41:03 +08:00
|
|
|
|
multiTouch = true;
|
|
|
|
|
var t1 = e.touches[0];
|
|
|
|
|
var t2 = e.touches[1];
|
|
|
|
|
var currentPinchLength = getPinchZoomLength(t1, t2);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2019-05-16 03:50:27 +08:00
|
|
|
|
// since the zoom speed is always based on distance from 1, we need to apply
|
|
|
|
|
// pinch speed only on that distance from 1:
|
2020-03-08 03:41:03 +08:00
|
|
|
|
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;
|
|
|
|
|
}
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
publicZoomTo(mouseX, mouseY, scaleMultiplier);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
pinchZoomLength = currentPinchLength;
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
e.preventDefault();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleTouchEnd(e) {
|
|
|
|
|
if (e.touches.length > 0) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var offset = getOffsetXY(e.touches[0]);
|
|
|
|
|
var point = transformToScreen(offset.x, offset.y);
|
|
|
|
|
mouseX = point.x;
|
|
|
|
|
mouseY = point.y;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
} else {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var now = new Date();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
if (now - lastTouchEndTime < doubleTapSpeedInMS) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
if (transformOrigin) {
|
|
|
|
|
var offset = getTransformOriginOffset();
|
|
|
|
|
smoothZoom(offset.x, offset.y, zoomDoubleClickSpeed);
|
|
|
|
|
} else {
|
|
|
|
|
smoothZoom(lastSingleFingerOffset.x, lastSingleFingerOffset.y, zoomDoubleClickSpeed);
|
|
|
|
|
}
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
lastTouchEndTime = now;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
touchInProgress = false;
|
|
|
|
|
triggerPanEnd();
|
|
|
|
|
releaseTouches();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getPinchZoomLength(finger1, finger2) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var dx = finger1.clientX - finger2.clientX;
|
|
|
|
|
var dy = finger1.clientY - finger2.clientY;
|
|
|
|
|
return Math.sqrt(dx * dx + dy * dy);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onDoubleClick(e) {
|
|
|
|
|
beforeDoubleClick(e);
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var offset = getOffsetXY(e);
|
|
|
|
|
if (transformOrigin) {
|
|
|
|
|
// TODO: looks like this is duplicated in the file.
|
|
|
|
|
// Need to refactor
|
|
|
|
|
offset = getTransformOriginOffset();
|
|
|
|
|
}
|
|
|
|
|
smoothZoom(offset.x, offset.y, zoomDoubleClickSpeed);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onMouseDown(e) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
// if client does not want to handle this event - just ignore the call
|
|
|
|
|
if (beforeMouseDown(e)) return;
|
|
|
|
|
|
2018-10-31 03:22:05 +08:00
|
|
|
|
if (touchInProgress) {
|
|
|
|
|
// modern browsers will fire mousedown for touch events too
|
|
|
|
|
// we do not want this: touch is handled separately.
|
2020-03-08 03:41:03 +08:00
|
|
|
|
e.stopPropagation();
|
|
|
|
|
return false;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
// for IE, left click == 1
|
|
|
|
|
// for Firefox, left click == 0
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var isLeftButton =
|
|
|
|
|
(e.button === 1 && window.event !== null) || e.button === 0;
|
|
|
|
|
if (!isLeftButton) return;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
smoothScroll.cancel();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
var offset = getOffsetXY(e);
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var point = transformToScreen(offset.x, offset.y);
|
|
|
|
|
mouseX = point.x;
|
|
|
|
|
mouseY = point.y;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
// We need to listen on document itself, since mouse can go outside of the
|
|
|
|
|
// window, and we will loose it
|
2020-03-08 03:41:03 +08:00
|
|
|
|
document.addEventListener('mousemove', onMouseMove);
|
|
|
|
|
document.addEventListener('mouseup', onMouseUp);
|
|
|
|
|
textSelection.capture(e.target || e.srcElement);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
return false;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onMouseMove(e) {
|
|
|
|
|
// no need to worry about mouse events when touch is happening
|
2020-03-08 03:41:03 +08:00
|
|
|
|
if (touchInProgress) return;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
triggerPanStart();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
var offset = getOffsetXY(e);
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var point = transformToScreen(offset.x, offset.y);
|
|
|
|
|
var dx = point.x - mouseX;
|
|
|
|
|
var dy = point.y - mouseY;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
mouseX = point.x;
|
|
|
|
|
mouseY = point.y;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
internalMoveBy(dx, dy);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onMouseUp() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
textSelection.release();
|
|
|
|
|
triggerPanEnd();
|
|
|
|
|
releaseDocumentMouse();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function releaseDocumentMouse() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
document.removeEventListener('mousemove', onMouseMove);
|
|
|
|
|
document.removeEventListener('mouseup', onMouseUp);
|
|
|
|
|
panstartFired = false;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function releaseTouches() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
document.removeEventListener('touchmove', handleTouchMove);
|
|
|
|
|
document.removeEventListener('touchend', handleTouchEnd);
|
|
|
|
|
document.removeEventListener('touchcancel', handleTouchEnd);
|
|
|
|
|
panstartFired = false;
|
|
|
|
|
multiTouch = false;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onMouseWheel(e) {
|
|
|
|
|
// if client does not want to handle this event - just ignore the call
|
2020-03-08 03:41:03 +08:00
|
|
|
|
if (beforeWheel(e)) return;
|
|
|
|
|
|
|
|
|
|
smoothScroll.cancel();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var delta = e.deltaY;
|
|
|
|
|
if (e.deltaMode > 0) delta *= 100;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var scaleMultiplier = getScaleMultiplier(delta);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
if (scaleMultiplier !== 1) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var offset = transformOrigin
|
|
|
|
|
? getTransformOriginOffset()
|
|
|
|
|
: getOffsetXY(e);
|
|
|
|
|
publicZoomTo(offset.x, offset.y, scaleMultiplier);
|
|
|
|
|
e.preventDefault();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
2020-03-08 03:41:03 +08:00
|
|
|
|
offsetX = e.clientX - ownerRect.left;
|
|
|
|
|
offsetY = e.clientY - ownerRect.top;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
return { x: offsetX, y: offsetY };
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function smoothZoom(clientX, clientY, scaleMultiplier) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var fromValue = transform.scale;
|
|
|
|
|
var from = { scale: fromValue };
|
|
|
|
|
var to = { scale: scaleMultiplier * fromValue };
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
smoothScroll.cancel();
|
|
|
|
|
cancelZoomAnimation();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
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
|
|
|
|
|
};
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function publicZoomTo(clientX, clientY, scaleMultiplier) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
smoothScroll.cancel();
|
|
|
|
|
cancelZoomAnimation();
|
|
|
|
|
return zoomByRatio(clientX, clientY, scaleMultiplier);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function cancelZoomAnimation() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
if (zoomToAnimation) {
|
|
|
|
|
zoomToAnimation.cancel();
|
|
|
|
|
zoomToAnimation = null;
|
|
|
|
|
}
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getScaleMultiplier(delta) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var sign = Math.sign(delta);
|
|
|
|
|
var deltaAdjustedSpeed = Math.min(0.25, Math.abs(speed * delta / 128));
|
|
|
|
|
return 1 - sign * deltaAdjustedSpeed;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function triggerPanStart() {
|
|
|
|
|
if (!panstartFired) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
triggerEvent('panstart');
|
|
|
|
|
panstartFired = true;
|
|
|
|
|
smoothScroll.start();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function triggerPanEnd() {
|
|
|
|
|
if (panstartFired) {
|
2019-05-16 03:50:27 +08:00
|
|
|
|
// we should never run smooth scrolling if it was multiTouch (pinch zoom animation):
|
2020-03-08 03:41:03 +08:00
|
|
|
|
if (!multiTouch) smoothScroll.stop();
|
|
|
|
|
triggerEvent('panend');
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
function triggerZoomEnd() {
|
|
|
|
|
triggerEvent('zoomend');
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-31 03:22:05 +08:00
|
|
|
|
function triggerEvent(name) {
|
|
|
|
|
api.fire(name, api);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
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')
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-31 03:22:05 +08:00
|
|
|
|
function noop() { }
|
|
|
|
|
|
|
|
|
|
function validateBounds(bounds) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var boundsType = typeof bounds;
|
|
|
|
|
if (boundsType === 'undefined' || boundsType === 'boolean') return; // this is okay
|
2018-10-31 03:22:05 +08:00
|
|
|
|
// otherwise need to be more thorough:
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var validBounds =
|
|
|
|
|
isNumber(bounds.left) &&
|
|
|
|
|
isNumber(bounds.top) &&
|
|
|
|
|
isNumber(bounds.bottom) &&
|
|
|
|
|
isNumber(bounds.right);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
if (!validBounds)
|
|
|
|
|
throw new Error(
|
|
|
|
|
'Bounds object is not valid. It can be: ' +
|
|
|
|
|
'undefined, boolean (true|false) or an object {left, top, right, bottom}'
|
|
|
|
|
);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isNumber(x) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
return Number.isFinite(x);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
2018-10-18 17:46:07 +08:00
|
|
|
|
|
|
|
|
|
// IE 11 does not support isNaN:
|
2018-10-31 03:22:05 +08:00
|
|
|
|
function isNaN(value) {
|
|
|
|
|
if (Number.isNaN) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
return Number.isNaN(value);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
return value !== value;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function rigidScroll() {
|
|
|
|
|
return {
|
|
|
|
|
start: noop,
|
|
|
|
|
stop: noop,
|
|
|
|
|
cancel: noop
|
2020-03-08 03:41:03 +08:00
|
|
|
|
};
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function autoRun() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
if (typeof document === 'undefined') return;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
var scripts = document.getElementsByTagName('script');
|
|
|
|
|
if (!scripts) return;
|
|
|
|
|
var panzoomScript;
|
|
|
|
|
|
2019-05-16 03:50:27 +08:00
|
|
|
|
for (var i = 0; i < scripts.length; ++i) {
|
|
|
|
|
var x = scripts[i];
|
2018-10-31 03:22:05 +08:00
|
|
|
|
if (x.src && x.src.match(/\bpanzoom(\.min)?\.js/)) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
panzoomScript = x;
|
2019-05-16 03:50:27 +08:00
|
|
|
|
break;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
2019-05-16 03:50:27 +08:00
|
|
|
|
}
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
if (!panzoomScript) return;
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var query = panzoomScript.getAttribute('query');
|
2018-10-31 03:22:05 +08:00
|
|
|
|
if (!query) return;
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var globalName = panzoomScript.getAttribute('name') || 'pz';
|
|
|
|
|
var started = Date.now();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
tryAttach();
|
|
|
|
|
|
|
|
|
|
function tryAttach() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var el = document.querySelector(query);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
if (!el) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var now = Date.now();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
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
|
2020-03-08 03:41:03 +08:00
|
|
|
|
console.error('Cannot find the panzoom element', globalName);
|
|
|
|
|
return;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var options = collectOptions(panzoomScript);
|
|
|
|
|
console.log(options);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
window[globalName] = createPanZoom(el, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function collectOptions(script) {
|
|
|
|
|
var attrs = script.attributes;
|
|
|
|
|
var options = {};
|
2020-03-08 03:41:03 +08:00
|
|
|
|
for (var i = 0; i < attrs.length; ++i) {
|
2018-10-31 03:22:05 +08:00
|
|
|
|
var attr = attrs[i];
|
|
|
|
|
var nameValue = getPanzoomAttributeNameValue(attr);
|
|
|
|
|
if (nameValue) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
options[nameValue.name] = nameValue.value;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return options;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getPanzoomAttributeNameValue(attr) {
|
|
|
|
|
if (!attr.name) return;
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var isPanZoomAttribute =
|
|
|
|
|
attr.name[0] === 'p' && attr.name[1] === 'z' && attr.name[2] === '-';
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
if (!isPanZoomAttribute) return;
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var name = attr.name.substr(3);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
var value = JSON.parse(attr.value);
|
2020-03-08 03:41:03 +08:00
|
|
|
|
return { name: name, value: value };
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
autoRun();
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
},{"./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){
|
2018-10-31 03:22:05 +08:00
|
|
|
|
module.exports = makeDomController
|
|
|
|
|
|
2019-05-16 03:50:27 +08:00
|
|
|
|
function makeDomController(domElement, options) {
|
2018-10-31 03:22:05 +08:00
|
|
|
|
var elementValid = (domElement instanceof HTMLElement)
|
|
|
|
|
if (!elementValid) {
|
|
|
|
|
throw new Error('svg element is required for svg.panzoom to work')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var owner = domElement.parentElement
|
|
|
|
|
if (!owner) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
'Do not apply panzoom to the detached DOM element. '
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
domElement.scrollTop = 0;
|
2019-05-16 03:50:27 +08:00
|
|
|
|
|
|
|
|
|
if (!options.disableKeyboardInteraction) {
|
|
|
|
|
owner.setAttribute('tabindex', 0);
|
|
|
|
|
}
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
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 + ')'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
},{}],4:[function(require,module,exports){
|
|
|
|
|
(function (global){
|
2018-10-31 03:22:05 +08:00
|
|
|
|
/**
|
|
|
|
|
* 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.
|
2020-03-08 03:41:03 +08:00
|
|
|
|
settings = {};
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
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();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var lastPoint;
|
|
|
|
|
var timestamp;
|
|
|
|
|
var timeConstant = 342;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var ticker;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
var vx, targetX, ax;
|
|
|
|
|
var vy, targetY, ay;
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var raf;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
start: start,
|
|
|
|
|
stop: stop,
|
|
|
|
|
cancel: dispose
|
2020-03-08 03:41:03 +08:00
|
|
|
|
};
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
function dispose() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
cancelAnimationFrame(ticker);
|
|
|
|
|
cancelAnimationFrame(raf);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function start() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
lastPoint = getPoint();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
ax = ay = vx = vy = 0;
|
|
|
|
|
timestamp = new Date();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
cancelAnimationFrame(ticker);
|
|
|
|
|
cancelAnimationFrame(raf);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
// we start polling the point position to accumulate velocity
|
|
|
|
|
// Once we stop(), we will use accumulated velocity to keep scrolling
|
|
|
|
|
// an object.
|
2020-03-08 03:41:03 +08:00
|
|
|
|
ticker = requestAnimationFrame(track);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function track() {
|
|
|
|
|
var now = Date.now();
|
|
|
|
|
var elapsed = now - timestamp;
|
|
|
|
|
timestamp = now;
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var currentPoint = getPoint();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var dx = currentPoint.x - lastPoint.x;
|
|
|
|
|
var dy = currentPoint.y - lastPoint.y;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
lastPoint = currentPoint;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var dt = 1000 / (1 + elapsed);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
// moving average
|
2020-03-08 03:41:03 +08:00
|
|
|
|
vx = 0.8 * dx * dt + 0.2 * vx;
|
|
|
|
|
vy = 0.8 * dy * dt + 0.2 * vy;
|
|
|
|
|
|
|
|
|
|
ticker = requestAnimationFrame(track);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function stop() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
cancelAnimationFrame(ticker);
|
|
|
|
|
cancelAnimationFrame(raf);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var currentPoint = getPoint();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
targetX = currentPoint.x;
|
|
|
|
|
targetY = currentPoint.y;
|
|
|
|
|
timestamp = Date.now();
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
if (vx < -minVelocity || vx > minVelocity) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
ax = amplitude * vx;
|
|
|
|
|
targetX += ax;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (vy < -minVelocity || vy > minVelocity) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
ay = amplitude * vy;
|
|
|
|
|
targetY += ay;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
raf = requestAnimationFrame(autoScroll);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function autoScroll() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var elapsed = Date.now() - timestamp;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var moving = false;
|
|
|
|
|
var dx = 0;
|
|
|
|
|
var dy = 0;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
if (ax) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
dx = -ax * Math.exp(-elapsed / timeConstant);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
if (dx > 0.5 || dx < -0.5) moving = true;
|
|
|
|
|
else dx = ax = 0;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ay) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
dy = -ay * Math.exp(-elapsed / timeConstant);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
if (dy > 0.5 || dy < -0.5) moving = true;
|
|
|
|
|
else dy = ay = 0;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (moving) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
scroll(targetX + dx, targetY + dy);
|
|
|
|
|
raf = requestAnimationFrame(autoScroll);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-03-08 03:41:03 +08:00
|
|
|
|
}
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
function getCancelAnimationFrame() {
|
|
|
|
|
if (typeof global.cancelAnimationFrame === 'function') return global.cancelAnimationFrame;
|
|
|
|
|
|
|
|
|
|
return clearTimeout;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
function getRequestAnimationFrame() {
|
|
|
|
|
if (typeof global.requestAnimationFrame === 'function') return global.requestAnimationFrame;
|
|
|
|
|
|
|
|
|
|
return function (handler) {
|
|
|
|
|
return setTimeout(handler, 16);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
|
|
|
|
},{}],5:[function(require,module,exports){
|
2018-10-31 03:22:05 +08:00
|
|
|
|
module.exports = makeSvgController
|
|
|
|
|
|
2019-05-16 03:50:27 +08:00
|
|
|
|
function makeSvgController(svgElement, options) {
|
2018-10-31 03:22:05 +08:00
|
|
|
|
var elementValid = (svgElement instanceof SVGElement)
|
|
|
|
|
if (!elementValid) {
|
|
|
|
|
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 <svg> element. ' +
|
|
|
|
|
'Use its child instead (e.g. <g></g>). ' +
|
|
|
|
|
'As of March 2016 only FireFox supported transform on the root element')
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-16 03:50:27 +08:00
|
|
|
|
if (!options.disableKeyboardInteraction) {
|
|
|
|
|
owner.setAttribute('tabindex', 0);
|
|
|
|
|
}
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
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() {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
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;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function initTransform(transform) {
|
2020-03-08 03:41:03 +08:00
|
|
|
|
var screenCTM = svgElement.getCTM()
|
2018-10-31 03:22:05 +08:00
|
|
|
|
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 + ')')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},{}],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')
|
2018-10-18 17:46:07 +08:00
|
|
|
|
|
|
|
|
|
// Predefined set of animations. Similar to CSS easing functions
|
2018-10-31 03:22:05 +08:00
|
|
|
|
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;
|
2019-08-29 05:08:05 +08:00
|
|
|
|
frontBuffer = t;
|
2018-10-31 03:22:05 +08:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
*/
|
2018-10-18 17:46:07 +08:00
|
|
|
|
|
|
|
|
|
// These values are established by empiricism with tests (tradeoff: performance VS precision)
|
2018-10-31 03:22:05 +08:00
|
|
|
|
var NEWTON_ITERATIONS = 4;
|
|
|
|
|
var NEWTON_MIN_SLOPE = 0.001;
|
|
|
|
|
var SUBDIVISION_PRECISION = 0.0000001;
|
|
|
|
|
var SUBDIVISION_MAX_ITERATIONS = 10;
|
2018-10-18 17:46:07 +08:00
|
|
|
|
|
2018-10-31 03:22:05 +08:00
|
|
|
|
var kSplineTableSize = 11;
|
|
|
|
|
var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);
|
2018-10-18 17:46:07 +08:00
|
|
|
|
|
2018-10-31 03:22:05 +08:00
|
|
|
|
var float32ArraySupported = typeof Float32Array === 'function';
|
2018-10-18 17:46:07 +08:00
|
|
|
|
|
2018-10-31 03:22:05 +08:00
|
|
|
|
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; }
|
2018-10-18 17:46:07 +08:00
|
|
|
|
|
|
|
|
|
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
|
2018-10-31 03:22:05 +08:00
|
|
|
|
function calcBezier (aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; }
|
2018-10-18 17:46:07 +08:00
|
|
|
|
|
|
|
|
|
// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
|
2018-10-31 03:22:05 +08:00
|
|
|
|
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(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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-10-25 21:45:14 +08:00
|
|
|
|
}
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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){
|
|
|
|
|
/**
|
2020-03-08 03:41:03 +08:00
|
|
|
|
* This module used to unify mouse wheel behavior between different browsers in 2014
|
|
|
|
|
* Now it's just a wrapper around addEventListener('wheel');
|
2018-10-31 03:22:05 +08:00
|
|
|
|
*
|
|
|
|
|
* Usage:
|
|
|
|
|
* var addWheelListener = require('wheel').addWheelListener;
|
|
|
|
|
* var removeWheelListener = require('wheel').removeWheelListener;
|
|
|
|
|
* addWheelListener(domElement, function (e) {
|
|
|
|
|
* // mouse wheel event
|
|
|
|
|
* });
|
|
|
|
|
* removeWheelListener(domElement, function);
|
|
|
|
|
*/
|
2018-10-18 17:46:07 +08:00
|
|
|
|
|
2018-10-31 03:22:05 +08:00
|
|
|
|
module.exports = addWheelListener;
|
2018-10-18 17:46:07 +08:00
|
|
|
|
|
|
|
|
|
// But also expose "advanced" api with unsubscribe:
|
2018-10-31 03:22:05 +08:00
|
|
|
|
module.exports.addWheelListener = addWheelListener;
|
|
|
|
|
module.exports.removeWheelListener = removeWheelListener;
|
|
|
|
|
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
function addWheelListener(element, listener, useCapture) {
|
|
|
|
|
element.addEventListener('wheel', listener, useCapture);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 03:41:03 +08:00
|
|
|
|
function removeWheelListener( element, listener, useCapture ) {
|
|
|
|
|
element.removeEventListener('wheel', listener, useCapture);
|
2018-10-31 03:22:05 +08:00
|
|
|
|
}
|
|
|
|
|
},{}]},{},[1])(1)
|
|
|
|
|
});
|