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

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  until we can revisit this functionality and make it work across all devices and browsers that are supported by Mastodon.

This reverts commit 5021c4e9ca.
signup-info-prompt
Emelia Smith 2018-04-04 11:58:15 +02:00 committed by Eugen Rochko
parent 07176fed37
commit 7a81082704
4 changed files with 76 additions and 99 deletions
app/javascript
mastodon/features/ui/components
styles/mastodon

View File

@ -1,10 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import Hammer from 'hammerjs';
const MIN_SCALE = 1;
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));
@ -31,97 +37,83 @@ export default class ZoomableImage extends React.PureComponent {
removers = [];
container = null;
image = null;
lastScale = null;
zoomCenter = null;
lastTouchEndTime = 0;
lastDistance = 0;
componentDidMount () {
// register pinch event handlers to the container
let hammer = new Hammer.Manager(this.container, {
// required to make container scrollable by touch
touchAction: 'pan-x pan-y',
});
hammer.add(new Hammer.Pinch());
hammer.on('pinchstart', this.handlePinchStart);
hammer.on('pinchmove', this.handlePinchMove);
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'));
let handler = this.handleTouchStart;
this.container.addEventListener('touchstart', handler);
this.removers.push(() => this.container.removeEventListener('touchstart', handler));
handler = this.handleTouchMove;
// on Chrome 56+, touch event listeners will default to passive
// https://www.chromestatus.com/features/5093566007214080
this.container.addEventListener('touchmove', handler, { passive: false });
this.removers.push(() => this.container.removeEventListener('touchend', handler));
}
componentWillUnmount () {
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 () {
this.removers.forEach(listeners => listeners());
this.removers = [];
}
handleClick = e => {
// prevent the click event propagated to parent
handleTouchStart = e => {
if (e.touches.length !== 2) return;
this.lastDistance = getDistance(...e.touches);
}
handleTouchMove = e => {
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;
e.preventDefault();
e.stopPropagation();
// the tap event handler is executed at the same time by touch and mouse,
// so we don't need to execute the onClick handler here
const distance = getDistance(...e.touches);
const midpoint = getMidpoint(...e.touches);
const scale = clamp(MIN_SCALE, MAX_SCALE, this.state.scale * distance / this.lastDistance);
this.zoom(scale, midpoint);
this.lastMidpoint = midpoint;
this.lastDistance = distance;
}
handlePinchStart = () => {
this.lastScale = this.state.scale;
zoom(nextScale, midpoint) {
const { scale } = this.state;
const { scrollLeft, scrollTop } = this.container;
// 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;
});
}
handlePinchMove = e => {
const scale = clamp(MIN_SCALE, MAX_SCALE, this.lastScale * e.scale);
this.zoom(scale, e.center);
}
handleTap = () => {
handleClick = e => {
// don't propagate event to MediaModal
e.stopPropagation();
const handler = this.props.onClick;
if (handler) handler();
}
handleDoubleTap = e => {
if (this.state.scale === MIN_SCALE)
this.zoom(DOUBLE_TAP_SCALE, e.center);
else
this.zoom(MIN_SCALE, e.center);
}
zoom (scale, center) {
this.zoomCenter = center;
this.setState({ scale });
}
setContainerRef = c => {
this.container = c;
}
@ -134,18 +126,6 @@ export default class ZoomableImage extends React.PureComponent {
const { alt, src } = this.props;
const { scale } = this.state;
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 (
<div
@ -153,18 +133,17 @@ export default class ZoomableImage extends React.PureComponent {
ref={this.setContainerRef}
style={{ overflow }}
>
<div
className='zoomable-image__margin'
style={marginStyle}
>
<img
ref={this.setImageRef}
role='presentation'
alt={alt}
src={src}
onClick={this.handleClick}
/>
</div>
<img
role='presentation'
ref={this.setImageRef}
alt={alt}
src={src}
style={{
transform: `scale(${scale})`,
transformOrigin: '0 0',
}}
onClick={this.handleClick}
/>
</div>
);
}

View File

@ -1483,6 +1483,9 @@
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
img {
max-width: $media-modal-media-max-width;

View File

@ -54,7 +54,6 @@
"file-loader": "^0.11.2",
"font-awesome": "^4.7.0",
"glob": "^7.1.1",
"hammerjs": "^2.0.8",
"http-link-header": "^0.8.0",
"immutable": "^3.8.2",
"imports-loader": "^0.8.0",

View File

@ -3096,10 +3096,6 @@ gzip-size@^3.0.0:
dependencies:
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:
version "1.2.5"
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"