When I first came into the front-end industry, I had an idea, and that was to write a super cool image preview gallery.
The component has been released an incomplete version a few years ago, and after intermittent maintenance, it always feels that something is wrong. There is no rest this year, and all the development is carried out on it. Now it is finally realized! First look at the effect:
Thumbnail perfect gradient:
Zoom in at a specified location:
Slow down scrolling:
what is react-photo-view
react-photo-view
has an unparalleled preview interaction experience: starting from the opening of the image, the animation, details and interaction of each frame have been carefully designed and repeatedly debugged, which is comparable to the effect of native image preview.
pnpm i react-photo-view
Overview:
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>
);
}
Why develop it separately?
Of course, the obsession to realize it is also an aspect, but the fundamental reason is that in the powerful ecosystem of React
, there is simply no easy-to-use image preview solution. At that time, I followed the principle of use, and I found a circle of React
-based zoom preview component libraries on the Internet. The result surprised me a bit. The number of image zoom preview libraries is obviously not comparable to the carousel component library. What is even more suffocating is that most of these meager component libraries are secondary encapsulation based on the PhotoSwipe
open source library. In addition, the preview component library that can be used in actual production... It seems that there is no (or maybe I can't find it), this situation is not only reflected in the React
library, other frameworks Vue
and even native related Libraries are like that.
Of course, PhotoSwipe
is not unusable, but the native operation DOM
is out of place in React
, and its volume is also above gzip 12KB
, which is a bit bloated, so I have this bold idea.
How good is it?
It has very perfect details and features:
- Support touch gestures, drag and pan physical effect sliding, two-finger specified position to zoom in and out
- All aspects of animation connection, open and close the rebound touch edge, let the natural interaction effect
- The image is adaptive, with a suitable initial rendering size, and adapts according to the adjustment
- Support for custom previews like
<video>
or anyHTML
element - Keyboard navigation, perfect for desktop
- Support custom node expansion, easy to achieve full-screen preview, rotation control, picture introduction and more functions
- Based on
typescript
,7KB Gzipped
, supports server-side rendering - Simple and easy to use
API
, zero cost to get started
It also exports JS
that supports ES2017
and above, and can achieve 6KB Gzipped
. It is not easy to add a lot of experience details to such a volume. More functions can be achieved through very easy custom rendering, which is perfectly in line with the React
concept, thus avoiding built-in functions that are not rigidly needed. .
Comparison of popular libraries
The following table summarizes the functions required for most scenarios, showing a comparison of react-photo-view
, PhotoSwipe
and rc-image
(ant-design):
react-photo-view | PhotoSwipe | rc-image | |
---|---|---|---|
MINIFIED | 19KB | 47KB | 40KB |
MINIFIED + GZIPPED | 7.3KB | 12KB | 14KB |
Basic preview | Support | Support | Support |
Toggle preview | Support | Support | Not support |
Mobile | Support | Support | Not support |
Thumbnail perfect gradient | Support | Support | Not support |
Thumbnail crop animation | Support | Support(need to be specified manually) | Not support |
Adaptive Image Size | Support | Not support(need to be specified manually) | Support |
fallback | Support | Not support | Support |
Mouse wheel zoom | Support | Not support | (missing location) |
Spring physical roll | Support | Support | Not support |
Animation parameter adjustment | Support | Support | Not support |
Easy-to-use API | Support | Not support | Support |
TypeScript | Support | Not support | Support |
Keyboard navigation | Support | Support | Support |
Custom element | Support | Risk of XSS | Not support |
controlled | Support | Support | Support |
Loop preview | Support | Support | Not support |
Rotation | Support | Not support | Support |
Custom toolbar | Support | Support | Not support |
Full screen | custom extension | Support | Not support |
Friendly documentation
What's more important than documentation, and for this, I also prepared a super beautiful document
https://react-photo-view.vercel.app/
Realization process
Image scrolls with your finger
Record the current trigger position state in onTouchStart
, let it follow the finger movement in onTouchMove
, and onTouchEnd
can be easily implemented.
The touch position feedback makes the picture switching need to slowly ponder the details: moving after onTouchStart
, if the picture follows the finger movement immediately, it will bring many misoperations, such as the logic of sliding up and down when you want to switch the picture . At this time, a 20px
movement buffer is needed to predict the direction of the finger movement.
Specify image location to zoom in
Use transform: scale(value)
to scale the image, but the center of the image is zoomed in, and the result of the scaling may not be what you want. Originally intended to use transform-origin
to achieve, the idea is good, although the first time can be zoomed in at the specified position. If the reduced position is not the original position, there will be chaotic beating. Obviously, this method will not work.
Later, I couldn't sleep after thinking about it, and found inspiration in my sleep: to facilitate calculation and understanding, we set the center point of the picture as 0
, and zooming in and out of any specified position means changing the position of the center of the picture. For example, the image width is 200
, the center point position is 100
, and it is doubled based on the leftmost position. Now the image width is 400
, then the position of the center point should be 200
. Then 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;
This mode of computing can take on various positional responses, such as pinch-to-zoom, pinch-to-scroll+zoom, edge computing, and more.
Distance between fingers
This requires the right triangle Pythagorean theorem:
Math.sqrt((nextClientX - clientX) ** 2 + (nextClientY - clientY) ** 2);
Simulate scrolling
The previous version was implemented using transition
. The initial speed was calculated by the time difference between the start and end of the finger sliding, and it was estimated that the transition
was used to simulate a distance to make the eyes look like a scrolling effect 😂. But this way the experience is always much worse. Later, combined with the high school physics formula, the rolling effect is simulated:
Accelerated movement:
Air resistance:
CρS
are all constants, so just make them a quantity. As for how to get this amount... I tried it out 😂 This is only proportional to the square of v
.
In addition, because it is opposite to the direction of motion, take the direction of v
that is Math.sign(-v)
function scrollMove(
initialSpeed: number,
callback: (spatial: number) => boolean,
) {
const acceleration = -0.002;
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);
}
Thumbnail crop
PhotoSwipe
supports thumbnail cropping, but you need to manually specify the image width and height and data-cropped
, which is quite troublesome. react-photo-view
gets the current cropping parameters by reading the thumbnail getComputedStyle(element).objectFit
. Realize automatic cropping effect.
Compatibility Handling
Since each image is a composite layer, this consumes quite a bit of memory. IOS
has a considerable memory limit, if the image is always using scale
when zoomed in, it will appear very blurry on Safari
. Now by changing the width and height of the image to the specified value every time after the movement is completed, and then resetting the scale
to 1, this method should achieve the desired effect.
Other
The author of PhotoSwipe
is a Ukrainian living in Kyiv who fled Kyiv and is now safe with his family in western Ukraine, and hopes he will bounce back after the war.
epilogue
I spent a lot of time on the details of react-photo-view
, if you like it, you can help me by clicking Star
https://github.com/MinJieLiu/react-photo-view
Thanks!
Top comments (0)