Commit 7a810827 authored by Emelia Smith's avatar Emelia Smith Committed by Eugen Rochko

Revert "Add double-tap zoom functionary to `ZoomableImage` (#6944)" (#7035)

Unfortunately the new hammer.js functionality wasn't correctly tested and didn't work across devices and browsers, as such, it's best to revert PR #6944 until we can revisit this functionality and make it work across all devices and browsers that are supported by Mastodon.

This reverts commit 5021c4e9.
parent 07176fed
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Hammer from 'hammerjs';
const MIN_SCALE = 1; const MIN_SCALE = 1;
const MAX_SCALE = 4; const MAX_SCALE = 4;
const DOUBLE_TAP_SCALE = 2;
const getMidpoint = (p1, p2) => ({
x: (p1.clientX + p2.clientX) / 2,
y: (p1.clientY + p2.clientY) / 2,
});
const getDistance = (p1, p2) =>
Math.sqrt(Math.pow(p1.clientX - p2.clientX, 2) + Math.pow(p1.clientY - p2.clientY, 2));
const clamp = (min, max, value) => Math.min(max, Math.max(min, value)); const clamp = (min, max, value) => Math.min(max, Math.max(min, value));
...@@ -31,95 +37,81 @@ export default class ZoomableImage extends React.PureComponent { ...@@ -31,95 +37,81 @@ export default class ZoomableImage extends React.PureComponent {
removers = []; removers = [];
container = null; container = null;
image = null; image = null;
lastScale = null; lastTouchEndTime = 0;
zoomCenter = null; lastDistance = 0;
componentDidMount () { componentDidMount () {
// register pinch event handlers to the container let handler = this.handleTouchStart;
let hammer = new Hammer.Manager(this.container, { this.container.addEventListener('touchstart', handler);
// required to make container scrollable by touch this.removers.push(() => this.container.removeEventListener('touchstart', handler));
touchAction: 'pan-x pan-y', handler = this.handleTouchMove;
}); // on Chrome 56+, touch event listeners will default to passive
hammer.add(new Hammer.Pinch()); // https://www.chromestatus.com/features/5093566007214080
hammer.on('pinchstart', this.handlePinchStart); this.container.addEventListener('touchmove', handler, { passive: false });
hammer.on('pinchmove', this.handlePinchMove); this.removers.push(() => this.container.removeEventListener('touchend', handler));
this.removers.push(() => hammer.off('pinchstart pinchmove'));
// register tap event handlers
hammer = new Hammer.Manager(this.image);
// NOTE the order of adding is also the order of gesture recognition
hammer.add(new Hammer.Tap({ event: 'doubletap', taps: 2 }));
hammer.add(new Hammer.Tap());
// prevent the 'tap' event handler be fired on double tap
hammer.get('tap').requireFailure('doubletap');
// NOTE 'tap' and 'doubletap' events are fired by touch and *mouse*
hammer.on('tap', this.handleTap);
hammer.on('doubletap', this.handleDoubleTap);
this.removers.push(() => hammer.off('tap doubletap'));
} }
componentWillUnmount () { componentWillUnmount () {
this.removeEventListeners(); this.removeEventListeners();
} }
componentDidUpdate (prevProps, prevState) {
if (!this.zoomCenter) return;
const { x: cx, y: cy } = this.zoomCenter;
const { scale: prevScale } = prevState;
const { scale: nextScale } = this.state;
const { scrollLeft, scrollTop } = this.container;
// math memo:
// x = (scrollLeft + cx) / scrollWidth
// x' = (nextScrollLeft + cx) / nextScrollWidth
// scrollWidth = clientWidth * prevScale
// scrollWidth' = clientWidth * nextScale
// Solve x = x' for nextScrollLeft
const nextScrollLeft = (scrollLeft + cx) * nextScale / prevScale - cx;
const nextScrollTop = (scrollTop + cy) * nextScale / prevScale - cy;
this.container.scrollLeft = nextScrollLeft;
this.container.scrollTop = nextScrollTop;
}
removeEventListeners () { removeEventListeners () {
this.removers.forEach(listeners => listeners()); this.removers.forEach(listeners => listeners());
this.removers = []; this.removers = [];
} }
handleClick = e => { handleTouchStart = e => {
// prevent the click event propagated to parent if (e.touches.length !== 2) return;
e.stopPropagation();
// the tap event handler is executed at the same time by touch and mouse, this.lastDistance = getDistance(...e.touches);
// so we don't need to execute the onClick handler here
} }
handlePinchStart = () => { handleTouchMove = e => {
this.lastScale = this.state.scale; const { scrollTop, scrollHeight, clientHeight } = this.container;
} if (e.touches.length === 1 && scrollTop !== scrollHeight - clientHeight) {
// prevent propagating event to MediaModal
e.stopPropagation();
return;
}
if (e.touches.length !== 2) return;
handlePinchMove = e => { e.preventDefault();
const scale = clamp(MIN_SCALE, MAX_SCALE, this.lastScale * e.scale); e.stopPropagation();
this.zoom(scale, e.center);
}
handleTap = () => { const distance = getDistance(...e.touches);
const handler = this.props.onClick; const midpoint = getMidpoint(...e.touches);
if (handler) handler(); const scale = clamp(MIN_SCALE, MAX_SCALE, this.state.scale * distance / this.lastDistance);
this.zoom(scale, midpoint);
this.lastMidpoint = midpoint;
this.lastDistance = distance;
} }
handleDoubleTap = e => { zoom(nextScale, midpoint) {
if (this.state.scale === MIN_SCALE) const { scale } = this.state;
this.zoom(DOUBLE_TAP_SCALE, e.center); const { scrollLeft, scrollTop } = this.container;
else
this.zoom(MIN_SCALE, e.center); // math memo:
// x = (scrollLeft + midpoint.x) / scrollWidth
// x' = (nextScrollLeft + midpoint.x) / nextScrollWidth
// scrollWidth = clientWidth * scale
// scrollWidth' = clientWidth * nextScale
// Solve x = x' for nextScrollLeft
const nextScrollLeft = (scrollLeft + midpoint.x) * nextScale / scale - midpoint.x;
const nextScrollTop = (scrollTop + midpoint.y) * nextScale / scale - midpoint.y;
this.setState({ scale: nextScale }, () => {
this.container.scrollLeft = nextScrollLeft;
this.container.scrollTop = nextScrollTop;
});
} }
zoom (scale, center) { handleClick = e => {
this.zoomCenter = center; // don't propagate event to MediaModal
this.setState({ scale }); e.stopPropagation();
const handler = this.props.onClick;
if (handler) handler();
} }
setContainerRef = c => { setContainerRef = c => {
...@@ -134,18 +126,6 @@ export default class ZoomableImage extends React.PureComponent { ...@@ -134,18 +126,6 @@ export default class ZoomableImage extends React.PureComponent {
const { alt, src } = this.props; const { alt, src } = this.props;
const { scale } = this.state; const { scale } = this.state;
const overflow = scale === 1 ? 'hidden' : 'scroll'; const overflow = scale === 1 ? 'hidden' : 'scroll';
const marginStyle = {
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transform: `scale(${scale})`,
transformOrigin: '0 0',
};
return ( return (
<div <div
...@@ -153,18 +133,17 @@ export default class ZoomableImage extends React.PureComponent { ...@@ -153,18 +133,17 @@ export default class ZoomableImage extends React.PureComponent {
ref={this.setContainerRef} ref={this.setContainerRef}
style={{ overflow }} style={{ overflow }}
> >
<div <img
className='zoomable-image__margin' role='presentation'
style={marginStyle} ref={this.setImageRef}
> alt={alt}
<img src={src}
ref={this.setImageRef} style={{
role='presentation' transform: `scale(${scale})`,
alt={alt} transformOrigin: '0 0',
src={src} }}
onClick={this.handleClick} onClick={this.handleClick}
/> />
</div>
</div> </div>
); );
} }
......
...@@ -1483,6 +1483,9 @@ ...@@ -1483,6 +1483,9 @@
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex;
align-items: center;
justify-content: center;
img { img {
max-width: $media-modal-media-max-width; max-width: $media-modal-media-max-width;
......
...@@ -3096,10 +3096,6 @@ gzip-size@^3.0.0: ...@@ -3096,10 +3096,6 @@ gzip-size@^3.0.0:
dependencies: dependencies:
duplexer "^0.1.1" duplexer "^0.1.1"
hammerjs@^2.0.8:
version "2.0.8"
resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1"
handle-thing@^1.2.5: handle-thing@^1.2.5:
version "1.2.5" version "1.2.5"
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment