preface

Recently, I need to make a plug-in for picture preview. After referring to the picture preview of many products (nuggets, Zhihu, Jianshu, graphite, etc.), I finally think that graphite is more suitable for our product needs.

I thought I could find a plugin in the community, but the idea was nice, but the reality was boring, so I decided to do one myself and learn about the component development process.

 

Project introduction

Project Preview

The final result of the project is shown below, which is basically the same as the graphite preview. Zoom in, zoom out, full-size display, fit to screen, download (which is still under development), and the five action buttons in the bottom bar.

Technology stack

The components are implemented based on React Hooks and TypeScript, and the packaging tool uses WebPack.

This article will not introduce the configuration of Webpack. If you are interested in Webpack, you can refer to the author’s discussion on Webpack performance optimization (with a large detailed Webpack study notes).

Project directory

. ├ ─ ─ node_modules// Third party dependencies├ ─ ─ the config// WebPack configuration folder├ ─ ─ webpack. Base. Js// Webpack public configuration file├ ─ ─ webpack. Dev. Config. Js// Development environment configuration file└ ─ ─ webpack. Prod. Config. Js// Production environment configuration file├ ─ ─ example// Preview code at development time├ ─ ─ the SRC// Sample code directory├ ─ ─ app. Js// Test project entry js file└ ─ ─ index. Less// Test the project entry style file file├ ─ ─ the SRC// Component source directory├ ─ ─ the components// The directory of wheels├ ─ ─ photoGallery// photoGallery component folder├ ─ ─ types// Typescripe interface definition├ ─ ─ utils// Directory of utility functions├ ─ ─ images// Image file directory├ ─ ─ index. HTML// Project entry template file├ ─ ─ index. The TSX// Project entry file└ ─ ─ index. Less// Project entry style file├ ─ ─ lib// Component package results directory├ ─ ─ babelrc// Babel configuration file├ ─ ─ gitignore// git files ignored while uploading├ ─ ─ npmignore// NPM uploads ignored files├ ─ ─ the README. Md ├ ─ ─ tslint. Json// tslint configuration file├ ─ ─ tsconfig. Json// Ts configuration file├ ─ ─ package - lock. Json// yarn lock file└ ─ ─ package. Json// The current dependency of the entire project
Copy the code

The warehouse address

Warehouse address here: imitation graphite picture preview plug-in.

 

Thought analysis

The plugin is at the heart of the picture show, and around to the operation of the preview images, such as amplification, narrow, adapt to the screen, and these operations are related to the size of the picture, actually we want to know when click the button corresponding operations, image should show how old size, the whole problem is solved.

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

First pictures cannot zoom in and out all the time, it must be a maximum and minimum values, operating a wave found that the maximum number of preview images in the graphite is four times that of the original image, minimum 10 times that of the original image, at the same time also need rules from the original image began to click a few times the maximum or minimum value, in the plug-in I specified number is 6.

After the image is loaded, we can easily calculate all the dimensions of the preview image. We can maintain these dimensions in an array, so that behind each zoom in and out click, there will be a corresponding image size.

Then we need to know which index in the size array is the display size of the current preview image. After we have this index, we only need to take out the corresponding image width of this index to draw.

Here’s what happens when an image is first displayed in a container. Let’s take a long graph as an example: For long preview, the plugin will leave A certain distance between the upper and lower sides of the image, which is actually fixed. In the graphite, I calculated that the space left between the upper and lower sides is 5% of the height of the container. For details, you can see the following figure (forgive the magic picture).

In this way we can calculate the size of the current image and find the closest value in the size array. The index of the closest value is the index value of the current preview image.

There is also a graphite preview image drawn on canvas. We will also use the Canvas drawImage API to draw the image. Of course, on browsers that do not support Canvas, we will use the tag directly.

In this article, I will mainly analyze canvas drawing. In fact, the tag is similar.

Now that most of the plug-in’s difficulties are solved, let’s start analyzing the corresponding code.

 

The code analysis

Plug-in receive parameter

First of all, let’s take a look at the parameters required by the plug-in, which can be roughly classified as the following:

  • visible: Controls the display hiding of preview plug-ins
  • imgData: Array of images to preview
  • currentImg: When the preview plugin is opened, the default number of images will be displayed
  • hideModalPreview the closing method of the plug-in

The author can think of these four temporarily, basically has been enough, use as follows:

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

 

The plug-in structure

The structure of the plug-in is actually very simple, in fact, there are three blocks: picture display block, picture list selection Sidebar, bottom operation block, defined as three sub-component blocks:
,
,

, unified by a parent component management.

Since we are mainly talking about drawing pictures on canvas, the Image display block is set to < canvas />. Canvas browsers that are not supported will use the component to display pictures in the source code, which is not detailed here. You can refer to the source code.

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 image is currently displayed
  const [currentImgIndex, setCurrentImgIndex] = useState(currentImg);

  return( <div className={ classNames( styles.modalWrapper, { [styles.showImgGallery]: visible, ImgUrl ={imgUrl} /> </div> <Sidebar // Image array imgData={imgData} /> <Footer // Number of images imgsLens={imgdata. length} // Number of current entries currentImgIndex={currentImgIndex} /> </div> ); }Copy the code

As shown in the figure above, the general structure of the plug-in is complete, and the logic of the core image display module is next.

 

Image preview core logic

We will create a class called Canvas. ts, where we will preview the image.

This class takes two arguments, one is the render container DOM, the other is the instantiation parameter options, the following interface implementation options:

interface CanvasOptions {
  imgUrl: string; // Image address
  winWidth: number; // Screen width
  winHeight: number; // Screen height
  canUseCanvas: boolean; // The browser can use canUseCanvasloadingComplete? (instance:any) :void; // Create an image loading effect
}
Copy the code

There are also a number of properties associated with the preview image that are attached to the example properties, such as:

  • el: render container
  • canUseCanvas: Supported or notcanvasAnd decide how to draw the picture
  • context:canvasThe canvasgetContext('2d')
  • image: Previews the image object
  • imgUrl: Preview imagesurl
  • imgTop: Target in the upper right corner of the picturecanvasyThe height of the shaft
  • imgLeft: Target in the upper right corner of the picturecanvasxThe height of the shaft
  • LongImgTop: The distance between the image and the top of the container, used for scrolling and dragging
  • LongImgLeft: The distance between the image and the left side of the container, used for scrolling and dragging
  • sidebarWidth: Width of the sidebar
  • footerHeight: Height of the bottom bar
  • cImgWidth: Width of the picture on the canvas
  • cImgHeight: Height of the picture on the canvas
  • winWidth: Screen width
  • winHeight: Screen height
  • curPos: Mouse drag picture is neededx/y
  • curScaleIndex: Which image is currently displayed in the dimensions arrayindex
  • fixScreenSize: uses the size array of screen sizesindex
  • EachSizeWidthArray: An array of dimensions for an image, containing width values for all sizes
  • isDoCallback: Whether the image is loaded

The values of the properties used in the plug-in are basically there.

 

Let me draw a simple picture

Let’s start by looking at the Canvas drawing API, which allows us to draw images, canvases, or videos on a canvas.

We can zoom in to help us draw a picture:

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

The meanings of parameters are as follows:

  • Image: Specifies the image, canvas, or video to use.
  • dx:imageThe top left corner of the targetcanvasXAxis coordinates
  • dy:imageThe top left corner of the targetcanvasyAxis coordinates
  • dWidth:imageIn the targetcanvasThe width drawn on.
  • dHeight:imageIn the targetcanvasThe height drawn on.

See the following figure for details:

For more information on how to use this method, see the MDN documentation for drawImage.

Now that we have the API, we just need to calculate the five parameters of the API. For a simple example, here’s how to get the five parameters:

  • imageobject

We can instantiate an Image object with new Image() and specify its SRC property as the corresponding Image URL. This gives us an Image object. When the Image is loaded, Imgdom. naturalWidth and imgdom. naturalHeight show the original width and height of the image:

// src/components/photoGallery/canvas.ts

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

  imgDom.onload = function() {
    // After the image is loaded
    Do what you want to do}}Copy the code
  • dxdy,dwidthdHeightattribute

Let’s take the long picture as an example: The left space is 5% of the container height shown in the image. Here we define the footerHeight of the bottom block to be 50px and the sidebarWidth to be 120px. We use window.innerWidth and window.innerHeight to get the width and height of the screen, which gives us the four properties we need:

/** * winWidth: width of the screen * winHeight: height of the screen * footerHeight: bottom height * sidebarWidth: width of the sidebar * wrapperWidth: width of the image display area * wrapperHeight: Image display area height * naturalWidth: image original width * naturalHeight: image original height */

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
Copy the code

The above is the procedure for calculating the five attributes we need, but generally it is quite convenient.

So every time we want to draw an image, we just have to calculate these five values.

 

Initial image width and height

We define a method getBoundingClientRect in img.ts under utils to get the image’s display width and height and imgTop from the top of the container and imgLeft from the left.

// src/utils/img.ts
ImgTop /imgLeft * Draw the image directly from drawImage **/
export const getBoundingClientRect = (options: RectWidth): BoundingClientRect= > {
  const {
    naturalWidth, // The original width of the image
    naturalHeight, // The image is originally high
    wrapperWidth, // Displays the container width
    wrapperHeight, // Display the container height
    winWidth, // Screen width
  } = options;

  // Image width ratio
  const imageRadio = naturalWidth / naturalHeight;
  
  // Displays the height ratio of the container
  const wrapperRadio = wrapperWidth / wrapperHeight;

  // Long graph logic
  if (imageRadio <= 1) {
    // The default container height above the canvas is 0.05
    imgTop = wrapperHeight * 0.05;

    // Height of the image
    ImgHeight = wrapperHeight - wrapperHeight * 0.05 * 2;
    // The width of the image is proportional to the original width and height
    ImgWidth = ImgHeight * naturalWidth / naturalHeight;

    // If the width ratio of the image is larger than that of the container
    The width of the left and right sides of the image should be 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;
  }

  // Handle the logic of the wide graph
  // ...

  / / return
  return {
    imgLeft,
    imgTop,
    ImgWidth,
    ImgHeight,
  }
}
Copy the code

More detailed code we can refer to the source code.

 

Preview image size array

As we mentioned earlier, we can put all the size of the picture in an array, so that we can get the corresponding size of the picture through the index later. So how do we do this?

In fact, as long as the picture is loaded, the original width and height of the picture, through the original width and height, through the corresponding calculation formula, calculate the corresponding size array, into the array.

Define a setEachSizeArr instance method in the class:

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

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

  // Get the index that fits the screen
  // The fourth button of the action button
  const fixScreenSize = getFixScreenIndex({
    naturalWidth: image.width,
    naturalHeight: image.height,
    wrapperWidth: this.cWidth,
    wrapperHeight: this.cHeight,
  }, EachSizeWidthArray);

  // Attach the screen-appropriate index to the instance property
  this.fixScreenSize = fixScreenSize;
}
Copy the code
  • getEachSizeWidthArray

We get the size array through this method, because the largest picture is 4 times of the original picture, the smallest picture is 1/10 of the original picture, from the minimum to the original picture and from the original picture to the maximum need to go through 6 times, we can get the size of each size according to the scale, the author will not paste the specific code.

  • getFixScreenIndex

We use this method to get the index of the screen size array. The principle is that the first index in the size array is less than the width and height of the display container.

The specific code of these two methods is not posted, you can go to the source code to view.

 

Initial preview image index

We need to calculate the index in the size array when the image is first rendered. Since we get the width of the first rendered image, we can compare this width with the array in the size array. The closest index to this value is the index of the current image:

// 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;
}
Copy the code
  • getCurImgIndex

We use this method to get the current picture section index value, he is based on the current render picture width, to take out the size array closest to the preview picture width, so as to get the current picture index, the specific implementation we can refer to the source code.

 

Zoom out logic

The logic of the zoom preview is actually to calculate the imgTop height of the current image from the top of the canvas and imgLeft from the left canvas based on the enlarged size.

We already have the first image display index. When we click to enlarge, we simply increase the current index value by one, and reduce it by one.

ImgTop, imgLeft, imgLeft, imgTop, imgLeft, imgLeft, imgTop, imgLeft, imgLeft, imgTop, imgLeft, imgLeft, imgTop, imgLeft, imgLeft, imgTop, imgLeft, imgLeft, imgTop, imgLeft, imgLeft

/** * change the index in the current image 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, display container width and height
  const cWidth = this.cWidth;
  const cHeight = this.cHeight;

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

  let scaleRadio = 1;

	// The ratio of the width this time to the last time
  // Use this value to make it easier to get the image width and height
  scaleRadio = EachSizeWidthArray[curScaleIndex] / EachSizeWidthArray[prevScaleTimes];

  // Width and height of the current image
  this.cImgHeight = this.cImgHeight * scaleRadio;
  this.cImgWidth = this.cImgWidth * scaleRadio;

  // Get the latest imgTop
  // imgTop Value Positive or negative values are based on the point in the upper left corner of the canvas, and are positive downward
  this.imgTop = cHeight / 2 - (cHeight / 2 - this.imgTop) * scaleRadio;
  // Set the current index value
  this.curScaleIndex = curScaleIndex;

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

  // imgLeft calculation
  this.imgLeft = cWidth / 2 - this.cImgWidth / 2;

  // This is used when dragging or sliding images
  this.LongImgTop = this.imgTop;
  this.LongImgLeft = this.imgLeft;

  // Draw a picture
  // ...
}
Copy the code

 

The event

Scroll event

ImgTop and imgLeft recalculate the imgTop and imgLeft of the image and redraw it on the canvas.

Here we use the roller event onWheel to calculate the rolling distance, and the rolling distance on the X /y axis is obtained by deltaX and deltaY on the event object.

ImgTop should not be infinitely large or small. The maximum value of imgTop should not be larger than LONG_IMG_TOP (10px), and the minimum value can be calculated as follows:

/** * minImgTop: minimum imgTop value * maxImgTop: maximum imgTop value * imgHeight: image height * winHeight: screen height * footerHeight: Bottom bar height * LONG_IMG_TOP: a constant padding */ that we set up
// The minimum must be negative
minImgTop = -(imgHeight - (winHeight - footerHeight - LONG_IMG_TOP))
/ / the biggest
maxImgTop = LONG_IMG_TOP
Copy the code

Next we define a WheelUpdate instance method in the Canvas class and expose it to external calls,

// src/components/photoGallery/canvas.ts

/** * wheel event * @param e wheel event parameter */
public WheelUpdate(e: any) {
	// ...

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

  // If the width and height of the image are all smaller than the width and height of the image display container, return the image directly
  if (this.cImgHeight < cHeight && this.cImgWidth < cWidth) {
    return;
  }

  // If the height of the image is greater than that of the display container
  // Allow sliding in the Y direction
  if (this.cImgHeight > cHeight) {
    // This value stores the current image distance from the container imgTop
    this.LongImgTop = this.LongImgTop - e.deltaY;

    / / e.d eltaY downward
    if (e.deltaY > 0) {
      // Make a limit judgment here
      // This is 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 compatible LONG_IMG_TOP
      if (this.LongImgTop > LONG_IMG_TOP) {
        this.LongImgTop = LONG_IMG_TOP; }}}// Handle the scroll on the x axis
  // ...

  ImgTop, imgLeft
  this.imgTop = this.LongImgTop;
  this.imgLeft = this.LongImgLeft;

  // Draw a picture
  // ...
}
Copy the code

 

Drag events

Image drag we need to use onMouseDown, onMouseMove, onMouseUp three event functions. ImgTop and imgLeft to redraw the image, but we can’t directly get the drag value from the event below. We need to calculate the drag distance by the difference between the last and the previous one. ImgTop and imgLeft,

First we put the real-time coordinates of the dragging process on the instance property curPos, and then assign the initial coordinates on onMouseDown, so that in the onMouseMove function we can get the initial coordinates of the mouse down.

// src/components/photoGallery/index.tsx

/** ** mouse press event * @param e * @param instance: image preview instance */
const MouseDown = (e: any, instance: any) = > {
  // Global moveFlag indicates whether the drag has started
  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 lift event */
const MouseUp = (e: any) = > {
  moveFlag = false;
};

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

MoveCanvas = MoveCanvas = MoveCanvas = MoveCanvas = MoveCanvas = MoveCanvas = MoveCanvas = MoveCanvas Of course, don’t forget to calculate the boundary values here.

// src/components/photoGallery/canvas.ts

/** ** mouse drag events * @param moveFlag: flag bits that can be moved * @param e */
public MoveCanvas(moveFlag: boolean, e: any) {
  // Drag logic is executed only in the drag case
  if (moveFlag) {
    // The image shows the width and height of the container
    const cWidth = this.cWidth;
    const cHeight = this.cHeight;
        
    if (this.cImgHeight < cHeight && this.cImgWidth < cWidth) {
      return;
    }

    // The current slide coordinates
    const { clientX, clientY } = e;

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

    // Handle the scroll on the Y axis
    if (this.cImgHeight > this.cHeight) {
      // This value stores the current image distance from the container imgTop
      this.LongImgTop = this.LongImgTop + (clientY - this.curPos.y);
      // The boundary value calculation is similar to rolling
    }

    // Handle the scroll on the x axis
    // ...

    // Update the x and y values on the instance attributes
    this.curPos.x = clientX;
    this.curPos.y = clientY;

    ImgTop, imgLeft
    this.imgTop = this.LongImgTop;
    this.imgLeft = this.LongImgLeft;

		// Draw a picture
    // ...}}Copy the code

 

Preview plugin closed

When we click on the image to close the preview picture plugin, but need to consider here is that we are able to drag the image, when the user is dragging the pictures, we don’t need to close the plug-in, so we need to decide before and after the user mouse click, x/y coordinate values have changed, if changed, We will not perform the close operation, otherwise we will directly close the preview plug-in.

Since the mosueDown and mouseUp events precede the Click events, we set a flag bit DoClick that is true if the position of the mouse does not change before and after the click, so that the image is closed when clicked, and not when clicked.

// 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 = (a)= > {
  if(! DoClick)return;
  
  const { hideModal } = props;
  if(hideModal) { hideModal(); }}Copy the code

 

Other knowledge points

When the picture class is instantiated

We created a class that previews images, so when do we instantiate it?

Just listen for changes in the passed imgUrl and empty the previous instance while instantiating a new plugin.

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

const Canvas = (props: Props): JSX.Element => {
  // ...
  // Canvas dom element
  let canvasRef: any = useRef();
  // Store the preview image instance variable
  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,
      // Image loading completes hook
      loadingComplete: function(instance) {
        props.setImgLoading(false); props.setCurSize(instance.curScaleIndex); props.setFixScreenSize(instance.fixScreenSize); }}); }, [imgUrl]);// ...
}
Copy the code

With the image instance canvasInstance, various operations on the preview image, such as zooming in and out, can be easily implemented by calling its own methods.

 

The screen size

As the screen size changes, we need to draw the image in real time according to the latest size. Here we write a custom Hooks that listen for screen size changes.

// src/components/photoGallery/index.tsx

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

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

    return (a)= >{
      window.removeEventListener('resize', onResize, false); }}, [])return size;
}
Copy the code

 

canvasDrawing flashing

There is another problem in the drawing process of canvas, flickering will occur during the screen resize process, as shown below:

This is because when repainting the cloth, we need to use clearRect to clear the canvas. At this time, the canvas is empty and it takes corresponding time to start repainting, so there will be a flash screen in the vision. To solve the flash screen, in fact, is how to solve the problem of drawing a long time.

We can refer to the concept of double cache to solve this problem and hand over the drawing process to the cache canvas, so that the drawing process of the canvas in the page is omitted, and the cache canvas is not added to the page, so we can’t see the drawing process. After the cache canvas is drawn, Assigning it directly to the original canvas of the page solves the splash screen problem.

// src/components/photoGallery/canvas.ts

class ImgToCanvas {
  // ...
  private cacheCanvas : any;
  private context : any;
  
  // ...private drawImg (type? : string) {// Canvas in the page
    const context = this.context;
    // ...
    
    // create a cacheCanvas and attach it to the instance property cacheCanvas
    if (!this.cacheCanvas) {
      this.cacheCanvas = document.createElement("canvas");
    }

    // Set the width of the cache canvas
    this.cacheCanvas.width = this.cWidth;
    this.cacheCanvas.height = this.cHeight;
    // Create canvas
    const tempCtx = this.cacheCanvas.getContext('2d')! ;// Use the cache canvas for drawing
    tempCtx.drawImage(image, this.imgLeft, this.imgTop, this.cImgWidth, this.cImgHeight);

    // Clear the canvas and assign the cache canvas to the page canvas
    requestAnimationFrame((a)= > {
      this.clearLastCanvas(context);
      context.drawImage(this.cacheCanvas, 0.0);
    })
    
    // ...}}Copy the code

 

summary

This article sorted out the implementation process of an imitates ink picture preview plug-in from zero to one, and elaborated a wave from the aspects of thinking analysis, code structure division, and the realization of the main logic.

Through the writing of this plug-in, the author has a general understanding of canvas drawing API, how to deal with the problem of flickering pictures during canvas drawing, and some usages of React Hooks.

Honestly, I want a thumbs up!

 

Refer to the content

  • Webpack learns documents
  • Use dual cache to solve Canvas clearRect flash screen problem
  • Implement class ANTD pager from scratch (3) : publish NPM