This is the first day of my participation in Gwen Challenge

antecedents

In the project, a series of images need to be previewed. After investigation, I found a React library on Github called React-Viewer, which is very easy to use and perfectly meets the requirements.

Github address: github.com/infeng/reac…

Implementation example:

The core code is as follows, with images as a list of incoming images.

class PicViewer extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      visible: false.images: [],}; } closeViewer =() = > {
    this.setState({ visible: false });
  };

  render() {
    const { visible, images } = this.state;
    return (
      <div>
        <Viewer
          visible={visible}
          onClose={this.closeViewer}
          images={images}
          downloadable
          noImgDetails
          zoomable={false}
          zoomSpeed=0.3} {
          scalable={false}
          loop={false}
          showTotal={false}
          noNavbar
          customToolbar={toolbars= >
            toolbars.filter(item => ['rotateLeft', 'rotateRight', 'download'].includes(item.key))
          }
        />
      </div>); }}Copy the code

The problem

However, it was later found that with the enrichment of business scenes, images may be very large, sometimes consisting of hundreds of images. If hundreds of images are sent to images at one time, hundreds of requests for obtaining images will be sent to the server in batches, which will cause great pressure on the server.

solution

The solution I came up with was to implement lazy loading. Although the number of images is large, I control to start with a fixed number of images being passed into the component at a time, say 10, so that at most 10 images are requested from the server.

In addition, the onchange method is used to detect changes in the current number of images as you scroll through them. If you scroll through the end of an image, update the component’s image range, slowly expanding it and controlling the number of images loaded each time.

For example, let’s say there are 20 images, numbered 1-20.

When I click on any image, I read the first 4 and the last 5 images of that image, a total of 10 images, and load them. For example, if I click on the image number 10, it will pass in the 10 images from 6 to 15 like the preview component.

When flipping through an image, the preview component’s function is triggered, and if it detects an image reaching a boundary (such as 6 or 15), an image range extension is triggered. The specific expansion mode is as follows: if it is a left subscript, the left boundary is updated 1 to the left, and the preview component picture range becomes 1-15; if it is a right subscript, the right boundary is updated 5 to the right, and the preview component picture range becomes 6-20.

This can be very smooth to achieve the preview effect, and will not cause too much waste to the server bandwidth resources.

implementation

After reviewing the documentation, the React-Viewer component provides an onChange method that will be used to monitor image movement.

Important attributes also used are:

  • ActiveIndex: The index of the currently displayed image (note that this index is the index of the list of images currently passed into the preview component, so I will notice to dynamically update this value when writing the code)
  • Images: A list of images

Directly into the code, I packaged the original Viewer into my own component, PictureViewer

import Viewer from 'react-viewer';
import 'react-photo-view/dist/index.css';
import React, { PureComponent } from 'react';

class PictureViewer extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      start: 0.// Left edge of the image
      end: 0.// Right edge of the image
      cursorIndex: 0.// Component internal pointer
      useCursorIndex: false.// Whether to use component Pointers
    };
  }

  ActiveIndex is passed in by the component calling this component
  componentWillReceiveProps(nextProps) {
    const { activeIndex, images } = nextProps;
    const { useCursorIndex } = this.state;
    if(! useCursorIndex && nextProps.visible) {if (activeIndex - 4 > 0) {
        this.setState({ start: activeIndex - 4 });
      } else {
        this.setState({ start: 0 });
      }
      if (activeIndex + 5 > images.length) {
        this.setState({ end: images.length });
      } else {
        this.setState({ end: activeIndex + 5}); }}}// Close the preview, the closeViewer function also comes from the outer component
  closeViewer = () = > {
    const { closeViewer } = this.props;
    this.setState({ useCursorIndex: false.start: 0.end: 0 });
    closeViewer();
  };

  // Update boundaries
  changePic = (activeImage, index) = > {
    const { images } = this.props;
    const { start, end } = this.state;
    if (index === end - start - 1 && index < images.length - 1) {
      if (end + 5 < images.length) {
        this.setState({ end: end + 5.cursorIndex: index, useCursorIndex: true });
      } else {
        this.setState({ end: images.length, cursorIndex: index, useCursorIndex: true}); }}if (index === 0&& start ! = =0) {
      if (start - 5 > 0) {
        this.setState({ start: start - 5.cursorIndex: 5.useCursorIndex: true });
      } else {
        this.setState({ start: 0.cursorIndex: start, useCursorIndex: true}); }}};render() {
    const { images, visible, activeIndex } = this.props;
    const { start, end, cursorIndex, useCursorIndex } = this.state;
    const showImages = images.slice(start, end);
    // These lines can be used for debugging
    // console.log(useCursorIndex);
    // console.log(`cursorIndex:${cursorIndex}`);
    // console.log(`activeIndex:${activeIndex}`);
    // console.log(`start:${start}`);
    // console.log(`end:${end}`);
    return (
      <div>
        <Viewer
          visible={visible && end > 0}
          onClose={this.closeViewer}
          activeIndex={useCursorIndex ? cursorIndex : activeIndex - start}
          images={showImages}
          downloadable
          noImgDetails
          zoomable={false}
          scalable={false}
          loop={false}
          showTotal={false}
          onChange={(activeImage, index) => this.changePic(activeImage, index)}
        />
      </div>); }}Copy the code

I’m going to explain exactly what happened with the screenshots.

First of all, the 20 images are neat.

Then, I click on the first image. Start =0,end=5,activeIndex=0,userCursorIndex=false

I kept moving the button to the right and the image component rotated normally. When I turned to the fifth one, the magic happened and requested 6-10 images at once. Now start = 0, end = 10, activeIndex = 0, cursorIndex = 4, userCursorIndex = true.

This explains why I refer to the variable userCursorIndex, because activeIndex does not update when I update the scope of the image component, so I artificially tell the image component what the activeIndex value should be at this time.

I continued to move the button to the right, and it worked as expected. When I reached the 10th image, I continued to request 11-15 images, at which point the image boundaries were 0-15, the values of start and end. By the time I get to 15, I’m still asking for 16-20 more pictures, and all of them are already requested. The image boundary is now 0-20 and no matter how MUCH I flip through it, it doesn’t load anymore.

Finally, I’ll demonstrate a case that is requested from the middle.

Let me click on the eighth image. It was found that the first four and the last four of this picture had also been loaded. Now start = 3, end = 12, activeIndex = 7, cursorIndex = 0, userCursorIndex = false.

As I scroll forward to page 3, or backward to page 12, boundary updates are triggered. I’m not going to do the demo here.