New feature experiment: pinch-zoom for images

This will probably be very buggy
This commit is contained in:
Lim Chee Aun 2023-03-28 00:29:01 +08:00
parent c1bf95d1eb
commit 8908359b50
5 changed files with 89 additions and 22 deletions

28
package-lock.json generated
View file

@ -26,6 +26,7 @@
"preact": "~10.13.1", "preact": "~10.13.1",
"react-hotkeys-hook": "~4.3.8", "react-hotkeys-hook": "~4.3.8",
"react-intersection-observer": "~9.4.3", "react-intersection-observer": "~9.4.3",
"react-quick-pinch-zoom": "~4.6.0",
"react-router-dom": "6.6.2", "react-router-dom": "6.6.2",
"string-length": "~5.0.1", "string-length": "~5.0.1",
"swiped-events": "~1.1.7", "swiped-events": "~1.1.7",
@ -5814,6 +5815,27 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"node_modules/react-quick-pinch-zoom": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/react-quick-pinch-zoom/-/react-quick-pinch-zoom-4.6.0.tgz",
"integrity": "sha512-M3woYVzWt8Kh6FCAytBtJJ4KC/5noG98GpI8ZTxTIE2sjR1XRPjV0NpFRhFgxPQpDvD+lkMp63sxP130uhafaw==",
"peerDependencies": {
"react": ">=16.4.0",
"react-dom": ">=16.4.0",
"tslib": ">=2.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-dom": {
"optional": true
},
"tslib": {
"optional": true
}
}
},
"node_modules/react-router": { "node_modules/react-router": {
"version": "6.6.2", "version": "6.6.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.6.2.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.6.2.tgz",
@ -11072,6 +11094,12 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"react-quick-pinch-zoom": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/react-quick-pinch-zoom/-/react-quick-pinch-zoom-4.6.0.tgz",
"integrity": "sha512-M3woYVzWt8Kh6FCAytBtJJ4KC/5noG98GpI8ZTxTIE2sjR1XRPjV0NpFRhFgxPQpDvD+lkMp63sxP130uhafaw==",
"requires": {}
},
"react-router": { "react-router": {
"version": "6.6.2", "version": "6.6.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.6.2.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.6.2.tgz",

View file

@ -28,6 +28,7 @@
"preact": "~10.13.1", "preact": "~10.13.1",
"react-hotkeys-hook": "~4.3.8", "react-hotkeys-hook": "~4.3.8",
"react-intersection-observer": "~9.4.3", "react-intersection-observer": "~9.4.3",
"react-quick-pinch-zoom": "~4.6.0",
"react-router-dom": "6.6.2", "react-router-dom": "6.6.2",
"string-length": "~5.0.1", "string-length": "~5.0.1",
"swiped-events": "~1.1.7", "swiped-events": "~1.1.7",

View file

@ -93,7 +93,8 @@ function MediaModal({
onClick={(e) => { onClick={(e) => {
if ( if (
e.target.classList.contains('carousel-item') || e.target.classList.contains('carousel-item') ||
e.target.classList.contains('media') e.target.classList.contains('media') ||
e.target.classList.contains('media-zoom')
) { ) {
onClose(); onClose();
} }

View file

@ -1,5 +1,6 @@
import { getBlurHashAverageColor } from 'fast-blurhash'; import { getBlurHashAverageColor } from 'fast-blurhash';
import { useRef } from 'preact/hooks'; import { useCallback, useRef, useState } from 'preact/hooks';
import QuickPinchZoom, { make3dTransformValue } from 'react-quick-pinch-zoom';
import Icon from './icon'; import Icon from './icon';
import { formatDuration } from './status'; import { formatDuration } from './status';
@ -39,6 +40,19 @@ function Media({ media, showOriginal, autoAnimate, onClick = () => {} }) {
focalBackgroundPosition = `${x.toFixed(0)}% ${y.toFixed(0)}%`; focalBackgroundPosition = `${x.toFixed(0)}% ${y.toFixed(0)}%`;
} }
const imgRef = useRef();
const onUpdate = useCallback(({ x, y, scale }) => {
const { current: img } = imgRef;
if (img) {
const value = make3dTransformValue({ x, y, scale });
img.style.setProperty('transform', value);
}
}, []);
const [imageLoaded, setImageLoaded] = useState(false);
if (type === 'image' || (type === 'unknown' && previewUrl && url)) { if (type === 'image' || (type === 'unknown' && previewUrl && url)) {
// Note: type: unknown might not have width/height // Note: type: unknown might not have width/height
return ( return (
@ -46,33 +60,55 @@ function Media({ media, showOriginal, autoAnimate, onClick = () => {} }) {
class={`media media-image`} class={`media media-image`}
onClick={onClick} onClick={onClick}
style={ style={
showOriginal && { showOriginal &&
!imageLoaded && {
backgroundImage: `url(${previewUrl})`, backgroundImage: `url(${previewUrl})`,
} }
} }
> >
{showOriginal ? (
<QuickPinchZoom
enabled={imageLoaded}
draggableUnZoomed={false}
inertiaFriction={0.9}
containerProps={{
className: 'media-zoom',
style: {
width: 'inherit',
height: 'inherit',
},
}}
onUpdate={onUpdate}
>
<img
ref={imgRef}
src={mediaURL}
alt={description}
width={width}
height={height}
loading="eager"
onLoad={(e) => {
setImageLoaded(true);
}}
/>
</QuickPinchZoom>
) : (
<img <img
src={mediaURL} src={mediaURL}
alt={description} alt={description}
width={width} width={width}
height={height} height={height}
loading={showOriginal ? 'eager' : 'lazy'} loading="lazy"
style={ style={{
!showOriginal && {
backgroundColor: backgroundColor:
rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`, rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`,
backgroundPosition: focalBackgroundPosition || 'center', backgroundPosition: focalBackgroundPosition || 'center',
}
}
onDblClick={() => {
// Open original image in new tab
window.open(url, '_blank');
}} }}
onLoad={(e) => { onLoad={(e) => {
// Hide background image after image loads setImageLoaded(true);
e.target.parentElement.style.backgroundImage = 'none';
}} }}
/> />
)}
</div> </div>
); );
} else if (type === 'gifv' || type === 'video') { } else if (type === 'gifv' || type === 'video') {

View file

@ -668,6 +668,7 @@ body:has(#modal-container .carousel) .status .media img:hover {
align-items: center; align-items: center;
gap: 8px; gap: 8px;
font-size: 90%; font-size: 90%;
z-index: 1;
} }
.carousel-item button.media-alt .media-alt-desc { .carousel-item button.media-alt .media-alt-desc {
overflow: hidden; overflow: hidden;