One idea I had when I first got into the front-end business was to write a really cool gallery of preview images. I still remember using Meitu at that time, the brisk speed and interaction was so fascinating that I wanted to write one myself.
This component was released a few years ago in an incomplete version, and has been maintained intermittently since, always feeling a little off. This year’s Spring Festival did not rest, all built on the above development, now it is finally perfect! First look at the results:
Preview: react-photo-view.vercel.app/
Perfect thumbnail gradient:
Zoom in at specified location:
Slow rolling:
What is thereact-photo-view
React-photo-view offers an unrivalled interactive preview experience: from the moment the image is opened, every frame of animation, detail, and interaction is carefully designed and tweaked to match the effects of a native image preview.
pnpm i react-photo-view
Copy the code
The issue:
import { PhotoProvider, PhotoView } from 'react-photo-view';
import 'react-photo-view/dist/react-photo-view.css';
export default function MyComponent() {
return (
<PhotoProvider>
<PhotoView src="/1.jpg">
<img src="/1-thumbnail.jpg" alt="" />
</PhotoView>
</PhotoProvider>
);
}
Copy the code
Why develop it separately?
Of course, the obsession with implementing it was part of the equation, but the root cause was that there was no easy way to preview images in the React ecosystem. At that time, I was a copyist. I searched the React zoom-based preview component library on the Internet. The result surprised me a little. What makes it even more suffocating is that most of the component libraries are repackaged based on PhotoSwipe, an open source library. In addition, a library of preview components that can be used in actual production… There doesn’t seem to be one (or maybe I can’t find one), and this is true not only for the React library, but also for other frameworks like Vue and even native libraries.
PhotoSwipe wasn’t out of place, but the native DOM format didn’t work in React and was a bit too bulky for gzip’s 12KB size, so the idea came up.
How good is it?
It has perfect details and features:
- Support touch gestures, drag/pan/physical effect sliding, double finger specified position zoom in/out
- Full animation connection, open/close/bounce/touch, natural interaction
- The image ADAPTS to a suitable initial rendering size and ADAPTS according to adjustments
- Customizations such as
<video />
Or anyHTML
Preview of elements - Keyboard navigation, perfect for desktop
- Support custom node expansion, easy to achieve full-screen preview, rotation control, picture introduction and more
- Based on the
typescript
.7KB Gzipped
To support server-side rendering - Easy to use
API
Zero cost to get started
Also exported to support ES2017 above JS, can do 6KB Gzipped. It’s not easy to add a lot of experience detail to that size, but more functionality can be achieved with very easy custom rendering, which fits perfectly with the React concept and eliminates the need to build in unnecessary features.
Popular library comparison
The following table shows the functionality required for most scenes, showing the contrast between React-Photo-view, PhotoSwipe and RC-image (ant-Design) :
react-photo-view | PhotoSwipe | rc-image | |
---|---|---|---|
MINIFIED | 19KB | 47KB | 40KB |
MINIFIED + GZIPPED | 7.3 KB | 12KB | 14KB |
Based on the preview | support | support | support |
Switch to preview | support | support | Does not support |
The mobile terminal | support | support | Does not support |
Perfect gradient for thumbnails | support | support | Does not support |
Thumbnail crop animation | support | Supported (manually specified) | Does not support |
Adaptive image size | support | Not supported (Manually specified) | support |
fallback | support | Does not support | support |
Mouse wheel zoom | support | Does not support | (Lack of location) |
Physical rolling of spring | support | support | Does not support |
Animation parameter adjustment | support | support | Does not support |
Easy to use API | support | Does not support | support |
TypeScript | support | Does not support | support |
Keyboard navigation | support | support | support |
Custom elements | support | XSS risk exists | Does not support |
The controlled components | support | support | support |
Loop preview | support | support | Does not support |
Image rotation | support | Does not support | support |
Customize toolbars | support | support | Does not support |
Native full screen open | Custom extension | support | Does not support |
Friendly documentation
What is more important than the document, for this purpose, I have prepared a very beautiful document (currently only in Chinese, I will have time to translate it later).
react-photo-view.vercel.app/
Implementation process
Pictures scroll with your finger
Record the current trigger position state when onTouchStart, let it follow the finger when onTouchMove, onTouchEnd unfollow can be easily achieved.
Edge position feedback makes image switching a matter of painstaking detail: moving after onTouchStart and immediately following the image with your finger can lead to a lot of misoperations, such as swiping up and down in order to switch images. A 20px movement buffer is needed to predict finger movement.
Specifies the image location to enlarge
Transform: Scale (value) can be used to scale the image, but the center of the image is enlarged, the result of scaling may not be desired. The original idea was to use transform-Origin, but the idea was nice, although the first time it was possible to zoom in at the specified location. If you zoom in on something other than the original position, you get jumpy, which is obviously not going to work.
Later, I could not sleep, and found inspiration in my sleep: for easy calculation and understanding, we set the center point of the picture as 0, and change the position of the center of the picture by zooming in and out of any specified position. For example, the width of the image is 200, the center position is 100, and the image is doubled based on the leftmost position. Now the image width is 400, so the center position should be 200. So the summary formula is as follows:
const centerClientX = innerWidth / 2;
// Coordinate offset conversion
const lastPositionX = centerClientX + lastX;
// Zoom offset
const offsetScale = nextScale / scale;
// Final offset position
const originX = clientX - (clientX - lastPositionX) * offsetScale - centerClientX;
Copy the code
This mode calculation can handle a variety of position responses, such as two-finger zoom, two-finger scroll + zoom, edge calculation, and so on.
The distance between the fingers
Here is the Pythagorean theorem needed in junior high:
Math.sqrt((nextClientX - clientX) ** 2 + (nextClientY - clientY) ** 2);
Copy the code
Simulated rolling operation
The previous version used Transition, which calculated the initial speed by sliding the time difference between the start and end of the finger, and estimated the distance simulated by Transition to make the eye look like it was rolling 😂. But the experience is still far worse. Then, the rolling effect is simulated by combining high school physics formula:
Accelerated motion:
Air resistance:
CρS is a constant, so let’s make it a constant. As for how to get this amount… So 😂 is only proportional to v squared.
And because of the opposite direction of motion, take the direction of v math.sign (-v)
function scrollMove(
initialSpeed: number,
callback: (spatial: number) = >boolean.) {
/ / acceleration
const acceleration = -0.002;
/ / resistance
const resistance = 0.0002;
let v = initialSpeed;
let s = 0;
let lastTime: number | undefined = undefined;
let frameId = 0;
const calcMove = (now: number) = > {
if(! lastTime) { lastTime = now; }const dt = now - lastTime;
const direction = Math.sign(initialSpeed);
const a = direction * acceleration;
const f = Math.sign(-v) * v ** 2 * resistance;
const ds = v * dt + ((a + f) * dt ** 2) / 2;
v = v + (a + f) * dt;
s = s + ds;
// move to s
lastTime = now;
if (direction * v <= 0) {
cancelAnimationFrame(frameId);
return;
}
if (callback(s)) {
frameId = requestAnimationFrame(calcMove);
return;
}
cancelAnimationFrame(frameId);
};
frameId = requestAnimationFrame(calcMove);
}
Copy the code
Cut the thumbnail
PhotoSwipe supports thumbnail clipping, but requires manually specifying the image width and height and data-cropped, which can be quite cumbersome. React-photo-view gets the current crop parameters by reading the thumbnail getComputedStyle(Element).objectFit. Realize automatic cutting effect.
Compatibility processing
Because each image is a composite layer, this can consume quite a bit of memory. IOS has a fairly large memory limit, and if you use scale all the way up, it will look very blurry on Safari. Now by changing the width and height of the image to the specified value each time after the motion is complete, and then resetting the scale to 1, this method should achieve the desired effect.
other
PhotoSwipe, a Ukrainian who lives in Kiev, fled the city and is now safe in western Ukraine with his family, hoping to get back on his feet once the war is over.
conclusion
I spent a lot of energy on the details of react-photo-view. If you like it, please click a Star to support me. Thank you!
- Making: github.com/MinJieLiu/r…
- Gitee: gitee.com/MinJieLiu/r…
Articles exported this year
- Vite is the best practice of reductive routing, a cool way to implement it
- Vite plug-in development practices: resource handling of microfront-end
- Vite micro front-end practice, to achieve a componentized scheme
- JS immutable data trams, immer is not the ultimate way out, high-performance scenarios need to be implemented by themselves