catstodon/app/javascript/mastodon/features/ui/components/zoomable_image.js
Emelia Smith 7a81082704 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.
2018-04-04 11:58:15 +02:00

151 lines
3.8 KiB
JavaScript

import React from 'react';
import PropTypes from 'prop-types';
const MIN_SCALE = 1;
const MAX_SCALE = 4;
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));
export default class ZoomableImage extends React.PureComponent {
static propTypes = {
alt: PropTypes.string,
src: PropTypes.string.isRequired,
width: PropTypes.number,
height: PropTypes.number,
onClick: PropTypes.func,
}
static defaultProps = {
alt: '',
width: null,
height: null,
};
state = {
scale: MIN_SCALE,
}
removers = [];
container = null;
image = null;
lastTouchEndTime = 0;
lastDistance = 0;
componentDidMount () {
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();
}
removeEventListeners () {
this.removers.forEach(listeners => listeners());
this.removers = [];
}
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();
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;
}
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;
});
}
handleClick = e => {
// don't propagate event to MediaModal
e.stopPropagation();
const handler = this.props.onClick;
if (handler) handler();
}
setContainerRef = c => {
this.container = c;
}
setImageRef = c => {
this.image = c;
}
render () {
const { alt, src } = this.props;
const { scale } = this.state;
const overflow = scale === 1 ? 'hidden' : 'scroll';
return (
<div
className='zoomable-image'
ref={this.setContainerRef}
style={{ overflow }}
>
<img
role='presentation'
ref={this.setImageRef}
alt={alt}
src={src}
style={{
transform: `scale(${scale})`,
transformOrigin: '0 0',
}}
onClick={this.handleClick}
/>
</div>
);
}
}