7
头图

When I first came into the front-end industry, I had an idea, and that was to write a cool gallery of image previews. I still remember taking a look at Meitu at that time, the brisk speed and interaction are very fascinating.

The component has been released an incomplete version a few years ago, and the intermittent maintenance afterwards always feels that something is wrong. There is no rest in the Spring Festival this year, and all the development is carried out on it. Now it is finally realized! First look at the effect:

Thumbnail perfect gradient:

1.gif

Zoom in at a specified location:

2.gif

Slow down scrolling:

3.gif

what is react-photo-view

react-photo-view has an unparalleled interactive preview experience: starting from opening the image, the animation, details and interaction of each frame have been carefully designed and repeatedly debugged, 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 ecology of React , there is simply no easy-to-use picture preview solution. At that time, I followed the principle of use, and I found a circle based on the React zoom preview component library 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 open source library PhotoSwipe . In addition, there is no 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, but also in other frameworks Vue and even native related libraries. .

Of course, PhotoSwipe is not unusable, but the writing method of 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/pan/slide with physical effects, two-finger specified position zoom in/out
  • All aspects of animation connection, open / close / 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 custom preview of elements such as <video /> or any HTML 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 , support server-side rendering
  • Simple and easy to use API , zero cost to get started

It also exported JS that supports ES2017 and above, and can do 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 perfectly fits the concept of React , so that some non-rigorous functions can be avoided.

Comparison of popular libraries

The following table summarizes the functions required for most scenarios, showing the comparison of react-photo-view , PhotoSwipe and rc-image (ant-design):

react-photo-viewPhotoSwiperc-image
MINIFIED19KB47KB40KB
MINIFIED + GZIPPED7.3KB12KB14KB
Basic previewsupportsupportsupport
toggle previewsupportsupportnot support
mobilesupportsupportnot support
Thumbnail perfect gradientsupportsupportnot support
Thumbnail crop animationsupportSupport (need to specify manually)not support
Adaptive Image SizesupportNot supported (must be specified manually)support
fallbacksupportnot supportsupport
Mouse wheel zoomsupportnot support(missing location)
spring physical rollsupportsupportnot support
Animation parameter adjustmentsupportsupportnot support
Easy-to-use APIsupportnot supportsupport
TypeScriptsupportnot supportsupport
keyboard navigationsupportsupportsupport
custom elementsupportRisk of XSSnot support
Controlled Componentssupportsupportsupport
Loop previewsupportsupportnot support
Image rotationsupportnot supportsupport
custom toolbarsupportsupportnot support
Native full screen opencustom extensionsupportnot support

friendly documentation

What is more important than the document, for this, I also prepared a super beautiful document (currently only in Chinese, I have time to translate it in the future~)

https://react-photo-view.vercel.app/

4.png

Realization process

Image scrolls with your finger

Record the current trigger position state at onTouchStart , let it follow the finger movement at onTouchEnd , and release the following at onTouchMove .

The touch position feedback makes the picture switching need to slowly ponder the details: moving after onTouchStart , if the picture moves with the finger immediately, it will bring many misoperations, such as the logic of sliding up and down when he wants to switch the picture. At this time, a movement buffer of 20px is needed to predict the movement direction of the finger.

Specify image location to zoom in

Using transform: scale(value) can realize the zooming of the picture, but the center of the picture is enlarged, and the result of the zooming may not be what you want. At first, I planned to use transform-origin to realize it. The idea is good, although it is the first time to be able to zoom in at the designated 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 the calculation and understanding, we set the center point of the picture to be 0 , and to zoom in and out of any specified position, that is, to change 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;
// 坐标偏移转换
const lastPositionX = centerClientX + lastX;
// 缩放偏移
const offsetScale = nextScale / scale;
// 最终偏移位置
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

Here we need the right triangle Pythagorean theorem in junior high school:

Math.sqrt((nextClientX - clientX) ** 2 + (nextClientY - clientY) ** 2);

Simulate scrolling

The previous version was implemented with transition . The initial speed was calculated by the time difference between the start and end of the finger sliding, and it was estimated that transition was used to simulate a distance to make the eyes seem to have a rolling effect. But this way the experience is always much worse. Later, combined with the high school physics formula, the rolling effect is simulated:

5.png

Accelerated movement:

6.png

air resistance:

7.png

CρS are all constants, so just make them into one 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 movement direction, 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 crop parameters by reading the thumbnail getComputedStyle(element).objectFit . Realize automatic cropping effect.

4.gif

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 picture to the specified value every time after the movement is completed, and then resetting scale to 1, this method should achieve the effect it needs to achieve.

other

The author of PhotoSwipe is a Ukrainian living in Kiev who escaped from Kiev 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 energy on the details of react-photo-view , if you like it, you can help me by Star

https://github.com/MinJieLiu/react-photo-view

Thanks!


MinJieLiu
107 声望4 粉丝

前端工程师,开源爱好者