Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lightbox 2.0: add basic pinch zoom #12219

Merged
merged 4 commits into from Dec 1, 2017

Conversation

cathyxz
Copy link
Contributor

@cathyxz cathyxz commented Nov 28, 2017

This PR adds basic pinch zoom to amp-lightbox-viewer via image-viewer, tested on Android/Chrome (iOS testing in progress).

Some small tweaks were made to the behavior of PinchRecognizer and SwipeXYRecognizer because the default implementation keeps causing pinches to be miscategorized as swipes. Comments inline.

TODOs:

  • iOS testing.
  • fix tests. (Forgot that GestureRecognizers have lovely and elaborate tests).

Added a event.preventDefault() for iOS safari. The pinch zoom is now pretty smooth. =)

@@ -691,42 +693,44 @@ export class PinchRecognizer extends GestureRecognizer {
/** @override */
onTouchStart(e) {
const touches = e.touches;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should only stop tracking this series if touches are more than 2. If there has been one touch, PinchRecognizer should wait for a second touch, since practically speaking, when you pinch a phone, the touches very rarely happen simultaneously.

}

/** @override */
onTouchMove(e) {
const touches = e.touches;
if (!touches || touches.length != 2) {
return false;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of returning false, which causes PinchRecognizer to stop tracking this event series, we return true, but do not act on the touch event unless the number of touches is explicitly 2.

const dx2 = this.lastX2_ - this.startX2_;
const dy2 = this.lastY2_ - this.startY2_;
// Fingers should move in opposite directions and go over the threshold.
if (dx1 * dx2 <= 0 && dy1 * dy2 <= 0) {
Copy link
Contributor Author

@cathyxz cathyxz Nov 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two boundaries here were tweaked from 8 to 3 and 10 respectively.

  • Lowered the standards for registering a signalReady from 8 to 3. Empirically on the android tester, most pinch zooms here registered a delta of 3-8. Another possibility is to look at the total distance and to see if it is larger than 8.
  • Increased the standard for dropping this event series from 8 to 10, since swipes are usually a lot more generous than pinches.
    This will be finalized after iOS testing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empirical values should be sufficient, especially when distinguishing between two gestures. Let's move these thresholds from magic literals into named magic constants at the module level, like:

/**
 * Threshold in pixels for ...
 */
const PINCH_THRESHOLD = 4;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -339,17 +340,25 @@ export class ImageViewer {
const deltaX = this.viewerBox_.width / 2 - e.data.clientX;
const deltaY = this.viewerBox_.height / 2 - e.data.clientY;
this.onZoom_(newScale, deltaX, deltaY, true).then(() => {
return this.onZoomRelease_(0, 0, 0, 0, 0, 0);
return this.onZoomRelease_();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored this, since DoubleTap zoom and Pinch zoom only needs a subset of the onZoomRelease functionality necessary for TapZoom.

* @param {number} deltaY
* @param {number} dir
* @private
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a wrapper for naming conventions. We could also take it out and use onZoomInc. I'm not actually sure why it's called onZoomInc. Maybe onZoomEvent / onZoomAction would be more appropriate? Feedback welcome. =)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah onZoomInc is probably not the best name. I generally prefer to avoid abbreviations in variable and function names for this reason. I guess it was onZoomIncrement?
Maybe a better name could describe the action that the function accomplishes. zoomToPoint? zoomInstant? Naming is hard : P

* released.
* @return {!Promise}
* @private
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically refactored out this shared portion so that DoubleTap zoom and Pinch zoom can use this without unnecessary variables.

@cathyxz cathyxz force-pushed the feature/lightbox-pinch-zoom branch 2 times, most recently from b2b37fc to 2079d7e Compare November 28, 2017 23:15
@cathyxz
Copy link
Contributor Author

cathyxz commented Nov 28, 2017

/to @aghassemi @cvializ

Copy link
Contributor

@cvializ cvializ left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks awesome! I just have a few thoughts and questions

const touches = e.changedTouches || e.touches;
if (touches && touches.length == 1) {
const touches = e.touches;
if (!touches || touches.length != 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be clearer with touches && touches.length == 1 and a return false; at the end of the function. Thinking about what touches.length != 1 might mean takes a little more effort to reason about the condition.

const touches = e.changedTouches || e.touches;
if (touches && touches.length == 1) {
const touches = e.touches;
if (!touches || touches.length != 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto the above

@@ -271,8 +273,10 @@ class SwipeRecognizer extends GestureRecognizer {

/** @override */
onTouchMove(e) {
const touches = e.changedTouches || e.touches;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does changing this from e.changedTouches || e.touches; to just touches = e.touches; change the behavior? I notice the the TapzoomRecognizer#onTouchMove still uses e.changedTouches. Can you help me understand the different cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So changedTouches refers to all touches involved in this event. Aka touches that moved, initiated, or were removed. touches refers to all contact points with the screen. This changes the functionality of swipe so that it only works when one finger is on the screen--basically no matter how many changedTouches there are, if more than one touch is on the screen, then we don't register a swipe. This was done because a large number of pinches were aggressively misread as swipes.

For TapZoom, I'm not sure if we consider one finger on the screen (not moving), and a second finger moving a TapZoom or a Pinch. If we assign that to Pinch and not TapZoom, then we can make the same change to TapZoom.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, that makes sense. My initial impression is that one stationary finger and one moving finger should be a pinch zoom and not a tap zoom. macOS seems to require both fingers moving, but iOS's default behavior seems to allow one finger stationary or both fingers moving to pinch zoom

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. I've gone ahead and updated TapZoom to const touches = e.touches as well.

@@ -512,7 +516,9 @@ export class TapzoomRecognizer extends GestureRecognizer {
/** @override */
onTouchMove(e) {
const touches = e.changedTouches || e.touches;
if (touches && touches.length == 1) {
if (!touches || touches.length != 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto the above

const dx2 = this.lastX2_ - this.startX2_;
const dy2 = this.lastY2_ - this.startY2_;
// Fingers should move in opposite directions and go over the threshold.
if (dx1 * dx2 <= 0 && dy1 * dy2 <= 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empirical values should be sufficient, especially when distinguishing between two gestures. Let's move these thresholds from magic literals into named magic constants at the module level, like:

/**
 * Threshold in pixels for ...
 */
const PINCH_THRESHOLD = 4;

* @param {number} centerClientY
* @param {number} deltaX
* @param {number} deltaY
* @param {number} dir
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the whitespace in this comment is incorrect. The stars should be at the same indentation as the first star in the jsdoc comment opener.

* @param {number} deltaY
* @param {number} dir
* @private
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah onZoomInc is probably not the best name. I generally prefer to avoid abbreviations in variable and function names for this reason. I guess it was onZoomIncrement?
Maybe a better name could describe the action that the function accomplishes. zoomToPoint? zoomInstant? Naming is hard : P

const dx2 = this.lastX2_ - this.startX2_;
const dy2 = this.lastY2_ - this.startY2_;
// Fingers should move in opposite directions and go over the threshold.
if (dx1 * dx2 <= 0 && dy1 * dy2 <= 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.data.deltaX, e.data.deltaY, e.data.velocityY, e.data.velocityY);
}
});
gestures.onGesture(PinchRecognizer, e => {
// To disable iOS 10 Safari default pinch zoom
event.preventDefault();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest removing disableTouchZoom from enterOverlayMode (a new PR might be better, maybe an issue to do it later).

Reasoning is:
1- the old disableTouchZoom don't work on iOS anymore
2- Disabling touchzoom for every overlay is not the right thing to do. It only makes sense for image viewer and similar cases not every lightbox.

Copy link
Contributor Author

@cathyxz cathyxz Nov 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #12254 as a separate PR.

onTapZoom_(centerClientX, centerClientY, deltaX, deltaY) {
const dir = Math.abs(deltaY) > Math.abs(deltaX) ?
Math.sign(deltaY) : Math.sign(-deltaX);
this.onZoomInc_(centerClientX, centerClientY, deltaX, deltaY, dir);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made me think of Monster Inc :). Mayeb a different name? performZoom, applyZoom?

@cathyxz
Copy link
Contributor Author

cathyxz commented Nov 29, 2017

@cvializ @aghassemi PTAL at your leisure. =)

Copy link
Contributor

@cvializ cvializ left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!!

@cvializ cvializ merged commit f7d7a03 into ampproject:master Dec 1, 2017
@cathyxz cathyxz deleted the feature/lightbox-pinch-zoom branch December 1, 2017 22:06
ghost pushed a commit that referenced this pull request Dec 6, 2017
* Add basic pinch zoom

* Correct if / else chain in GestureRecognizers for clarity

* Reindent comments and rename onZoomInc to zoomToPoint

* Pull out threshold constants
gzgogo pushed a commit to gzgogo/amphtml that referenced this pull request Jan 26, 2018
* Add basic pinch zoom

* Correct if / else chain in GestureRecognizers for clarity

* Reindent comments and rename onZoomInc to zoomToPoint

* Pull out threshold constants
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants