Using react hooks to realize graphite like picture preview plug-in (giant detail)

Time:2021-10-24

preface

In recent work, we need to make a picture preview plug-in. After referring to the picture preview of many products (nuggets, Zhihu, Jianshu, graphite, etc.), we finally feel that the graphite is more in line with our product needs.

Originally, I thought I could find relevant plug-ins in the community, but the idea was beautiful, but the reality was very skinny, so I decided to grab one by myself and learn the component development process by the way.

Using react hooks to realize graphite like picture preview plug-in (giant detail)

 

Project introduction

Project Preview

The final implementation effect of the project is shown in the figure below, which is basically the same as the picture preview of graphite. supportEnlarge pictureZoom out pictureFull size displayPicture fit screenDownload the picture (this is still under development), that is, the five operation buttons in the bottom bar.

Using react hooks to realize graphite like picture preview plug-in (giant detail)

Technology stack

Components are based onReact HooksandTypeScriptThe packaging tool useswebpack

This article is rightwebpackThe configuration of will not be introduced accordingly, if you are rightwebpackIf you are interested, you can refer to the information compiled by the authorOn webpack performance optimization (with detailed webpack learning notes)

Project directory

.
├── node_ Modules // third party dependencies
Config // webpack configuration folder
    Keywords: webpack. Base. JS // webpack public configuration file
    Http://webpack.dev.config.js // development environment configuration file
    └ - webpack.prod.config.js // production environment configuration file
Example // code preview during development
    [HW] - Src // sample code directory
      App.js // test project entry JS file
      └ -- index.less // test item entry style file
- Src // component source code directory
    - components // directory of wheels
      Photo gallery // photo gallery component folder
    - interface definition of types // typescript
    Utils // tool function directory
    Image // image file directory
    Index.html // project entry template file
    Index.tsx // project entry file
    └ -- index.less // project entry style file
- lib // component packaging result directory
. babelrc // Babel configuration file
. gitignore // files ignored during git upload
File. Npmignore // NPM uploads ignored files
├── README.md
: tslint.json // configuration file of tslint
Configuration file -- tsconfig. JSON // configuration file of TS
Package-lock.json // yarn lock file
└ - package. JSON // the current dependency of the whole project

Warehouse address

Warehouse address here:Graphite like picture preview plug-in

 

Train of thought analysis

The core of this plug-in lies in the display of pictures and the operation of preview pictures, such asenlargenarrowFit screenThese operations are related to the size of the picture,In fact, as long as we know the size of the picture when we click the corresponding button, the whole problem will be solved.

So the author studied a wave of preview logic behind it and found several useful points for coding:

First of all, the picture can’t be enlarged and reduced all the time. It must have a maximum and minimum value. It operates a wave of discovery in graphiteThe maximum value of the preview image is 4 times that of the original imageThe minimum value is 10 times of the original figureAt the same time, it is also necessary to specify how many times to click from the original image to the maximum or minimum value. The number of times I specify in the plug-in is6Times.

In this way, after the picture is loaded, we can easily calculate all the dimensions of the preview picture, and maintain these dimensions in an array, so that behind each zoom in and out click, there will be a picture size corresponding to it.

Next, we need to know that the display size of the current preview image is located atDimension arrayWhich one of themindex, with thisindexAfter that, we just need to take out thisindexThe corresponding picture width can be drawn.

Here comes to the first display of the picture in the container. Let’s take the long picture as an example: in the long picture preview, the plug-in will leave a certain distance between the upper and lower sides of the picture. In fact, this distance is fixed. I calculated in graphite. The gap between the upper and lower sides is the height of the container5%For details, see the following figure (forgive the magic of the picture),In the figureAThe distance isBof5%

Using react hooks to realize graphite like picture preview plug-in (giant detail)

So we can calculate the size of the current picture and take this sizeDimension arrayFind the value closest to this value, and the index of this closest value is the index of the current preview imageindexValue.

There is also a preview picture of graphite throughcanvasWe’ll use it here, toocanvasofdrawImagethisapiTo draw pictures. Of course, it is not supported incanvasOn the browser, we use it directly<img />label.

In this paper, the main analysiscanvasThe content of drawing,<img />The label is actually similar.

Basically, the difficulties of this plug-in have been solved. Next, we will start to analyze the corresponding code.

Using react hooks to realize graphite like picture preview plug-in (giant detail)

 

code analysis

Plug in receive parameters

First, let’s take a look at the required parameters of the plug-in, which can be roughly divided into the following:

  • visibleControls the display and hiding of the preview plug-in:
  • imgData: array of pictures to preview
  • currentImg: when you open the preview plug-in again, which picture is displayed by default
  • hideModal: preview the closing method of the plug-in

The author can think of only these four for the time being, which are basically enough. They are used as follows:

<PhotoGallery
  visible={visible}
  imgData={ImgData}
  currentImg = {9}
  hideModal={
    () => {
      setVisible(false);
    }
  }
/>

 

Plug in structure

The structure of the plug-in is actually very simple. In fact, there are three parts:Picture display blockPicture list selection sidebarBottom operation block, defined as three sub component blocks:<Canvas /><Sidebar /><Footer />, uniformly managed by a parent component.

Because we mainly explaincanvasDraw a picture, so the picture display block is set to<Canvas />, unsupportedcanvasBrowser, which will be used in the source code<Image />Component for picture display. There is no specific introduction here. You can refer to the source code.

Using react hooks to realize graphite like picture preview plug-in (giant detail)

The parent component code is as follows:

// src/components/photoGallery/index.tsx

import React, { useState }  from 'react';
import classNames from 'classnames';
import { Footer, Sidebar, Canvas } from './components';

const photoGallery = (props: Props): JSX.Element => {
  const { imgData, currentImg, visible } = props;
  
  //Which picture is currently displayed
  const [currentImgIndex, setCurrentImgIndex] = useState(currentImg);

  return (
    <div
      className={
        classNames(
          styles.modalWrapper,
          {
            [styles. Showimggallery]: visible, // render plug-ins according to visible
          }
        )
      }
    >
      <div className={styles.contentWrapper}>
        <Canvas
          //URL of the picture to load
          imgUrl={imgUrl}
        />
      </div>
      <Sidebar
        //Image array
        imgData={imgData}
      />
      <Footer
        //Number of pictures
        imgsLens={imgData.length}
        //Current page number
        currentImgIndex={currentImgIndex}
      />
    </div>
  );
}

As shown in the figure above, even if the general structure of the plug-in is completed, the next step is the logic of the core image display module.

 

Picture preview core logic

Let’s create a class firstcanvas.ts, we all operate in this class for image preview.

This class accepts two parameters, one is the render containerdomThe other is the parameters needed for instantiationoptions, here isoptionsInterface implementation:

interface CanvasOptions {
  imgUrl: string; //  Picture address
  winWidth: number; //  Screen width
  winHeight: number; //  Screen height
  canUseCanvas: boolean; //  Can browsers use canusecanvas
  loadingComplete? (instance: any): void; //  Making picture loading effect
}

In addition, we will talk about a series of properties related to preview pictures, which are hung on their instance properties, such as:

  • el: container for rendering
  • canUseCanvas: is it supportedcanvas, decide how to draw
  • contextcanvasCanvas ofgetContext('2d')
  • image: preview picture object
  • imgUrl: preview pictureurl
  • imgTopTarget image: upper right cornercanvasinyHeight of shaft
  • imgLeftTarget image: upper right cornercanvasinxHeight of shaft
  • LongImgTop: the distance of the picture from the top of the container, used for picture scrolling and dragging
  • LongImgLeft: the distance from the picture to the left of the container, used for picture scrolling and dragging
  • sidebarWidth: width of sidebar
  • footerHeight: height of bottom bar
  • cImgWidth: the width of the picture in the canvas
  • cImgHeight: the height of the picture in the canvas
  • winWidth: the width of the screen
  • winHeight: screen height
  • curPos: it is necessary to drag the picture with the mousex/yvalue
  • curScaleIndex: the currently displayed picture, which one is in the size arrayindex
  • fixScreenSize: in the size array using screen sizeindexvalue
  • EachSizeWidthArray: the size array of the picture, which contains the width values for enlarging and reducing all sizes
  • isDoCallback: is the picture loading complete

The attribute values used in the plug-in are basically above.

 

First draw a simple picture

First, let’s take a look at thiscanvasThe one who drew the pictureapi, it can help us draw images, canvases or videos on the canvas.

We can help us draw a picture by enlarging it in the following way:

var c = document.getElementById("myCanvas");
//Create canvas
var ctx = c.getContext("2d");
//Start drawing
ctx.drawImage(image, dx, dy, dWidth, dHeight);

The meanings of parameters are:

  • image: Specifies the image, canvas, or video to use.
  • dximageThe upper left corner of the is on the targetcanvasupperXAxis coordinates
  • dyimageThe upper left corner of the is on the targetcanvasupperyAxis coordinates
  • dWidthimageIn targetcanvasThe width drawn on the.
  • dHeightimage In targetcanvasThe height drawn on the.

See the following figure for details:

Using react hooks to realize graphite like picture preview plug-in (giant detail)

For more usage of this method, you can refer to:MDN document for DrawImage

With thisapiAfter that, we just need to calculate thisapiFor a simple example, how can we get the following figure5Parameters:

Using react hooks to realize graphite like picture preview plug-in (giant detail)

  • imageobject

We can usenew Image()To instantiate aimageObject and specify hissrcProperty is the corresponding pictureurlAddress, so you can get oneimageObject, after the picture is loaded, we can useimgDom.naturalWidthandimgDom.naturalHeightOriginal width and height of the picture:

// src/components/photoGallery/canvas.ts

loadimg(imgurl) {
  const imgDom = new Image();
  imgDom.src = imgUrl;

  imgDom.onload = function() {
    //After the picture is loaded
    //Do what you want to do
  }
}
  • dxAnddydwidthAnddHeightattribute

Let’s take the long picture as an example: We analyzed it when explaining the idea, and the parts left blank on the upper and lower sides areThe picture shows the height of the containerof5%, here we define the height of the bottom block(footerHeight)For50px, width of sidebar(sidebarWidth)For120px, this has become a primary school application problem, which we can passwindow.innerWidthandwindow.innerHeightGet the width of the screen(winWidth)Hegao(winHeight)After calculation, we can get the four attributes we need:

/**
 *Winwidth: screen width
 *Winheight: screen height
 *Footerheight: bottom height
 *Sidebarwidth: sidebar width
 *Wrapperwidth: the width of the image display area
 *Wrapperheight: the height of the image display area
 *Naturalwidth: the original width of the picture
 *Naturalheight: the original height of the picture
 */

wrapperHeight = winHeight - footerHeight;
wrapperWidth = winWidth - sidebarWidth;

dy = wrapperHeight * 0.05;
dHeight = wrapperHeight - 2 * dy;

//It has an equal proportion to the original width and height
dWidth = naturalWidth * dHeight / naturalHeight;
dx = (wrapperWidth - dWidth) / 2

The above is the process of calculating the five attributes we need. Generally speaking, it is relatively convenient.

So every time we want to draw a picture, just calculate this5Just a valueokYes.

 

Initial picture width and height

We areutilsLowerimg.tsDefine a method ingetBoundingClientRect, used to getThe width and height of the picture and its distance from the top of the containerimgTop, and distance to the leftimgLeft

// src/utils/img.ts
/**
 *Returns the width and height of the first loaded picture, and imgtop / imgleft
 *Draw the drawing directly through DrawImage through the returned parameters
 **/
export const getBoundingClientRect = (options: RectWidth): BoundingClientRect => {
  const {
    Naturalwidth, // the original width of the picture
    Naturalheight, // the original height of the picture
    Wrapperwidth, // displays the container width
    Wrapperheight, // display container height
    Winwidth, // screen width
  } = options;

  //Picture aspect ratio
  const imageRadio = naturalWidth / naturalHeight;
  
  //Displays the container aspect ratio
  const wrapperRadio = wrapperWidth / wrapperHeight;

  //Logic of long graph
  if (imageRadio <= 1) {
    //The default height above the specific canvas is 0.05 of the container height
    imgTop = wrapperHeight * 0.05;

    //Height of picture
    ImgHeight = wrapperHeight - wrapperHeight * 0.05 * 2;
    //According to the original width and height, the image width is obtained in equal proportion
    ImgWidth = ImgHeight * naturalWidth / naturalHeight;

    //If the aspect ratio of the picture is larger than that of the display container
    //Note the width of the left and right sides of the picture needs to be fixed to 0.05 times the width of the container
    if (wrapperRadio <= imageRadio) {
      ImgWidth = wrapperWidth - wrapperWidth * 0.05 * 2;
      ImgHeight =  ImgWidth * naturalHeight / naturalWidth;

      imgTop = (wrapperHeight - ImgHeight) / 2
    }

    // ...
    imgLeft = newWinWidth - ImgWidth / 2;
  }

  //Logic for dealing with wide graphs
  // ...

  //Return
  return {
    imgLeft,
    imgTop,
    ImgWidth,
    ImgHeight,
  }
}

More detailed code, you can refer to the source code.

 

Preview picture size array

We mentioned earlier that we can put all the sizes in the process of zooming in and out of the picture into an array, which is convenient for us to get the corresponding picture size through index. So how to operate?

In fact, as long as the original width and height of the picture are obtained after the picture is loaded, the corresponding size array can be calculated through the original width and height and the corresponding calculation formula, and then inserted into the array.

Define asetEachSizeArrInstance method:

// src/components/photoGallery/canvas.ts
/**
 *Calculate the size array of each size of the picture,
 */
private setEachSizeArr () {
  const image = this.image;
  
  //Get dimension array
  const EachSizeWidthArray: number[] = getEachSizeWidthArray({
    naturalWidth: image.width,
    naturalHeight: image.height,
  })

  //Hang to instance properties
  this.EachSizeWidthArray = EachSizeWidthArray;

  //Get an index that fits the screen
  //That is, the fourth button in the operation buttons
  const fixScreenSize = getFixScreenIndex({
    naturalWidth: image.width,
    naturalHeight: image.height,
    wrapperWidth: this.cWidth,
    wrapperHeight: this.cHeight,
  }, EachSizeWidthArray);

  //Hang the index that fits the screen to the instance property
  this.fixScreenSize = fixScreenSize;
}
  • getEachSizeWidthArray

We get the size array through this method, because the largest picture is 4 times of the original picture and the smallest picture is 4 times of the original picture1/10, from the smallest to the original and from the original to the largest6Once, we can get the size of each size according to the proportion. I won’t post the specific code.

  • getFixScreenIndex

We use this method to get the size array suitable for the screenindexThe principle is that the first width height in the dimension array is less than the width height of the display containerindex

I won’t post the specific codes of these two methods. If you are interested, you can go to the source code to check.

 

Initial preview picture index

We need to calculate the one in the size array when the image is rendered for the first timeindex, because we get the width of the first rendered image, we can compare this width with the array in the size array, and the index of the closest valueindex, which is the of the current pictureindexValue:

// src/components/photoGallery/canvas.ts
/**
 *Sets the index of the current eachsizewidtharray to zoom in and out
 */
private setCurScaleIndex() {
  const cImgWidth = this.cImgWidth || this.image.width;

  const EachSizeWidthArray = this.EachSizeWidthArray;

  const curScaleIndex = getCurImgIndex(EachSizeWidthArray, cImgWidth);

  this.curScaleIndex = curScaleIndex;
}
  • getCurImgIndex

We use this method to get the index value of the current picture, which is based on the width of the currently rendered pictureDimension arrayTake out the width closest to the preview picture to get the width of the current pictureindex, you can refer to the source code for specific implementation.

 

Zoom in and zoom out logic

The logic of zoom in preview is actually to calculate the distance of the current picture according to the enlarged sizecanvasTop heightimgTop, and distance to the leftcanvasofimgLeft

Previously, we have obtained the index for the first picture display. When we click to enlarge, it is nothing more than to increase the current index value by one and reduce it by one.

We can go according to the new index valueDimension arrayThe width of the corresponding index is taken out from the image. Through the original width and height of the image, the width and height that should be displayed can be obtained in equal proportion. Finally, we only need to calculate the width and height of the enlarged imageimgTopandimgLeftIn fact, the function can be realized:

/**
 *Modifies the index in the current picture size array
 * @param curSizeIndex :  
 */
public changeCurSizeIndex(curSizeIndex: number) {
  let curScaleIndex = curSizeIndex;

  if (curScaleIndex > 12) curScaleIndex = 12;
  if (curScaleIndex < 0) curScaleIndex = 0;

  //Canvas width and height, that is, the width and height of the display container
  const cWidth = this.cWidth;
  const cHeight = this.cHeight;

  //Last index
  const prevScaleTimes = this.curScaleIndex;
    //Dimension array
  const EachSizeWidthArray = this.EachSizeWidthArray;

  let scaleRadio = 1;

    //Ratio of this time width to last time
  //Through this value, it is more convenient to get the width and height of the picture
  scaleRadio = EachSizeWidthArray[curScaleIndex] / EachSizeWidthArray[prevScaleTimes];

  //Current picture width and height
  this.cImgHeight = this.cImgHeight * scaleRadio;
  this.cImgWidth = this.cImgWidth * scaleRadio;

  //Get the latest imgtop
  //The positive and negative values of imgtop value are positive downward according to the point in the upper left corner of the canvas
  this.imgTop = cHeight / 2 - (cHeight / 2 - this.imgTop) * scaleRadio;
  //Set current index value
  this.curScaleIndex = curScaleIndex;

  //If the picture does not exceed the width and height of the canvas
  if (this.cImgHeight < cHeight && this.cImgWidth < cWidth) {
    this.imgTop = (cHeight - this.cImgHeight) / 2;
  }

  //Calculation of imgleft
  this.imgLeft = cWidth / 2 - this.cImgWidth / 2;

  //It is needed when the picture is sliding or dragging
  this.LongImgTop = this.imgTop;
  this.LongImgLeft = this.imgLeft;

  //Draw picture
  // ...
}

 

event

Scroll event

staycanvasScrolling pictures in is actually recalculating the number of picturesimgTopandimgLeft, and then redraw it.

Here we use the scroll wheel eventonWheelTo calculate the scrolling distance through the event objecteventUpperdeltaXanddeltaYGet inx/yThe rolling distance on the axis.

One point to note here is the treatment of boundary values,imgTopWe can’t have endless big and small, and the maximum can’t exceed what we stipulated beforeLONG_IMG_TOPThis value we set is10pxFor minimum, please refer to the following calculation method (the boundary value calculation of width is similar, so it will not be introduced)

/**
 *Minimgtop: the minimum imgtop value
 *Maximgtop: the maximum imgtop value
 *Imgheight: picture height
 *Winheight: screen height
 *Footerheight: height of bottom operation bar
 * LONG_ IMG_ Top: we set an upper and lower constant padding
 */
//The minimum must be negative
minImgTop = -(imgHeight - (winHeight - footerHeight - LONG_IMG_TOP))
//Maximum
maxImgTop = LONG_IMG_TOP

Next we arecanvasClassWheelUpdateCase method, exposed to external calls,

// src/components/photoGallery/canvas.ts

/**
 *Wheel event
 *Event parameters for @ param e wheel
 */
public WheelUpdate(e: any) {
    // ...

  //The picture shows the width and height of the container
  const cWidth = this.cWidth;
  const cHeight = this.cHeight;

  //If the width and height of the picture are less than the width and height of the picture display container, it will be returned directly
  if (this.cImgHeight < cHeight && this.cImgWidth < cWidth) {
    return;
  }

  //If the height of the picture is greater than the height of the display container
  //It is allowed to slide in the Y direction
  if (this.cImgHeight > cHeight) {
    //This value saves the current picture from the container imgtop
    this.LongImgTop = this.LongImgTop - e.deltaY;

    //E. delta down
    if (e.deltaY > 0) {
      //Here is a limit value judgment
      //Specifically, our algorithm
      if ((-this.LongImgTop) > this.cImgHeight + LONG_IMG_TOP - window.innerHeight + this.footerHeight) {
        this.LongImgTop = -(this.cImgHeight + LONG_IMG_TOP - window.innerHeight + this.footerHeight);
      }
    } else {
      //When sliding up, the maximum value is the compatible value long_ IMG_ TOP
      if (this.LongImgTop > LONG_IMG_TOP) {
        this.LongImgTop = LONG_IMG_TOP;
      }
    }
  }

  //Handles scrolling on the x-axis 
  // ...

  //Assignment imgtop, imgleft
  this.imgTop = this.LongImgTop;
  this.imgLeft = this.LongImgLeft;

  //Draw picture
  // ...
}

 

Drag event

We need the help of picture draggingonMouseDownonMouseMoveonMouseUpThree event functions. In fact, the operation mode can be similar to picture scrolling. We need to calculate a new imageimgTopandimgLeftTo redraw the picture, but we can’t passeventNow we can get the drag value directly. We need to calculate the drag distance by the difference between the last time and the previous timeimgTopandimgLeftValue,

First, we hang the real-time coordinates during the image dragging process on the instance attributecurPosOn, ononMouseDownThe initial coordinates are assigned whenonMouseMoveFunction, we can get the initial coordinates of the mouse press.

// src/components/photoGallery/index.tsx

/**
 *Mouse down event
 * @param e
 *@ param instance: instance of picture preview
 */
const MouseDown = (e: any, instance: any) => {
  //The global moveflag indicates whether the drag starts
  moveFlag = true;
  const { clientX, clientY } = e;

  //Set the initial X and Y coordinates for the current preview instance
  instance.curPos.x = clientX;
  instance.curPos.y = clientY;

  // ...
};

/**
 *Mouse up event
 */
const MouseUp = (e: any) => {
  moveFlag = false;
};

/**
 *Mouse movement event
 */
const MouseMove = useCallback((e: any, instance: any) => {
  //Directly call the movecanvas method under the instance
  instance.MoveCanvas(moveFlag, e);
}, [])

Next, let’s look at the main drag methodsMoveCanvas, we subtract the last coordinate value from the real-time coordinate value(curPosCompare the saved values) to get the sliding distance, so that we can get the latest valueimgTopandimgLeftOf course, don’t forget to calculate the boundary value here.

// src/components/photoGallery/canvas.ts

/**
 *Mouse drag events
 *@ param moveflag: flag bit of whether it can be moved
 * @param e
 */
public MoveCanvas(moveFlag: boolean, e: any) {
  //The drag logic is executed only when dragging
  if (moveFlag) {
    //The picture shows the width and height of the container
    const cWidth = this.cWidth;
    const cHeight = this.cHeight;
        
    if (this.cImgHeight < cHeight && this.cImgWidth < cWidth) {
      return;
    }

    //Coordinates of the current slide
    const { clientX, clientY } = e;

    //Last coordinate
    const curX = this.curPos.x;
    const curY = this.curPos.y;

    //Handles scrolling on the Y axis 
    if (this.cImgHeight > this.cHeight) {
      //This value saves the current picture from the container imgtop
      this.LongImgTop = this.LongImgTop + (clientY - this.curPos.y);
      //Boundary value calculation similar to scrolling
    }

    //Handles scrolling on the x-axis 
    // ...

    //Update x, Y values on instance properties
    this.curPos.x = clientX;
    this.curPos.y = clientY;

    //Assignment imgtop, imgleft
    this.imgTop = this.LongImgTop;
    this.imgLeft = this.LongImgLeft;

        //Draw picture
    // ...
  }
}

 

Preview plug-in closed

We close the picture preview plug-in when clicking on the picture, but what we need to consider here is that we can drag the picture. When the user drags the picture, we don’t need to close the plug-in, so we need to judge before and after the user presses the mouse,x/yHas the coordinate value changed? If so, we will not close it. Otherwise, we will close the preview plug-in directly.

becausemosueDownandmouseUpThe event is earlier thanclickEvent, we set a flag bitDoClick, if the position before and after pressing the mouse does not change, this flag bit istrue, when the picture is clicked, it will be closed directly, otherwise it will not be processed.

// src/components/photoGallery/index.tsx

const MouseDown = (e: any, instance: any) => {
  // ...
  StartPos.x = clientX;
  StartPos.y = clientY;
}

const MouseUp = (e: any) => {
  if (e.clientX === StartPos.x && e.clientY === StartPos.y) {
    DoClick = true;
  } else {
    DoClick = false;
  }
}

const Click = () => {
  if (!DoClick) return;
  
  const { hideModal } = props;
  if (hideModal) {
    hideModal();
  }
}

 

Other knowledge points

When is the picture class instantiated

We created a preview image class before, so when do we need to instantiate it?

Just listen for incomingimgUrlWhen changing, empty the previous instance and instantiate a new plug-in.

// src/components/photoGallery/components/Canvas.tsx

const Canvas = (props: Props): JSX.Element => {
  // ...
  //DOM element of canvas
  let canvasRef: any = useRef();
  //The variable that holds the preview picture instance
  let canvasInstance: any = useRef(null);

  useEffect((): void => {
    if (canvasInstance.current) canvasInstance.current = null;

    const canvasNode = canvasRef.current;

    canvasInstance.current = new ImgToCanvas(canvasNode, {
      imgUrl,
      winWidth,
      winHeight,
      canUseCanvas,
      //Picture loading complete hook
      loadingComplete: function(instance) {
        props.setImgLoading(false);
        props.setCurSize(instance.curScaleIndex);
        props.setFixScreenSize(instance.fixScreenSize);
      },
    });
  }, [imgUrl]);
  
  // ...
}

With this picture examplecanvasInstance, for various operations of this preview, such asenlargenarrowWe can all call its own methods, which can be easily implemented.

 

Screen size

When the screen size changes, we need to draw pictures in real time according to the latest size. Here we write a customHooks, monitor screensizeChanges in.

// src/components/photoGallery/index.tsx

function useWinSize(){
  const [ size , setSize] = useState({
    width:  document.documentElement.clientWidth,
    height: document.documentElement.clientHeight
  });
  
  const onResize = useCallback(()=>{
    setSize({
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight,
    })
  }, []);

  useEffect(()=>{
    window.addEventListener('resize', onResize, false);

    return ()=>{
      window.removeEventListener('resize', onResize, false);
    }
  }, [])

  return size;
}

 

canvasDraw blink

There is another problemcanvasDuring drawing, when the screenresizeThe problem of flicker will occur during the process of, as shown in the following figure:

Using react hooks to realize graphite like picture preview plug-in (giant detail)

This is because when we redraw the canvas, we need to useclearRectTo empty the canvas. At this time, the canvas is empty. It takes a corresponding time to start redrawing. Therefore, the screen will flash visually.To solve the problem of flashing screen is actually how to solve the problem of long drawing time

We can refer toDual cacheTo solve this problem, the rendering process is handed over toCache canvas, so that thecanvasThe drawing process is omitted, andCache canvasIt is not added to the page, so we can’t see the drawing processcachecanvasAfter drawing, directly assign it to the original pagecanvasThis solves the problem of flashing screen.

// src/components/photoGallery/canvas.ts

class ImgToCanvas {
  // ...
  private cacheCanvas : any;
  private context : any;
  
  // ...
  
  private drawImg (type?: string) {
    //Canvas in page
    const context = this.context;
    // ...
    
    //Create a cache canvas and hang it under the instance attribute cachecanvas
    if (!this.cacheCanvas) {
      this.cacheCanvas = document.createElement("canvas");
    }

    //Sets the width and height of the cache canvas
    this.cacheCanvas.width = this.cWidth;
    this.cacheCanvas.height = this.cHeight;
    //Create canvas
    const tempCtx = this.cacheCanvas.getContext('2d')!;

    //Drawing with cache canvas
    tempCtx.drawImage(image, this.imgLeft, this.imgTop, this.cImgWidth, this.cImgHeight);

    //Clear the canvas and assign the cached canvas to the page canvas
    requestAnimationFrame(() => {
      this.clearLastCanvas(context);
      context.drawImage(this.cacheCanvas, 0, 0);
    })
    
    // ...
  }
}

 

Summary

This article arranges the implementation process of an imitation ink picture preview plug-in from zero to oneTrain of thought analysisCode structure divisionImplementation of main logicThese aspects illustrate a wave.

Through the compilation of this plug-in, the authorcanvasDrawing ofapiHow to deal with itcanvasThe problem of picture flicker during drawing, andReact HooksHave a general understanding of some usage of.

To be honest, I want a praise!

Using react hooks to realize graphite like picture preview plug-in (giant detail)

 

Reference content

Recommended Today

SQL exercise 20 – Modeling & Reporting

This blog is used to review and sort out the common topic modeling architecture, analysis oriented architecture and integration topic reports in data warehouse. I have uploaded these reports to GitHub. If you are interested, you can have a lookAddress:https://github.com/nino-laiqiu/TiTanI recorded a relatively complete development process in my hexo blog deployed on GitHub. You can […]