diff --git a/README.md b/README.md index d83a878..ef534a3 100644 --- a/README.md +++ b/README.md @@ -405,6 +405,51 @@ To pan in a smooth way use `smoothMoveTo(,)`: panzoom.smoothMoveTo(0, 0); ``` +To pan and zoom to a specific in dom/svg coordinate use: + +``` js +rect = { + top: 10, + left: 20, + bottom: 30, + right: 40, +}; + +panzoom.showRectangle(rect); +``` + +to do this in a smooth fashion do + +``` js +panzoom.smoothShowRectangle(rect); +``` +you can also give a function that gets the current rect and the target rect to determine the duration of the animation, like: + +``` js +panzoom.smoothShowRectangle(rect, (from, to) => { + var distance = Math.sqrt( + Math.pow(from.top - to.top, 2) + + Math.pow(from.right - to.right, 2) + + Math.pow(from.bottom - to.bottom, 2) + + Math.pow(from.left - to.left, 2) + ); + + var exp_diff = Math.exp(distance / 1000); + var sigmoid = (exp_diff * 1000) / (exp_diff + 1); + + return sigmoid; +}) +``` + +Further more: the `smoothShowRectangle` return a promise, so that the caller can act on the completion of the animation. + +``` js +panzoom.smoothShowRectangle(rect) + .then(() => { + console.log("animation complete"); + }); +``` + # license MIT diff --git a/index.d.ts b/index.d.ts index 84f20a5..ad58404 100644 --- a/index.d.ts +++ b/index.d.ts @@ -50,22 +50,23 @@ declare module "panzoom" { dispose: () => void; moveBy: (dx: number, dy: number, smooth: boolean) => void; moveTo: (x: number, y: number) => void; - smoothMoveTo: (x: number, y: number) => void; - centerOn: (ui: any) => void; + smoothMoveTo: (x: number, y: number) => Promise; + centerOn: (ui: any) => Promise; zoomTo: (clientX: number, clientY: number, scaleMultiplier: number) => void; zoomAbs: (clientX: number, clientY: number, zoomLevel: number) => void; smoothZoom: ( clientX: number, clientY: number, scaleMultiplier: number - ) => void; + ) => Promise; smoothZoomAbs: ( clientX: number, clientY: number, toScaleValue: number - ) => void; + ) => Promise; getTransform: () => Transform; showRectangle: (rect: ClientRect) => void; + smoothShowRectangle: (rect: ClientRect, duration: (from:ClientRect, to:ClientRect) => number) => Promise; pause: () => void; resume: () => void; isPaused: () => boolean; diff --git a/index.js b/index.js index 2833d45..22a31b3 100644 --- a/index.js +++ b/index.js @@ -102,6 +102,7 @@ function createPanZoom(domElement, options) { var moveByAnimation; var zoomToAnimation; + var showRectangleAnimation; var multiTouch; var paused = false; @@ -119,6 +120,7 @@ function createPanZoom(domElement, options) { smoothZoom: smoothZoom, smoothZoomAbs: smoothZoomAbs, showRectangle: showRectangle, + smoothShowRectangle: publicSmoothShowRectangle, pause: pause, resume: resume, @@ -168,7 +170,17 @@ function createPanZoom(domElement, options) { } function showRectangle(rect) { - // TODO: this duplicates autocenter. I think autocenter should go. + cancelAllAnimations(); + internalShowRectangle(rect); + } + + function internalShowRectangle(rect) { + var newTransform = clientRectToTransform(rect); + + setTransform(newTransform); + } + + function clientRectToTransform(rect) { var clientRect = owner.getBoundingClientRect(); var size = transformToScreen(clientRect.width, clientRect.height); @@ -181,9 +193,72 @@ function createPanZoom(domElement, options) { 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; + + var newTransform = new Transform(); + newTransform.x = -(rect.left + rectWidth / 2) * scale + size.x / 2; + newTransform.y = -(rect.top + rectHeight / 2) * scale + size.y / 2; + newTransform.scale = scale; + + return newTransform; + } + + function setTransform(newTransform) { + transform.x = newTransform.x; + transform.y = newTransform.y; + transform.scale = newTransform.scale; + + triggerEvent('pan'); + triggerEvent('zoom'); + makeDirty(); + } + + function publicSmoothShowRectangle(rect, duration = undefined) { + cancelAllAnimations(); + + var to = rect; + // get rect from current transform + var from = transformToClientRect(transform); + + // default duration is 600ms + var dur = 600; + if (typeof duration === 'function') { + // let consumer calculate a duration based on the new and current transform + dur = duration(from, to); + } + + var p = new Promise((resolve, _) => { + showRectangleAnimation = animate(from, to, { + duration: dur, + step: function (nextTransform) { + internalShowRectangle(nextTransform); + }, + done: () => { + triggerZoomEnd(); + triggerPanEnd(); + resolve(true); + } + }); + }) + + return p; + } + + // should this be made public? + function transformToClientRect(transform) { + var clientRect = owner.getBoundingClientRect(); + var size = transformToScreen(clientRect.width, clientRect.height); + + var w = size.x / transform.scale; + var h = size.y / transform.scale; + var l = transform.x / -transform.scale; + var t = transform.y / -transform.scale; + + return { + top: t, + left: l, + bottom: t + h, + right: l + w, + }; } function transformToScreen(x, y) { @@ -437,37 +512,50 @@ function createPanZoom(domElement, options) { var dx = container.width / 2 - cx; var dy = container.height / 2 - cy; - internalMoveBy(dx, dy, true); + return internalMoveBy(dx, dy, true); } function smoothMoveTo(x, y){ - internalMoveBy(x - transform.x, y - transform.y, true); + return internalMoveBy(x - transform.x, y - transform.y, true); } function internalMoveBy(dx, dy, smooth) { - if (!smooth) { - return moveBy(dx, dy); - } - - if (moveByAnimation) moveByAnimation.cancel(); - - var from = { x: 0, y: 0 }; - var to = { x: dx, y: dy }; - var lastX = 0; - var lastY = 0; - - moveByAnimation = animate(from, to, { - step: function (v) { - moveBy(v.x - lastX, v.y - lastY); - - lastX = v.x; - lastY = v.y; + var p = new Promise((resolve, _) => { + cancelAllAnimations(); + + if (!smooth) { + moveBy(dx, dy); + + triggerZoomEnd(); + triggerPanEnd(); + resolve(true); + } else { + var from = { x: 0, y: 0 }; + var to = { x: dx, y: dy }; + var lastX = 0; + var lastY = 0; + + moveByAnimation = animate(from, to, { + step: function (v) { + moveBy(v.x - lastX, v.y - lastY); + + lastX = v.x; + lastY = v.y; + }, + done: () => { + triggerZoomEnd(); + triggerPanEnd(); + resolve(true); + } + }); } - }); + }) + + return p; } function scroll(x, y) { - cancelZoomAnimation(); + cancelAllAnimations(); moveTo(x, y); } @@ -500,7 +588,7 @@ function createPanZoom(domElement, options) { frameAnimation = 0; } - smoothScroll.cancel(); + cancelAllAnimations(); releaseDocumentMouse(); releaseTouches(); @@ -624,7 +712,7 @@ function createPanZoom(domElement, options) { mouseX = point.x; mouseY = point.y; - smoothScroll.cancel(); + cancelAllAnimations(); startTouchListenerIfNeeded(); } @@ -745,7 +833,7 @@ function createPanZoom(domElement, options) { (e.button === 1 && window.event !== null) || e.button === 0; if (!isLeftButton) return; - smoothScroll.cancel(); + cancelAllAnimations(); var offset = getOffsetXY(e); var point = transformToScreen(offset.x, offset.y); @@ -803,7 +891,7 @@ function createPanZoom(domElement, options) { // if client does not want to handle this event - just ignore the call if (beforeWheel(e)) return; - smoothScroll.cancel(); + cancelAllAnimations(); var delta = e.deltaY; if (e.deltaMode > 0) delta *= 100; @@ -834,15 +922,22 @@ function createPanZoom(domElement, options) { var from = { scale: fromValue }; var to = { scale: scaleMultiplier * fromValue }; - smoothScroll.cancel(); - cancelZoomAnimation(); + cancelAllAnimations(); + + var p = new Promise((resolve, _) => { + zoomToAnimation = animate(from, to, { + step: function (v) { + zoomAbs(clientX, clientY, v.scale); + }, + done: () => { + triggerZoomEnd(); + triggerPanEnd(); + resolve(true); + } + }); + }) - zoomToAnimation = animate(from, to, { - step: function (v) { - zoomAbs(clientX, clientY, v.scale); - }, - done: triggerZoomEnd - }); + return p; } function smoothZoomAbs(clientX, clientY, toScaleValue) { @@ -850,14 +945,22 @@ function createPanZoom(domElement, options) { var from = { scale: fromValue }; var to = { scale: toScaleValue }; - smoothScroll.cancel(); - cancelZoomAnimation(); + cancelAllAnimations(); + + var p = new Promise((resolve, _) => { + zoomToAnimation = animate(from, to, { + step: function (v) { + zoomAbs(clientX, clientY, v.scale); + }, + done: () => { + triggerZoomEnd(); + triggerPanEnd(); + resolve(true); + } + }); + }) - zoomToAnimation = animate(from, to, { - step: function (v) { - zoomAbs(clientX, clientY, v.scale); - } - }); + return p; } function getTransformOriginOffset() { @@ -869,11 +972,17 @@ function createPanZoom(domElement, options) { } function publicZoomTo(clientX, clientY, scaleMultiplier) { - smoothScroll.cancel(); - cancelZoomAnimation(); + cancelAllAnimations(); return zoomByRatio(clientX, clientY, scaleMultiplier); } + function cancelAllAnimations() { + cancelShowRectangleAnimation(); + cancelZoomAnimation(); + cancelMoveByAnimation(); + cancelSmoothScroll(); + } + function cancelZoomAnimation() { if (zoomToAnimation) { zoomToAnimation.cancel(); @@ -881,6 +990,24 @@ function createPanZoom(domElement, options) { } } + function cancelSmoothScroll() { + smoothScroll.cancel(); + } + + function cancelMoveByAnimation() { + if (moveByAnimation) { + moveByAnimation.cancel(); + moveByAnimation = null; + } + } + + function cancelShowRectangleAnimation() { + if (showRectangleAnimation) { + showRectangleAnimation.cancel(); + showRectangleAnimation = null; + } + } + function getScaleMultiplier(delta) { var sign = Math.sign(delta); var deltaAdjustedSpeed = Math.min(0.25, Math.abs(speed * delta / 128));