GithubHelp home page GithubHelp logo

Comments (2)

cptnoblivious avatar cptnoblivious commented on July 19, 2024

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.

timmywil avatar timmywil commented on July 19, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.