panzoom.js 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753
  1. (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){
  2. 'use strict';
  3. /* globals SVGElement */
  4. /**
  5. * Allows to drag and zoom svg elements
  6. */
  7. var wheel = require('wheel');
  8. var animate = require('amator');
  9. var eventify = require('ngraph.events');
  10. var kinetic = require('./lib/kinetic.js');
  11. var createTextSelectionInterceptor = require('./lib/createTextSelectionInterceptor.js');
  12. var domTextSelectionInterceptor = createTextSelectionInterceptor();
  13. var fakeTextSelectorInterceptor = createTextSelectionInterceptor(true);
  14. var Transform = require('./lib/transform.js');
  15. var makeSvgController = require('./lib/svgController.js');
  16. var makeDomController = require('./lib/domController.js');
  17. var defaultZoomSpeed = 1;
  18. var defaultDoubleTapZoomSpeed = 1.75;
  19. var doubleTapSpeedInMS = 300;
  20. module.exports = createPanZoom;
  21. /**
  22. * Creates a new instance of panzoom, so that an object can be panned and zoomed
  23. *
  24. * @param {DOMElement} domElement where panzoom should be attached.
  25. * @param {Object} options that configure behavior.
  26. */
  27. function createPanZoom(domElement, options) {
  28. options = options || {};
  29. var panController = options.controller;
  30. if (!panController) {
  31. if (domElement instanceof SVGElement) {
  32. panController = makeSvgController(domElement, options);
  33. }
  34. if (domElement instanceof HTMLElement) {
  35. panController = makeDomController(domElement, options);
  36. }
  37. }
  38. if (!panController) {
  39. throw new Error(
  40. 'Cannot create panzoom for the current type of dom element'
  41. );
  42. }
  43. var owner = panController.getOwner();
  44. // just to avoid GC pressure, every time we do intermediate transform
  45. // we return this object. For internal use only. Never give it back to the consumer of this library
  46. var storedCTMResult = { x: 0, y: 0 };
  47. var isDirty = false;
  48. var transform = new Transform();
  49. if (panController.initTransform) {
  50. panController.initTransform(transform);
  51. }
  52. var filterKey = typeof options.filterKey === 'function' ? options.filterKey : noop;
  53. // TODO: likely need to unite pinchSpeed with zoomSpeed
  54. var pinchSpeed = typeof options.pinchSpeed === 'number' ? options.pinchSpeed : 1;
  55. var bounds = options.bounds;
  56. var maxZoom = typeof options.maxZoom === 'number' ? options.maxZoom : Number.POSITIVE_INFINITY;
  57. var minZoom = typeof options.minZoom === 'number' ? options.minZoom : 0;
  58. var boundsPadding = typeof options.boundsPadding === 'number' ? options.boundsPadding : 0.05;
  59. var zoomDoubleClickSpeed = typeof options.zoomDoubleClickSpeed === 'number' ? options.zoomDoubleClickSpeed : defaultDoubleTapZoomSpeed;
  60. var beforeWheel = options.beforeWheel || noop;
  61. var beforeMouseDown = options.beforeMouseDown || noop;
  62. var speed = typeof options.zoomSpeed === 'number' ? options.zoomSpeed : defaultZoomSpeed;
  63. var transformOrigin = parseTransformOrigin(options.transformOrigin);
  64. var textSelection = options.enableTextSelection ? fakeTextSelectorInterceptor : domTextSelectionInterceptor;
  65. validateBounds(bounds);
  66. if (options.autocenter) {
  67. autocenter();
  68. }
  69. var frameAnimation;
  70. var lastTouchEndTime = 0;
  71. var lastSingleFingerOffset;
  72. var touchInProgress = false;
  73. // We only need to fire panstart when actual move happens
  74. var panstartFired = false;
  75. // cache mouse coordinates here
  76. var mouseX;
  77. var mouseY;
  78. var pinchZoomLength;
  79. var smoothScroll;
  80. if ('smoothScroll' in options && !options.smoothScroll) {
  81. // If user explicitly asked us not to use smooth scrolling, we obey
  82. smoothScroll = rigidScroll();
  83. } else {
  84. // otherwise we use forward smoothScroll settings to kinetic API
  85. // which makes scroll smoothing.
  86. smoothScroll = kinetic(getPoint, scroll, options.smoothScroll);
  87. }
  88. var moveByAnimation;
  89. var zoomToAnimation;
  90. var multiTouch;
  91. var paused = false;
  92. listenForEvents();
  93. var api = {
  94. dispose: dispose,
  95. moveBy: internalMoveBy,
  96. moveTo: moveTo,
  97. centerOn: centerOn,
  98. zoomTo: publicZoomTo,
  99. zoomAbs: zoomAbs,
  100. smoothZoom: smoothZoom,
  101. smoothZoomAbs: smoothZoomAbs,
  102. showRectangle: showRectangle,
  103. pause: pause,
  104. resume: resume,
  105. isPaused: isPaused,
  106. getTransform: getTransformModel,
  107. getMinZoom: getMinZoom,
  108. setMinZoom: setMinZoom,
  109. getMaxZoom: getMaxZoom,
  110. setMaxZoom: setMaxZoom,
  111. getTransformOrigin: getTransformOrigin,
  112. setTransformOrigin: setTransformOrigin,
  113. getZoomSpeed: getZoomSpeed,
  114. setZoomSpeed: setZoomSpeed,
  115. getOwner: () => owner
  116. };
  117. eventify(api);
  118. return api;
  119. function pause() {
  120. releaseEvents();
  121. paused = true;
  122. }
  123. function resume() {
  124. if (paused) {
  125. listenForEvents();
  126. paused = false;
  127. }
  128. }
  129. function isPaused() {
  130. return paused;
  131. }
  132. function showRectangle(rect) {
  133. // TODO: this duplicates autocenter. I think autocenter should go.
  134. var clientRect = owner.getBoundingClientRect();
  135. var size = transformToScreen(clientRect.width, clientRect.height);
  136. var rectWidth = rect.right - rect.left;
  137. var rectHeight = rect.bottom - rect.top;
  138. if (!Number.isFinite(rectWidth) || !Number.isFinite(rectHeight)) {
  139. throw new Error('Invalid rectangle');
  140. }
  141. var dw = size.x / rectWidth;
  142. var dh = size.y / rectHeight;
  143. var scale = Math.min(dw, dh);
  144. transform.x = -(rect.left + rectWidth / 2) * scale + size.x / 2;
  145. transform.y = -(rect.top + rectHeight / 2) * scale + size.y / 2;
  146. transform.scale = scale;
  147. }
  148. function transformToScreen(x, y) {
  149. if (panController.getScreenCTM) {
  150. var parentCTM = panController.getScreenCTM();
  151. var parentScaleX = parentCTM.a;
  152. var parentScaleY = parentCTM.d;
  153. var parentOffsetX = parentCTM.e;
  154. var parentOffsetY = parentCTM.f;
  155. storedCTMResult.x = x * parentScaleX - parentOffsetX;
  156. storedCTMResult.y = y * parentScaleY - parentOffsetY;
  157. } else {
  158. storedCTMResult.x = x;
  159. storedCTMResult.y = y;
  160. }
  161. return storedCTMResult;
  162. }
  163. function autocenter() {
  164. var w; // width of the parent
  165. var h; // height of the parent
  166. var left = 0;
  167. var top = 0;
  168. var sceneBoundingBox = getBoundingBox();
  169. if (sceneBoundingBox) {
  170. // If we have bounding box - use it.
  171. left = sceneBoundingBox.left;
  172. top = sceneBoundingBox.top;
  173. w = sceneBoundingBox.right - sceneBoundingBox.left;
  174. h = sceneBoundingBox.bottom - sceneBoundingBox.top;
  175. } else {
  176. // otherwise just use whatever space we have
  177. var ownerRect = owner.getBoundingClientRect();
  178. w = ownerRect.width;
  179. h = ownerRect.height;
  180. }
  181. var bbox = panController.getBBox();
  182. if (bbox.width === 0 || bbox.height === 0) {
  183. // we probably do not have any elements in the SVG
  184. // just bail out;
  185. return;
  186. }
  187. var dh = h / bbox.height;
  188. var dw = w / bbox.width;
  189. var scale = Math.min(dw, dh);
  190. transform.x = -(bbox.left + bbox.width / 2) * scale + w / 2 + left;
  191. transform.y = -(bbox.top + bbox.height / 2) * scale + h / 2 + top;
  192. transform.scale = scale;
  193. }
  194. function getTransformModel() {
  195. // TODO: should this be read only?
  196. return transform;
  197. }
  198. function getMinZoom() {
  199. return minZoom;
  200. }
  201. function setMinZoom(newMinZoom) {
  202. minZoom = newMinZoom;
  203. }
  204. function getMaxZoom() {
  205. return maxZoom;
  206. }
  207. function setMaxZoom(newMaxZoom) {
  208. maxZoom = newMaxZoom;
  209. }
  210. function getTransformOrigin() {
  211. return transformOrigin;
  212. }
  213. function setTransformOrigin(newTransformOrigin) {
  214. transformOrigin = parseTransformOrigin(newTransformOrigin);
  215. }
  216. function getZoomSpeed() {
  217. return speed;
  218. }
  219. function setZoomSpeed(newSpeed) {
  220. if (!Number.isFinite(newSpeed)) {
  221. throw new Error('Zoom speed should be a number');
  222. }
  223. speed = newSpeed;
  224. }
  225. function getPoint() {
  226. return {
  227. x: transform.x,
  228. y: transform.y
  229. };
  230. }
  231. function moveTo(x, y) {
  232. transform.x = x;
  233. transform.y = y;
  234. keepTransformInsideBounds();
  235. triggerEvent('pan');
  236. makeDirty();
  237. }
  238. function moveBy(dx, dy) {
  239. moveTo(transform.x + dx, transform.y + dy);
  240. }
  241. function keepTransformInsideBounds() {
  242. var boundingBox = getBoundingBox();
  243. if (!boundingBox) return;
  244. var adjusted = false;
  245. var clientRect = getClientRect();
  246. var diff = boundingBox.left - clientRect.right;
  247. if (diff > 0) {
  248. transform.x += diff;
  249. adjusted = true;
  250. }
  251. // check the other side:
  252. diff = boundingBox.right - clientRect.left;
  253. if (diff < 0) {
  254. transform.x += diff;
  255. adjusted = true;
  256. }
  257. // y axis:
  258. diff = boundingBox.top - clientRect.bottom;
  259. if (diff > 0) {
  260. // we adjust transform, so that it matches exactly our bounding box:
  261. // transform.y = boundingBox.top - (boundingBox.height + boundingBox.y) * transform.scale =>
  262. // transform.y = boundingBox.top - (clientRect.bottom - transform.y) =>
  263. // transform.y = diff + transform.y =>
  264. transform.y += diff;
  265. adjusted = true;
  266. }
  267. diff = boundingBox.bottom - clientRect.top;
  268. if (diff < 0) {
  269. transform.y += diff;
  270. adjusted = true;
  271. }
  272. return adjusted;
  273. }
  274. /**
  275. * Returns bounding box that should be used to restrict scene movement.
  276. */
  277. function getBoundingBox() {
  278. if (!bounds) return; // client does not want to restrict movement
  279. if (typeof bounds === 'boolean') {
  280. // for boolean type we use parent container bounds
  281. var ownerRect = owner.getBoundingClientRect();
  282. var sceneWidth = ownerRect.width;
  283. var sceneHeight = ownerRect.height;
  284. return {
  285. left: sceneWidth * boundsPadding,
  286. top: sceneHeight * boundsPadding,
  287. right: sceneWidth * (1 - boundsPadding),
  288. bottom: sceneHeight * (1 - boundsPadding)
  289. };
  290. }
  291. return bounds;
  292. }
  293. function getClientRect() {
  294. var bbox = panController.getBBox();
  295. var leftTop = client(bbox.left, bbox.top);
  296. return {
  297. left: leftTop.x,
  298. top: leftTop.y,
  299. right: bbox.width * transform.scale + leftTop.x,
  300. bottom: bbox.height * transform.scale + leftTop.y
  301. };
  302. }
  303. function client(x, y) {
  304. return {
  305. x: x * transform.scale + transform.x,
  306. y: y * transform.scale + transform.y
  307. };
  308. }
  309. function makeDirty() {
  310. isDirty = true;
  311. frameAnimation = window.requestAnimationFrame(frame);
  312. }
  313. function zoomByRatio(clientX, clientY, ratio) {
  314. if (isNaN(clientX) || isNaN(clientY) || isNaN(ratio)) {
  315. throw new Error('zoom requires valid numbers');
  316. }
  317. var newScale = transform.scale * ratio;
  318. if (newScale < minZoom) {
  319. if (transform.scale === minZoom) return;
  320. ratio = minZoom / transform.scale;
  321. }
  322. if (newScale > maxZoom) {
  323. if (transform.scale === maxZoom) return;
  324. ratio = maxZoom / transform.scale;
  325. }
  326. var size = transformToScreen(clientX, clientY);
  327. transform.x = size.x - ratio * (size.x - transform.x);
  328. transform.y = size.y - ratio * (size.y - transform.y);
  329. // TODO: https://github.com/anvaka/panzoom/issues/112
  330. if (bounds && boundsPadding === 1 && minZoom === 1) {
  331. transform.scale *= ratio;
  332. keepTransformInsideBounds();
  333. } else {
  334. var transformAdjusted = keepTransformInsideBounds();
  335. if (!transformAdjusted) transform.scale *= ratio;
  336. }
  337. triggerEvent('zoom');
  338. makeDirty();
  339. }
  340. function zoomAbs(clientX, clientY, zoomLevel) {
  341. var ratio = zoomLevel / transform.scale;
  342. zoomByRatio(clientX, clientY, ratio);
  343. }
  344. function centerOn(ui) {
  345. var parent = ui.ownerSVGElement;
  346. if (!parent)
  347. throw new Error('ui element is required to be within the scene');
  348. // TODO: should i use controller's screen CTM?
  349. var clientRect = ui.getBoundingClientRect();
  350. var cx = clientRect.left + clientRect.width / 2;
  351. var cy = clientRect.top + clientRect.height / 2;
  352. var container = parent.getBoundingClientRect();
  353. var dx = container.width / 2 - cx;
  354. var dy = container.height / 2 - cy;
  355. internalMoveBy(dx, dy, true);
  356. }
  357. function internalMoveBy(dx, dy, smooth) {
  358. if (!smooth) {
  359. return moveBy(dx, dy);
  360. }
  361. if (moveByAnimation) moveByAnimation.cancel();
  362. var from = { x: 0, y: 0 };
  363. var to = { x: dx, y: dy };
  364. var lastX = 0;
  365. var lastY = 0;
  366. moveByAnimation = animate(from, to, {
  367. step: function (v) {
  368. moveBy(v.x - lastX, v.y - lastY);
  369. lastX = v.x;
  370. lastY = v.y;
  371. }
  372. });
  373. }
  374. function scroll(x, y) {
  375. cancelZoomAnimation();
  376. moveTo(x, y);
  377. }
  378. function dispose() {
  379. releaseEvents();
  380. }
  381. function listenForEvents() {
  382. owner.addEventListener('mousedown', onMouseDown, { passive: false });
  383. owner.addEventListener('dblclick', onDoubleClick, { passive: false });
  384. owner.addEventListener('touchstart', onTouch, { passive: false });
  385. owner.addEventListener('keydown', onKeyDown, { passive: false });
  386. // Need to listen on the owner container, so that we are not limited
  387. // by the size of the scrollable domElement
  388. wheel.addWheelListener(owner, onMouseWheel, { passive: false });
  389. makeDirty();
  390. }
  391. function releaseEvents() {
  392. wheel.removeWheelListener(owner, onMouseWheel);
  393. owner.removeEventListener('mousedown', onMouseDown);
  394. owner.removeEventListener('keydown', onKeyDown);
  395. owner.removeEventListener('dblclick', onDoubleClick);
  396. owner.removeEventListener('touchstart', onTouch);
  397. if (frameAnimation) {
  398. window.cancelAnimationFrame(frameAnimation);
  399. frameAnimation = 0;
  400. }
  401. smoothScroll.cancel();
  402. releaseDocumentMouse();
  403. releaseTouches();
  404. textSelection.release();
  405. triggerPanEnd();
  406. }
  407. function frame() {
  408. if (isDirty) applyTransform();
  409. }
  410. function applyTransform() {
  411. isDirty = false;
  412. // TODO: Should I allow to cancel this?
  413. panController.applyTransform(transform);
  414. triggerEvent('transform');
  415. frameAnimation = 0;
  416. }
  417. function onKeyDown(e) {
  418. var x = 0,
  419. y = 0,
  420. z = 0;
  421. if (e.keyCode === 38) {
  422. y = 1; // up
  423. } else if (e.keyCode === 40) {
  424. y = -1; // down
  425. } else if (e.keyCode === 37) {
  426. x = 1; // left
  427. } else if (e.keyCode === 39) {
  428. x = -1; // right
  429. } else if (e.keyCode === 189 || e.keyCode === 109) {
  430. // DASH or SUBTRACT
  431. z = 1; // `-` - zoom out
  432. } else if (e.keyCode === 187 || e.keyCode === 107) {
  433. // EQUAL SIGN or ADD
  434. z = -1; // `=` - zoom in (equal sign on US layout is under `+`)
  435. }
  436. if (filterKey(e, x, y, z)) {
  437. // They don't want us to handle the key: https://github.com/anvaka/panzoom/issues/45
  438. return;
  439. }
  440. if (x || y) {
  441. e.preventDefault();
  442. e.stopPropagation();
  443. var clientRect = owner.getBoundingClientRect();
  444. // movement speed should be the same in both X and Y direction:
  445. var offset = Math.min(clientRect.width, clientRect.height);
  446. var moveSpeedRatio = 0.05;
  447. var dx = offset * moveSpeedRatio * x;
  448. var dy = offset * moveSpeedRatio * y;
  449. // TODO: currently we do not animate this. It could be better to have animation
  450. internalMoveBy(dx, dy);
  451. }
  452. if (z) {
  453. var scaleMultiplier = getScaleMultiplier(z * 100);
  454. var offset = transformOrigin ? getTransformOriginOffset() : midPoint();
  455. publicZoomTo(offset.x, offset.y, scaleMultiplier);
  456. }
  457. }
  458. function midPoint() {
  459. var ownerRect = owner.getBoundingClientRect();
  460. return {
  461. x: ownerRect.width / 2,
  462. y: ownerRect.height / 2
  463. };
  464. }
  465. function onTouch(e) {
  466. // let the override the touch behavior
  467. beforeTouch(e);
  468. if (e.touches.length === 1) {
  469. return handleSingleFingerTouch(e, e.touches[0]);
  470. } else if (e.touches.length === 2) {
  471. // handleTouchMove() will care about pinch zoom.
  472. pinchZoomLength = getPinchZoomLength(e.touches[0], e.touches[1]);
  473. multiTouch = true;
  474. startTouchListenerIfNeeded();
  475. }
  476. }
  477. function beforeTouch(e) {
  478. // TODO: Need to unify this filtering names. E.g. use `beforeTouch`
  479. if (options.onTouch && !options.onTouch(e)) {
  480. // if they return `false` from onTouch, we don't want to stop
  481. // events propagation. Fixes https://github.com/anvaka/panzoom/issues/12
  482. return;
  483. }
  484. e.stopPropagation();
  485. e.preventDefault();
  486. }
  487. function beforeDoubleClick(e) {
  488. // TODO: Need to unify this filtering names. E.g. use `beforeDoubleClick``
  489. if (options.onDoubleClick && !options.onDoubleClick(e)) {
  490. // if they return `false` from onTouch, we don't want to stop
  491. // events propagation. Fixes https://github.com/anvaka/panzoom/issues/46
  492. return;
  493. }
  494. e.preventDefault();
  495. e.stopPropagation();
  496. }
  497. function handleSingleFingerTouch(e) {
  498. var touch = e.touches[0];
  499. var offset = getOffsetXY(touch);
  500. lastSingleFingerOffset = offset;
  501. var point = transformToScreen(offset.x, offset.y);
  502. mouseX = point.x;
  503. mouseY = point.y;
  504. smoothScroll.cancel();
  505. startTouchListenerIfNeeded();
  506. }
  507. function startTouchListenerIfNeeded() {
  508. if (touchInProgress) {
  509. // no need to do anything, as we already listen to events;
  510. return;
  511. }
  512. touchInProgress = true;
  513. document.addEventListener('touchmove', handleTouchMove);
  514. document.addEventListener('touchend', handleTouchEnd);
  515. document.addEventListener('touchcancel', handleTouchEnd);
  516. }
  517. function handleTouchMove(e) {
  518. if (e.touches.length === 1) {
  519. e.stopPropagation();
  520. var touch = e.touches[0];
  521. var offset = getOffsetXY(touch);
  522. var point = transformToScreen(offset.x, offset.y);
  523. var dx = point.x - mouseX;
  524. var dy = point.y - mouseY;
  525. if (dx !== 0 && dy !== 0) {
  526. triggerPanStart();
  527. }
  528. mouseX = point.x;
  529. mouseY = point.y;
  530. internalMoveBy(dx, dy);
  531. } else if (e.touches.length === 2) {
  532. // it's a zoom, let's find direction
  533. multiTouch = true;
  534. var t1 = e.touches[0];
  535. var t2 = e.touches[1];
  536. var currentPinchLength = getPinchZoomLength(t1, t2);
  537. // since the zoom speed is always based on distance from 1, we need to apply
  538. // pinch speed only on that distance from 1:
  539. var scaleMultiplier =
  540. 1 + (currentPinchLength / pinchZoomLength - 1) * pinchSpeed;
  541. var firstTouchPoint = getOffsetXY(t1);
  542. var secondTouchPoint = getOffsetXY(t2);
  543. mouseX = (firstTouchPoint.x + secondTouchPoint.x) / 2;
  544. mouseY = (firstTouchPoint.y + secondTouchPoint.y) / 2;
  545. if (transformOrigin) {
  546. var offset = getTransformOriginOffset();
  547. mouseX = offset.x;
  548. mouseY = offset.y;
  549. }
  550. publicZoomTo(mouseX, mouseY, scaleMultiplier);
  551. pinchZoomLength = currentPinchLength;
  552. e.stopPropagation();
  553. e.preventDefault();
  554. }
  555. }
  556. function handleTouchEnd(e) {
  557. if (e.touches.length > 0) {
  558. var offset = getOffsetXY(e.touches[0]);
  559. var point = transformToScreen(offset.x, offset.y);
  560. mouseX = point.x;
  561. mouseY = point.y;
  562. } else {
  563. var now = new Date();
  564. if (now - lastTouchEndTime < doubleTapSpeedInMS) {
  565. if (transformOrigin) {
  566. var offset = getTransformOriginOffset();
  567. smoothZoom(offset.x, offset.y, zoomDoubleClickSpeed);
  568. } else {
  569. smoothZoom(lastSingleFingerOffset.x, lastSingleFingerOffset.y, zoomDoubleClickSpeed);
  570. }
  571. }
  572. lastTouchEndTime = now;
  573. touchInProgress = false;
  574. triggerPanEnd();
  575. releaseTouches();
  576. }
  577. }
  578. function getPinchZoomLength(finger1, finger2) {
  579. var dx = finger1.clientX - finger2.clientX;
  580. var dy = finger1.clientY - finger2.clientY;
  581. return Math.sqrt(dx * dx + dy * dy);
  582. }
  583. function onDoubleClick(e) {
  584. beforeDoubleClick(e);
  585. var offset = getOffsetXY(e);
  586. if (transformOrigin) {
  587. // TODO: looks like this is duplicated in the file.
  588. // Need to refactor
  589. offset = getTransformOriginOffset();
  590. }
  591. smoothZoom(offset.x, offset.y, zoomDoubleClickSpeed);
  592. }
  593. function onMouseDown(e) {
  594. // if client does not want to handle this event - just ignore the call
  595. if (beforeMouseDown(e)) return;
  596. if (touchInProgress) {
  597. // modern browsers will fire mousedown for touch events too
  598. // we do not want this: touch is handled separately.
  599. e.stopPropagation();
  600. return false;
  601. }
  602. // for IE, left click == 1
  603. // for Firefox, left click == 0
  604. var isLeftButton =
  605. (e.button === 1 && window.event !== null) || e.button === 0;
  606. if (!isLeftButton) return;
  607. smoothScroll.cancel();
  608. var offset = getOffsetXY(e);
  609. var point = transformToScreen(offset.x, offset.y);
  610. mouseX = point.x;
  611. mouseY = point.y;
  612. // We need to listen on document itself, since mouse can go outside of the
  613. // window, and we will loose it
  614. document.addEventListener('mousemove', onMouseMove);
  615. document.addEventListener('mouseup', onMouseUp);
  616. textSelection.capture(e.target || e.srcElement);
  617. return false;
  618. }
  619. function onMouseMove(e) {
  620. // no need to worry about mouse events when touch is happening
  621. if (touchInProgress) return;
  622. triggerPanStart();
  623. var offset = getOffsetXY(e);
  624. var point = transformToScreen(offset.x, offset.y);
  625. var dx = point.x - mouseX;
  626. var dy = point.y - mouseY;
  627. mouseX = point.x;
  628. mouseY = point.y;
  629. internalMoveBy(dx, dy);
  630. }
  631. function onMouseUp() {
  632. textSelection.release();
  633. triggerPanEnd();
  634. releaseDocumentMouse();
  635. }
  636. function releaseDocumentMouse() {
  637. document.removeEventListener('mousemove', onMouseMove);
  638. document.removeEventListener('mouseup', onMouseUp);
  639. panstartFired = false;
  640. }
  641. function releaseTouches() {
  642. document.removeEventListener('touchmove', handleTouchMove);
  643. document.removeEventListener('touchend', handleTouchEnd);
  644. document.removeEventListener('touchcancel', handleTouchEnd);
  645. panstartFired = false;
  646. multiTouch = false;
  647. }
  648. function onMouseWheel(e) {
  649. // if client does not want to handle this event - just ignore the call
  650. if (beforeWheel(e)) return;
  651. smoothScroll.cancel();
  652. var delta = e.deltaY;
  653. if (e.deltaMode > 0) delta *= 100;
  654. var scaleMultiplier = getScaleMultiplier(delta);
  655. if (scaleMultiplier !== 1) {
  656. var offset = transformOrigin
  657. ? getTransformOriginOffset()
  658. : getOffsetXY(e);
  659. publicZoomTo(offset.x, offset.y, scaleMultiplier);
  660. e.preventDefault();
  661. }
  662. }
  663. function getOffsetXY(e) {
  664. var offsetX, offsetY;
  665. // I tried using e.offsetX, but that gives wrong results for svg, when user clicks on a path.
  666. var ownerRect = owner.getBoundingClientRect();
  667. offsetX = e.clientX - ownerRect.left;
  668. offsetY = e.clientY - ownerRect.top;
  669. return { x: offsetX, y: offsetY };
  670. }
  671. function smoothZoom(clientX, clientY, scaleMultiplier) {
  672. var fromValue = transform.scale;
  673. var from = { scale: fromValue };
  674. var to = { scale: scaleMultiplier * fromValue };
  675. smoothScroll.cancel();
  676. cancelZoomAnimation();
  677. zoomToAnimation = animate(from, to, {
  678. step: function (v) {
  679. zoomAbs(clientX, clientY, v.scale);
  680. },
  681. done: triggerZoomEnd
  682. });
  683. }
  684. function smoothZoomAbs(clientX, clientY, toScaleValue) {
  685. var fromValue = transform.scale;
  686. var from = { scale: fromValue };
  687. var to = { scale: toScaleValue };
  688. smoothScroll.cancel();
  689. cancelZoomAnimation();
  690. zoomToAnimation = animate(from, to, {
  691. step: function (v) {
  692. zoomAbs(clientX, clientY, v.scale);
  693. }
  694. });
  695. }
  696. function getTransformOriginOffset() {
  697. var ownerRect = owner.getBoundingClientRect();
  698. return {
  699. x: ownerRect.width * transformOrigin.x,
  700. y: ownerRect.height * transformOrigin.y
  701. };
  702. }
  703. function publicZoomTo(clientX, clientY, scaleMultiplier) {
  704. smoothScroll.cancel();
  705. cancelZoomAnimation();
  706. return zoomByRatio(clientX, clientY, scaleMultiplier);
  707. }
  708. function cancelZoomAnimation() {
  709. if (zoomToAnimation) {
  710. zoomToAnimation.cancel();
  711. zoomToAnimation = null;
  712. }
  713. }
  714. function getScaleMultiplier(delta) {
  715. var sign = Math.sign(delta);
  716. var deltaAdjustedSpeed = Math.min(0.25, Math.abs(speed * delta / 128));
  717. return 1 - sign * deltaAdjustedSpeed;
  718. }
  719. function triggerPanStart() {
  720. if (!panstartFired) {
  721. triggerEvent('panstart');
  722. panstartFired = true;
  723. smoothScroll.start();
  724. }
  725. }
  726. function triggerPanEnd() {
  727. if (panstartFired) {
  728. // we should never run smooth scrolling if it was multiTouch (pinch zoom animation):
  729. if (!multiTouch) smoothScroll.stop();
  730. triggerEvent('panend');
  731. }
  732. }
  733. function triggerZoomEnd() {
  734. triggerEvent('zoomend');
  735. }
  736. function triggerEvent(name) {
  737. api.fire(name, api);
  738. }
  739. }
  740. function parseTransformOrigin(options) {
  741. if (!options) return;
  742. if (typeof options === 'object') {
  743. if (!isNumber(options.x) || !isNumber(options.y))
  744. failTransformOrigin(options);
  745. return options;
  746. }
  747. failTransformOrigin();
  748. }
  749. function failTransformOrigin(options) {
  750. console.error(options);
  751. throw new Error(
  752. [
  753. 'Cannot parse transform origin.',
  754. 'Some good examples:',
  755. ' "center center" can be achieved with {x: 0.5, y: 0.5}',
  756. ' "top center" can be achieved with {x: 0.5, y: 0}',
  757. ' "bottom right" can be achieved with {x: 1, y: 1}'
  758. ].join('\n')
  759. );
  760. }
  761. function noop() { }
  762. function validateBounds(bounds) {
  763. var boundsType = typeof bounds;
  764. if (boundsType === 'undefined' || boundsType === 'boolean') return; // this is okay
  765. // otherwise need to be more thorough:
  766. var validBounds =
  767. isNumber(bounds.left) &&
  768. isNumber(bounds.top) &&
  769. isNumber(bounds.bottom) &&
  770. isNumber(bounds.right);
  771. if (!validBounds)
  772. throw new Error(
  773. 'Bounds object is not valid. It can be: ' +
  774. 'undefined, boolean (true|false) or an object {left, top, right, bottom}'
  775. );
  776. }
  777. function isNumber(x) {
  778. return Number.isFinite(x);
  779. }
  780. // IE 11 does not support isNaN:
  781. function isNaN(value) {
  782. if (Number.isNaN) {
  783. return Number.isNaN(value);
  784. }
  785. return value !== value;
  786. }
  787. function rigidScroll() {
  788. return {
  789. start: noop,
  790. stop: noop,
  791. cancel: noop
  792. };
  793. }
  794. function autoRun() {
  795. if (typeof document === 'undefined') return;
  796. var scripts = document.getElementsByTagName('script');
  797. if (!scripts) return;
  798. var panzoomScript;
  799. for (var i = 0; i < scripts.length; ++i) {
  800. var x = scripts[i];
  801. if (x.src && x.src.match(/\bpanzoom(\.min)?\.js/)) {
  802. panzoomScript = x;
  803. break;
  804. }
  805. }
  806. if (!panzoomScript) return;
  807. var query = panzoomScript.getAttribute('query');
  808. if (!query) return;
  809. var globalName = panzoomScript.getAttribute('name') || 'pz';
  810. var started = Date.now();
  811. tryAttach();
  812. function tryAttach() {
  813. var el = document.querySelector(query);
  814. if (!el) {
  815. var now = Date.now();
  816. var elapsed = now - started;
  817. if (elapsed < 2000) {
  818. // Let's wait a bit
  819. setTimeout(tryAttach, 100);
  820. return;
  821. }
  822. // If we don't attach within 2 seconds to the target element, consider it a failure
  823. console.error('Cannot find the panzoom element', globalName);
  824. return;
  825. }
  826. var options = collectOptions(panzoomScript);
  827. console.log(options);
  828. window[globalName] = createPanZoom(el, options);
  829. }
  830. function collectOptions(script) {
  831. var attrs = script.attributes;
  832. var options = {};
  833. for (var i = 0; i < attrs.length; ++i) {
  834. var attr = attrs[i];
  835. var nameValue = getPanzoomAttributeNameValue(attr);
  836. if (nameValue) {
  837. options[nameValue.name] = nameValue.value;
  838. }
  839. }
  840. return options;
  841. }
  842. function getPanzoomAttributeNameValue(attr) {
  843. if (!attr.name) return;
  844. var isPanZoomAttribute =
  845. attr.name[0] === 'p' && attr.name[1] === 'z' && attr.name[2] === '-';
  846. if (!isPanZoomAttribute) return;
  847. var name = attr.name.substr(3);
  848. var value = JSON.parse(attr.value);
  849. return { name: name, value: value };
  850. }
  851. }
  852. autoRun();
  853. },{"./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){
  854. /**
  855. * Disallows selecting text.
  856. */
  857. module.exports = createTextSelectionInterceptor;
  858. function createTextSelectionInterceptor(useFake) {
  859. if (useFake) {
  860. return {
  861. capture: noop,
  862. release: noop
  863. };
  864. }
  865. var dragObject;
  866. var prevSelectStart;
  867. var prevDragStart;
  868. var wasCaptured = false;
  869. return {
  870. capture: capture,
  871. release: release
  872. };
  873. function capture(domObject) {
  874. wasCaptured = true;
  875. prevSelectStart = window.document.onselectstart;
  876. prevDragStart = window.document.ondragstart;
  877. window.document.onselectstart = disabled;
  878. dragObject = domObject;
  879. dragObject.ondragstart = disabled;
  880. }
  881. function release() {
  882. if (!wasCaptured) return;
  883. wasCaptured = false;
  884. window.document.onselectstart = prevSelectStart;
  885. if (dragObject) dragObject.ondragstart = prevDragStart;
  886. }
  887. }
  888. function disabled(e) {
  889. e.stopPropagation();
  890. return false;
  891. }
  892. function noop() {}
  893. },{}],3:[function(require,module,exports){
  894. module.exports = makeDomController
  895. function makeDomController(domElement, options) {
  896. var elementValid = (domElement instanceof HTMLElement)
  897. if (!elementValid) {
  898. throw new Error('svg element is required for svg.panzoom to work')
  899. }
  900. var owner = domElement.parentElement
  901. if (!owner) {
  902. throw new Error(
  903. 'Do not apply panzoom to the detached DOM element. '
  904. )
  905. }
  906. domElement.scrollTop = 0;
  907. if (!options.disableKeyboardInteraction) {
  908. owner.setAttribute('tabindex', 0);
  909. }
  910. var api = {
  911. getBBox: getBBox,
  912. getOwner: getOwner,
  913. applyTransform: applyTransform,
  914. }
  915. return api
  916. function getOwner() {
  917. return owner
  918. }
  919. function getBBox() {
  920. // TODO: We should probably cache this?
  921. return {
  922. left: 0,
  923. top: 0,
  924. width: domElement.clientWidth,
  925. height: domElement.clientHeight
  926. }
  927. }
  928. function applyTransform(transform) {
  929. // TODO: Should we cache this?
  930. domElement.style.transformOrigin = '0 0 0';
  931. domElement.style.transform = 'matrix(' +
  932. transform.scale + ', 0, 0, ' +
  933. transform.scale + ', ' +
  934. transform.x + ', ' + transform.y + ')'
  935. }
  936. }
  937. },{}],4:[function(require,module,exports){
  938. (function (global){
  939. /**
  940. * Allows smooth kinetic scrolling of the surface
  941. */
  942. module.exports = kinetic;
  943. function kinetic(getPoint, scroll, settings) {
  944. if (typeof settings !== 'object') {
  945. // setting could come as boolean, we should ignore it, and use an object.
  946. settings = {};
  947. }
  948. var minVelocity = typeof settings.minVelocity === 'number' ? settings.minVelocity : 5;
  949. var amplitude = typeof settings.amplitude === 'number' ? settings.amplitude : 0.25;
  950. var cancelAnimationFrame = typeof settings.cancelAnimationFrame === 'function' ? settings.cancelAnimationFrame : getCancelAnimationFrame();
  951. var requestAnimationFrame = typeof settings.requestAnimationFrame === 'function' ? settings.requestAnimationFrame : getRequestAnimationFrame();
  952. var lastPoint;
  953. var timestamp;
  954. var timeConstant = 342;
  955. var ticker;
  956. var vx, targetX, ax;
  957. var vy, targetY, ay;
  958. var raf;
  959. return {
  960. start: start,
  961. stop: stop,
  962. cancel: dispose
  963. };
  964. function dispose() {
  965. cancelAnimationFrame(ticker);
  966. cancelAnimationFrame(raf);
  967. }
  968. function start() {
  969. lastPoint = getPoint();
  970. ax = ay = vx = vy = 0;
  971. timestamp = new Date();
  972. cancelAnimationFrame(ticker);
  973. cancelAnimationFrame(raf);
  974. // we start polling the point position to accumulate velocity
  975. // Once we stop(), we will use accumulated velocity to keep scrolling
  976. // an object.
  977. ticker = requestAnimationFrame(track);
  978. }
  979. function track() {
  980. var now = Date.now();
  981. var elapsed = now - timestamp;
  982. timestamp = now;
  983. var currentPoint = getPoint();
  984. var dx = currentPoint.x - lastPoint.x;
  985. var dy = currentPoint.y - lastPoint.y;
  986. lastPoint = currentPoint;
  987. var dt = 1000 / (1 + elapsed);
  988. // moving average
  989. vx = 0.8 * dx * dt + 0.2 * vx;
  990. vy = 0.8 * dy * dt + 0.2 * vy;
  991. ticker = requestAnimationFrame(track);
  992. }
  993. function stop() {
  994. cancelAnimationFrame(ticker);
  995. cancelAnimationFrame(raf);
  996. var currentPoint = getPoint();
  997. targetX = currentPoint.x;
  998. targetY = currentPoint.y;
  999. timestamp = Date.now();
  1000. if (vx < -minVelocity || vx > minVelocity) {
  1001. ax = amplitude * vx;
  1002. targetX += ax;
  1003. }
  1004. if (vy < -minVelocity || vy > minVelocity) {
  1005. ay = amplitude * vy;
  1006. targetY += ay;
  1007. }
  1008. raf = requestAnimationFrame(autoScroll);
  1009. }
  1010. function autoScroll() {
  1011. var elapsed = Date.now() - timestamp;
  1012. var moving = false;
  1013. var dx = 0;
  1014. var dy = 0;
  1015. if (ax) {
  1016. dx = -ax * Math.exp(-elapsed / timeConstant);
  1017. if (dx > 0.5 || dx < -0.5) moving = true;
  1018. else dx = ax = 0;
  1019. }
  1020. if (ay) {
  1021. dy = -ay * Math.exp(-elapsed / timeConstant);
  1022. if (dy > 0.5 || dy < -0.5) moving = true;
  1023. else dy = ay = 0;
  1024. }
  1025. if (moving) {
  1026. scroll(targetX + dx, targetY + dy);
  1027. raf = requestAnimationFrame(autoScroll);
  1028. }
  1029. }
  1030. }
  1031. function getCancelAnimationFrame() {
  1032. if (typeof global.cancelAnimationFrame === 'function') return global.cancelAnimationFrame;
  1033. return clearTimeout;
  1034. }
  1035. function getRequestAnimationFrame() {
  1036. if (typeof global.requestAnimationFrame === 'function') return global.requestAnimationFrame;
  1037. return function (handler) {
  1038. return setTimeout(handler, 16);
  1039. }
  1040. }
  1041. }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  1042. },{}],5:[function(require,module,exports){
  1043. module.exports = makeSvgController
  1044. function makeSvgController(svgElement, options) {
  1045. var elementValid = (svgElement instanceof SVGElement)
  1046. if (!elementValid) {
  1047. throw new Error('svg element is required for svg.panzoom to work')
  1048. }
  1049. var owner = svgElement.ownerSVGElement
  1050. if (!owner) {
  1051. throw new Error(
  1052. 'Do not apply panzoom to the root <svg> element. ' +
  1053. 'Use its child instead (e.g. <g></g>). ' +
  1054. 'As of March 2016 only FireFox supported transform on the root element')
  1055. }
  1056. if (!options.disableKeyboardInteraction) {
  1057. owner.setAttribute('tabindex', 0);
  1058. }
  1059. var api = {
  1060. getBBox: getBBox,
  1061. getScreenCTM: getScreenCTM,
  1062. getOwner: getOwner,
  1063. applyTransform: applyTransform,
  1064. initTransform: initTransform
  1065. }
  1066. return api
  1067. function getOwner() {
  1068. return owner
  1069. }
  1070. function getBBox() {
  1071. var bbox = svgElement.getBBox()
  1072. return {
  1073. left: bbox.x,
  1074. top: bbox.y,
  1075. width: bbox.width,
  1076. height: bbox.height,
  1077. }
  1078. }
  1079. function getScreenCTM() {
  1080. var ctm = owner.getCTM();
  1081. if (!ctm) {
  1082. // This is likely firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=873106
  1083. // The code below is not entirely correct, but still better than nothing
  1084. return owner.getScreenCTM();
  1085. }
  1086. return ctm;
  1087. }
  1088. function initTransform(transform) {
  1089. var screenCTM = svgElement.getCTM()
  1090. transform.x = screenCTM.e;
  1091. transform.y = screenCTM.f;
  1092. transform.scale = screenCTM.a;
  1093. owner.removeAttributeNS(null, 'viewBox');
  1094. }
  1095. function applyTransform(transform) {
  1096. svgElement.setAttribute('transform', 'matrix(' +
  1097. transform.scale + ' 0 0 ' +
  1098. transform.scale + ' ' +
  1099. transform.x + ' ' + transform.y + ')')
  1100. }
  1101. }
  1102. },{}],6:[function(require,module,exports){
  1103. module.exports = Transform;
  1104. function Transform() {
  1105. this.x = 0;
  1106. this.y = 0;
  1107. this.scale = 1;
  1108. }
  1109. },{}],7:[function(require,module,exports){
  1110. var BezierEasing = require('bezier-easing')
  1111. // Predefined set of animations. Similar to CSS easing functions
  1112. var animations = {
  1113. ease: BezierEasing(0.25, 0.1, 0.25, 1),
  1114. easeIn: BezierEasing(0.42, 0, 1, 1),
  1115. easeOut: BezierEasing(0, 0, 0.58, 1),
  1116. easeInOut: BezierEasing(0.42, 0, 0.58, 1),
  1117. linear: BezierEasing(0, 0, 1, 1)
  1118. }
  1119. module.exports = animate;
  1120. module.exports.makeAggregateRaf = makeAggregateRaf;
  1121. module.exports.sharedScheduler = makeAggregateRaf();
  1122. function animate(source, target, options) {
  1123. var start = Object.create(null)
  1124. var diff = Object.create(null)
  1125. options = options || {}
  1126. // We let clients specify their own easing function
  1127. var easing = (typeof options.easing === 'function') ? options.easing : animations[options.easing]
  1128. // if nothing is specified, default to ease (similar to CSS animations)
  1129. if (!easing) {
  1130. if (options.easing) {
  1131. console.warn('Unknown easing function in amator: ' + options.easing);
  1132. }
  1133. easing = animations.ease
  1134. }
  1135. var step = typeof options.step === 'function' ? options.step : noop
  1136. var done = typeof options.done === 'function' ? options.done : noop
  1137. var scheduler = getScheduler(options.scheduler)
  1138. var keys = Object.keys(target)
  1139. keys.forEach(function(key) {
  1140. start[key] = source[key]
  1141. diff[key] = target[key] - source[key]
  1142. })
  1143. var durationInMs = typeof options.duration === 'number' ? options.duration : 400
  1144. var durationInFrames = Math.max(1, durationInMs * 0.06) // 0.06 because 60 frames pers 1,000 ms
  1145. var previousAnimationId
  1146. var frame = 0
  1147. previousAnimationId = scheduler.next(loop)
  1148. return {
  1149. cancel: cancel
  1150. }
  1151. function cancel() {
  1152. scheduler.cancel(previousAnimationId)
  1153. previousAnimationId = 0
  1154. }
  1155. function loop() {
  1156. var t = easing(frame/durationInFrames)
  1157. frame += 1
  1158. setValues(t)
  1159. if (frame <= durationInFrames) {
  1160. previousAnimationId = scheduler.next(loop)
  1161. step(source)
  1162. } else {
  1163. previousAnimationId = 0
  1164. setTimeout(function() { done(source) }, 0)
  1165. }
  1166. }
  1167. function setValues(t) {
  1168. keys.forEach(function(key) {
  1169. source[key] = diff[key] * t + start[key]
  1170. })
  1171. }
  1172. }
  1173. function noop() { }
  1174. function getScheduler(scheduler) {
  1175. if (!scheduler) {
  1176. var canRaf = typeof window !== 'undefined' && window.requestAnimationFrame
  1177. return canRaf ? rafScheduler() : timeoutScheduler()
  1178. }
  1179. if (typeof scheduler.next !== 'function') throw new Error('Scheduler is supposed to have next(cb) function')
  1180. if (typeof scheduler.cancel !== 'function') throw new Error('Scheduler is supposed to have cancel(handle) function')
  1181. return scheduler
  1182. }
  1183. function rafScheduler() {
  1184. return {
  1185. next: window.requestAnimationFrame.bind(window),
  1186. cancel: window.cancelAnimationFrame.bind(window)
  1187. }
  1188. }
  1189. function timeoutScheduler() {
  1190. return {
  1191. next: function(cb) {
  1192. return setTimeout(cb, 1000/60)
  1193. },
  1194. cancel: function (id) {
  1195. return clearTimeout(id)
  1196. }
  1197. }
  1198. }
  1199. function makeAggregateRaf() {
  1200. var frontBuffer = new Set();
  1201. var backBuffer = new Set();
  1202. var frameToken = 0;
  1203. return {
  1204. next: next,
  1205. cancel: next,
  1206. clearAll: clearAll
  1207. }
  1208. function clearAll() {
  1209. frontBuffer.clear();
  1210. backBuffer.clear();
  1211. cancelAnimationFrame(frameToken);
  1212. frameToken = 0;
  1213. }
  1214. function next(callback) {
  1215. backBuffer.add(callback);
  1216. renderNextFrame();
  1217. }
  1218. function renderNextFrame() {
  1219. if (!frameToken) frameToken = requestAnimationFrame(renderFrame);
  1220. }
  1221. function renderFrame() {
  1222. frameToken = 0;
  1223. var t = backBuffer;
  1224. backBuffer = frontBuffer;
  1225. frontBuffer = t;
  1226. frontBuffer.forEach(function(callback) {
  1227. callback();
  1228. });
  1229. frontBuffer.clear();
  1230. }
  1231. function cancel(callback) {
  1232. backBuffer.delete(callback);
  1233. }
  1234. }
  1235. },{"bezier-easing":8}],8:[function(require,module,exports){
  1236. /**
  1237. * https://github.com/gre/bezier-easing
  1238. * BezierEasing - use bezier curve for transition easing function
  1239. * by Gaëtan Renaudeau 2014 - 2015 – MIT License
  1240. */
  1241. // These values are established by empiricism with tests (tradeoff: performance VS precision)
  1242. var NEWTON_ITERATIONS = 4;
  1243. var NEWTON_MIN_SLOPE = 0.001;
  1244. var SUBDIVISION_PRECISION = 0.0000001;
  1245. var SUBDIVISION_MAX_ITERATIONS = 10;
  1246. var kSplineTableSize = 11;
  1247. var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);
  1248. var float32ArraySupported = typeof Float32Array === 'function';
  1249. function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; }
  1250. function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; }
  1251. function C (aA1) { return 3.0 * aA1; }
  1252. // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
  1253. function calcBezier (aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; }
  1254. // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
  1255. function getSlope (aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); }
  1256. function binarySubdivide (aX, aA, aB, mX1, mX2) {
  1257. var currentX, currentT, i = 0;
  1258. do {
  1259. currentT = aA + (aB - aA) / 2.0;
  1260. currentX = calcBezier(currentT, mX1, mX2) - aX;
  1261. if (currentX > 0.0) {
  1262. aB = currentT;
  1263. } else {
  1264. aA = currentT;
  1265. }
  1266. } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
  1267. return currentT;
  1268. }
  1269. function newtonRaphsonIterate (aX, aGuessT, mX1, mX2) {
  1270. for (var i = 0; i < NEWTON_ITERATIONS; ++i) {
  1271. var currentSlope = getSlope(aGuessT, mX1, mX2);
  1272. if (currentSlope === 0.0) {
  1273. return aGuessT;
  1274. }
  1275. var currentX = calcBezier(aGuessT, mX1, mX2) - aX;
  1276. aGuessT -= currentX / currentSlope;
  1277. }
  1278. return aGuessT;
  1279. }
  1280. function LinearEasing (x) {
  1281. return x;
  1282. }
  1283. module.exports = function bezier (mX1, mY1, mX2, mY2) {
  1284. if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) {
  1285. throw new Error('bezier x values must be in [0, 1] range');
  1286. }
  1287. if (mX1 === mY1 && mX2 === mY2) {
  1288. return LinearEasing;
  1289. }
  1290. // Precompute samples table
  1291. var sampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize);
  1292. for (var i = 0; i < kSplineTableSize; ++i) {
  1293. sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
  1294. }
  1295. function getTForX (aX) {
  1296. var intervalStart = 0.0;
  1297. var currentSample = 1;
  1298. var lastSample = kSplineTableSize - 1;
  1299. for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) {
  1300. intervalStart += kSampleStepSize;
  1301. }
  1302. --currentSample;
  1303. // Interpolate to provide an initial guess for t
  1304. var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]);
  1305. var guessForT = intervalStart + dist * kSampleStepSize;
  1306. var initialSlope = getSlope(guessForT, mX1, mX2);
  1307. if (initialSlope >= NEWTON_MIN_SLOPE) {
  1308. return newtonRaphsonIterate(aX, guessForT, mX1, mX2);
  1309. } else if (initialSlope === 0.0) {
  1310. return guessForT;
  1311. } else {
  1312. return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2);
  1313. }
  1314. }
  1315. return function BezierEasing (x) {
  1316. // Because JavaScript number are imprecise, we should guarantee the extremes are right.
  1317. if (x === 0) {
  1318. return 0;
  1319. }
  1320. if (x === 1) {
  1321. return 1;
  1322. }
  1323. return calcBezier(getTForX(x), mY1, mY2);
  1324. };
  1325. };
  1326. },{}],9:[function(require,module,exports){
  1327. module.exports = function(subject) {
  1328. validateSubject(subject);
  1329. var eventsStorage = createEventsStorage(subject);
  1330. subject.on = eventsStorage.on;
  1331. subject.off = eventsStorage.off;
  1332. subject.fire = eventsStorage.fire;
  1333. return subject;
  1334. };
  1335. function createEventsStorage(subject) {
  1336. // Store all event listeners to this hash. Key is event name, value is array
  1337. // of callback records.
  1338. //
  1339. // A callback record consists of callback function and its optional context:
  1340. // { 'eventName' => [{callback: function, ctx: object}] }
  1341. var registeredEvents = Object.create(null);
  1342. return {
  1343. on: function (eventName, callback, ctx) {
  1344. if (typeof callback !== 'function') {
  1345. throw new Error('callback is expected to be a function');
  1346. }
  1347. var handlers = registeredEvents[eventName];
  1348. if (!handlers) {
  1349. handlers = registeredEvents[eventName] = [];
  1350. }
  1351. handlers.push({callback: callback, ctx: ctx});
  1352. return subject;
  1353. },
  1354. off: function (eventName, callback) {
  1355. var wantToRemoveAll = (typeof eventName === 'undefined');
  1356. if (wantToRemoveAll) {
  1357. // Killing old events storage should be enough in this case:
  1358. registeredEvents = Object.create(null);
  1359. return subject;
  1360. }
  1361. if (registeredEvents[eventName]) {
  1362. var deleteAllCallbacksForEvent = (typeof callback !== 'function');
  1363. if (deleteAllCallbacksForEvent) {
  1364. delete registeredEvents[eventName];
  1365. } else {
  1366. var callbacks = registeredEvents[eventName];
  1367. for (var i = 0; i < callbacks.length; ++i) {
  1368. if (callbacks[i].callback === callback) {
  1369. callbacks.splice(i, 1);
  1370. }
  1371. }
  1372. }
  1373. }
  1374. return subject;
  1375. },
  1376. fire: function (eventName) {
  1377. var callbacks = registeredEvents[eventName];
  1378. if (!callbacks) {
  1379. return subject;
  1380. }
  1381. var fireArguments;
  1382. if (arguments.length > 1) {
  1383. fireArguments = Array.prototype.splice.call(arguments, 1);
  1384. }
  1385. for(var i = 0; i < callbacks.length; ++i) {
  1386. var callbackInfo = callbacks[i];
  1387. callbackInfo.callback.apply(callbackInfo.ctx, fireArguments);
  1388. }
  1389. return subject;
  1390. }
  1391. };
  1392. }
  1393. function validateSubject(subject) {
  1394. if (!subject) {
  1395. throw new Error('Eventify cannot use falsy object as events subject');
  1396. }
  1397. var reservedWords = ['on', 'fire', 'off'];
  1398. for (var i = 0; i < reservedWords.length; ++i) {
  1399. if (subject.hasOwnProperty(reservedWords[i])) {
  1400. throw new Error("Subject cannot be eventified, since it already has property '" + reservedWords[i] + "'");
  1401. }
  1402. }
  1403. }
  1404. },{}],10:[function(require,module,exports){
  1405. /**
  1406. * This module used to unify mouse wheel behavior between different browsers in 2014
  1407. * Now it's just a wrapper around addEventListener('wheel');
  1408. *
  1409. * Usage:
  1410. * var addWheelListener = require('wheel').addWheelListener;
  1411. * var removeWheelListener = require('wheel').removeWheelListener;
  1412. * addWheelListener(domElement, function (e) {
  1413. * // mouse wheel event
  1414. * });
  1415. * removeWheelListener(domElement, function);
  1416. */
  1417. module.exports = addWheelListener;
  1418. // But also expose "advanced" api with unsubscribe:
  1419. module.exports.addWheelListener = addWheelListener;
  1420. module.exports.removeWheelListener = removeWheelListener;
  1421. function addWheelListener(element, listener, useCapture) {
  1422. element.addEventListener('wheel', listener, useCapture);
  1423. }
  1424. function removeWheelListener( element, listener, useCapture ) {
  1425. element.removeEventListener('wheel', listener, useCapture);
  1426. }
  1427. },{}]},{},[1])(1)
  1428. });