Comments (2)
I ended up solving this problem in our own way for Wordvoyance. Like I said, I'm barely a programmer and I don't understand your project well enough to contribute a fix myself, but I can provide our solution for your consideration. Be aware this will be highly tailored to our specific use case, but I hope you can identify what we did differently and use it to your advantage. The following allows screenspace-correct transforms for zoomable elements inside arbitrarily-scaled parent containers:
`////////////////////////////////////////////////////////////////////////////////
// pinchZoom
// Pinch to Zoom logic for gameBoard.
////////////////////////////////////////////////////////////////////////////////
const pinchZoom = (element) => {
let mode = 'pan';
let pinchZoomScale = 1;
let maxElementScale = 2;
let start = {x: 0, y: 0, firstMidpoint: 0, originX: 0, originY: 0, distance: 0};
let currentTranslation = { x: 0, y: 0, originX: 0, originY: 0};
function getTouchDistance(event) {
return event.touches.length == 2 ? Math.hypot(event.touches[0].pageX - event.touches[1].pageX, event.touches[0].pageY - event.touches[1].pageY) : 0;
}
function getMidpoint(event) {
let midpointVals = {};
midpointVals.x = event.touches.length == 2 ? (event.touches[0].clientX + event.touches[1].clientX) / 2 : event.touches[0].clientX;
midpointVals.y = event.touches.length == 2 ? (event.touches[0].clientY + event.touches[1].clientY) / 2 : event.touches[0].clientY;
return midpointVals;
}
function getRelativeElementLocation(midpoint) {
let relativeVals = {};
relativeVals.x = midpoint.x - element.getBoundingClientRect().left;
relativeVals.y = midpoint.y - element.getBoundingClientRect().top;
return relativeVals;
}
function getRelativeParentLocation(midpoint) {
let relativeVals = {};
relativeVals.x = midpoint.x - element.parentElement.getBoundingClientRect().left;
relativeVals.y = midpoint.y - element.parentElement.getBoundingClientRect().top;
return relativeVals;
}
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
element.addEventListener('touchstart', (event) => {
if (event.touches.length == 1) {
mode = 'pan';
}
if (event.target.classList.contains('rackTile')) {
mode = 'stop';
}
if (mode === 'stop') {
return;
}
if (event.touches.length == 2) {
mode = 'pinch';
}
let midpoint = getMidpoint(event);
start.firstMidpoint = midpoint;
start.x = midpoint.x;
start.y = midpoint.y;
start.distance = getTouchDistance(event);
}, {passive: true} );
element.addEventListener('touchmove', (event) => {
if (event.target.classList.contains('rackTile')) {
mode = 'stop';
}
if (mode === 'stop') {
return;
}
if (event.touches.length === 1) {
if (mode !== 'pan') {
return;
}
}
let scale = event.touches.length == 2 ? getTouchDistance(event) / start.distance : 1;
pinchZoomScale = Math.min(Math.max(1, scale * pinchZoomScale), maxElementScale);
// Measurements
let containerWidth = element.parentElement.clientWidth; //1080
let containerHeight = element.parentElement.clientHeight; //1080
let containerWidthSS = element.parentElement.getBoundingClientRect().width; //screenspace (~300 on my phone)
let containerHeightSS = element.parentElement.getBoundingClientRect().height; //screenspace (~300 on my phone)
let elementWidth = element.clientWidth * pinchZoomScale; //1080 scaled up
let elementHeight = element.clientHeight * pinchZoomScale; //1080 scaled up
let elementWidthSS = element.getBoundingClientRect().width; //screenspace scaled up (~600 on my phone)
let elementHeightSS = element.getBoundingClientRect().height; //screenspace scaled up (~600 on my phone)
let midpointPan = getMidpoint(event); //screenspace
let midpointPinch = start.firstMidpoint; //screenspace
let relativeMidpoint = getRelativeElementLocation(midpointPinch); //screenspace
let relativeMidpointOfParent = getRelativeParentLocation(midpointPinch); //screenspace
let relativeMidpointPixels = {};
let relativeMidpointOfParentPixels = {};
relativeMidpointPixels.x = elementWidth * (relativeMidpoint.x / elementWidthSS);
relativeMidpointPixels.y = elementHeight * (relativeMidpoint.y / elementHeightSS);
relativeMidpointOfParentPixels.x = containerWidth * (relativeMidpointOfParent.x / containerWidthSS);
relativeMidpointOfParentPixels.y = containerHeight * (relativeMidpointOfParent.y / containerHeightSS);
// Calculate the new origin locations based on where we touched the element.
// The parent is used as a reference to the element's original size.
let originElement = {};
originElement.x = ((elementWidth / 2) - relativeMidpointPixels.x);
originElement.y = ((elementHeight / 2) - relativeMidpointPixels.y);
let originParent = {};
originParent.x = ((containerWidth / 2) - relativeMidpointOfParentPixels.x);
originParent.y = ((containerHeight / 2) - relativeMidpointOfParentPixels.y);
// Translation boundaries. To disable boundaries, comment-out the clamp functions below.
let minX = ((elementWidth - containerWidth) / 2) * -1;
let maxX = ((elementWidth - containerWidth) / 2);
let minY = ((elementHeight - containerHeight) / 2) * -1;
let maxY = ((elementHeight - containerHeight) / 2);
// Translation calculation
// Delta is used to offset or "scroll" the element when the midpoint moves.
let panDeltaX = ((midpointPan.x - start.x) * 2); // The pan calculation.
let panDeltaY = ((midpointPan.y - start.y) * 2);
let originDeltaX = originElement.x - originParent.x; // The additional translation to center the scaling point on our midpoint.
let originDeltaY = originElement.y - originParent.y;
panDeltaX = clamp(panDeltaX, minX, maxX);
panDeltaY = clamp(panDeltaY, minY, maxY);
originDeltaX = clamp(originDeltaX, minX, maxX);
originDeltaY = clamp(originDeltaY, minY, maxY);
let combinedX = originDeltaX + panDeltaX;
let combinedY = originDeltaY + panDeltaY;
let clampedDeltaX = clamp(combinedX, minX, maxX);
let clampedDeltaY = clamp(combinedY, minY, maxY);
// Our solution differs because we have to account for a parent element of arbitrary scale, the scene container itself.
// Scale-corrected from top left corner.
let transformXAdjusted = (relativeMidpointPixels.x * (pinchZoomScale - 1))
// Centered to element.
- ((containerWidth / 2) * (pinchZoomScale - 1))
// Translated by a portion of the size difference between element and parent (centered) then again by finger pan.
+ clampedDeltaX;
//-----------------------------------------------------------------
// Scale-corrected from top left corner.
let transformYAdjusted = (relativeMidpointPixels.y * (pinchZoomScale - 1))
// Centered to element.
- ((containerHeight / 2) * (pinchZoomScale - 1))
// Translated by a portion of the size difference between element and parent (centered) then again by finger pan.
+ clampedDeltaY;
// Transform the element
let transformOrigin = `${(relativeMidpointPixels.x)}px ${(relativeMidpointPixels.y)}px`;
let transform = `translate(${(transformXAdjusted)}px, ${transformYAdjusted}px) scale(${pinchZoomScale})`;
element.style.transformOrigin = transformOrigin;
element.style.transform = transform;
// Update results for next interation
start.x = midpointPan.x;
start.y = midpointPan.y;
start.originX = originElement.x;
start.originY = originElement.y;
start.distance = event.touches.length == 2 ? getTouchDistance(event) : start.distance;
currentTranslation.x = panDeltaX;
currentTranslation.y = panDeltaY;
currentTranslation.originX = originDeltaX;
currentTranslation.originY = originDeltaY;
}, {passive: true} );
}
pinchZoom(gameBoard);`
from panzoom.
Thanks for opening an issue! This is the kind of thing where if I try to support all possible scenarios, the library would likely end up with a lot more code with not lot of value for 95% of users. I am planning a rewrite soon and will try to keep this in mind, but I am not planning any changes for now. That said, I'm glad you got it working for your case and your example code may be valuable for future users.
from panzoom.
Related Issues (20)
- Using "contain:outside" throws TypeError: Window.getComputedStyle: Argument 1 is not an object HOT 3
- Issues with handleDown fn when there's a need to select text on elements HOT 1
- Feature (Epic?): Improve Panzoom for nested content use case (list of components, etc.) HOT 2
- Feature: adjust the zoom rate to increase with element size HOT 9
- Feature: option to pan on wheel event HOT 2
- Cannot find name '.....' in typescript files HOT 1
- Feature: disable one axis scaling HOT 1
- Jitter on touch devices when zooming out 'beyond the limit'. HOT 2
- Feature: Infinite horizontal scroll, for a map HOT 2
- Incorrect pan position after zooming with a single pointer.
- Panning and Zooming to centre of a selected element HOT 2
- Pinch And Pan not much precise - at least with Canvas
- cant seem to make sense out of focal
- Feature: adapt proportionally - scale element to fit container HOT 1
- False zoom location with zoomWithWheel when having a large panzoom element HOT 2
- disablePan not working? HOT 3
- Was something like a smart option added? HOT 1
- Logitech smooth scrolling causes wheel to zoom into full min and full max HOT 1
- Pinch in Firefox does not work correctly
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from panzoom.