从 canvas 中移除最后绘制的对象

Remove last drawn object from canvas

我有一个任务需要在我点击 cavnas (PDF) 的区域放置矩形。我正在使用 React,在我使用 react-pdf 模块上传 pdf 文件后,该文件被翻译成 canvas 元素。我想在多次单击后删除以前绘制的矩形,以便矩形将改变它不会在屏幕上重复的位置。到目前为止我尝试的是:

  1. 在我选择 pdf 文件后,该文件被翻译成 canvas 并使用我之前提到的 react-pdf 模块在页面上查看

                            <Document
                                className={classes.pdf_document}
                                file={file}
                                onLoadSuccess={handleOnPdfLoad}                      
                            >
                                <Page
                                    onClick={drawRectangle}
                                    width={400}
                                    pageNumber={currentPage}>
                                </Page>
                            </Document>
    
  2. drawRectangle 函数正在点击区域绘制红色矩形

    const setCoordinatesOnClick = (e) => {
    
    const rect = e.target.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    
     const marker = e.target.getContext("2d");
    
        const drawRect = () => {
            marker.beginPath();
            marker.lineWidth = "3";
            marker.strokeStyle = "red";
            marker.strokeRect(x, y, 70, 50);
            marker.stroke();
        }
    
        if (!rectDrawn) {
            drawRect();
            setRectDrawn(true);
        } else {
            marker.clearRect(0, 0, e.target.width, e.target.height);
            drawRect();
        }
    }
    
  3. 我还有rectDrawn是真还是假

    const [rectDrawn, setRectDrawn] = React.useState(false);
    
  4. marker.clearRect 发生红色矩形重新出现在新点击的区域但我丢失了那个 canvas 上的所有其他 pdf 数据(文本和其他一切)它只是变成空白。

这是一个自包含的注释示例,演示如何从 canvas 获取 ImageData 的矩形,将其存储在 React 状态,然后将其恢复到 canvas就地:

I used TypeScript when creating the example, but I manually removed all of the type information in the snippet below in case it might be confusing to you (your question didn't indicate that you are using TypeScript). However, in case you're interested in the typed version, you can view it at this TS Playground link.

<div id="root"></div><script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/@babel/standalone@7.16.4/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">

const {useEffect, useRef, useState} = React;

/** This function is just for having an example image for this demo */
async function loadInitialImageData (ctx) {
  const img = new Image();
  img.crossOrigin = 'anonymous';
  img.src = 'https://i.imgur.com/KeiVCph.jpg'; // 720px x 764px
  img.addEventListener('load', () => ctx.drawImage(img, 0, 0, 360, 382));
}

// Reusable utility/helper functions:

/** For making sure the context isn't `null` when trying to access it */
function assertIsContext (ctx) {
  if (!ctx) throw new Error('Canvas context not found');
}

/** 
 * Calculate left (x), and top (y) coordinates from a mouse click event
 * on a `<canvas>` element. If `centered` is `true` (default), the center of
 * the reactangle will be at the mouse click position, but if `centered` is
 * `false`, the top left corner of the rect will be at the click position
 */
function getXYCoords (ev, {h = 0, w = 0, centered = true} = {}) {
  const ctx = ev.target.getContext('2d');
  assertIsContext(ctx);
  const rect = ctx.canvas.getBoundingClientRect();
  const scaleX = ctx.canvas.width / rect.width;
  const scaleY = ctx.canvas.height / rect.height;
  const x = (ev.clientX - rect.left - (centered ? (w / 2) : 0)) * scaleX;
  const y = (ev.clientY - rect.top - (centered ? (h / 2) : 0)) * scaleY;
  return [x, y];
}

/** 
 * Draw the actual rectangle outline.
 * The stroke is always drawn on the **outside** of the rectangle:
 * This is, unfortunately, not configurable.
 */
function strokeRect (ctx, options): void {
  ctx.lineWidth = options.lineWidth;
  ctx.strokeStyle = options.strokeStyle;
  ctx.strokeRect(...options.dimensions);
}

/**
 * Calculates dimensions of a rectangle including optional XY offset values.
 * This is to accommodate for the fact that strokes are always drawn on the
 * outside of a rectangle.
 */
function getOffsetRect (x, y, w, h, xOffset = 0, yOffset = 0) {
  x -= xOffset;
  y -= yOffset;
  w += xOffset * 2;
  h += yOffset * 2;
  return [x, y, w, h];
}

/** Example component for this demo */
function Example () {
  // This might be useful to you, but is only used here when initially loading the demo image
  const canvasRef = useRef(null);

  // This will hold a closure (function) that will restore the original image data.
  // Initalize with empty function:
  const [restoreImageData, setRestoreImageData] = useState(() => () => {});

  // This one-time call to `useEffect` is just for having an example image for this demo
  useEffect(() => {
    const ctx = canvasRef.current?.getContext('2d') ?? null;
    assertIsContext(ctx);
    loadInitialImageData(ctx);
  }, []);

  // This is where all the magic happens:
  const handleClick = (ev) => {
    const ctx = ev.target.getContext('2d');
    assertIsContext(ctx);

    // You defined these width and height values statically in your question,
    // but you could also store these in React state to use them dynamically:
    const w = 70;
    const h = 50;

    // Use the helper function to get XY coordinates:
    const [x, y] = getXYCoords(ev, {h, w});

    // Again, these are static in your question, but could be in React state:
    const lineWidth = 3;
    const strokeRectOpts = {
      lineWidth,
      strokeStyle: 'red',
      dimensions: [x, y, w, h],
    };

    // Use a helper function again to calculate the offset rectangle dimensions:
    const expanded = getOffsetRect(x, y, w, h, lineWidth, lineWidth);

    // Restore the previous image data from the offset rectangle
    restoreImageData();

    // Get the new image data from the offset rectangle:
    const imageData = ctx.getImageData(...expanded);

    // Use the image data in a closure which will restore it when invoked later,
    // and put it into React state:
    setRestoreImageData(() => () => ctx.putImageData(imageData, expanded[0], expanded[1]));

    // Finally, draw the rectangle stroke:
    strokeRect(ctx, strokeRectOpts);
  };

  return (
    <div style={{border: '1px solid black', display: 'inline-block'}}>
      <canvas
        ref={canvasRef}
        onClick={handleClick}
        width="360"
        height="382"
      ></canvas>
    </div>
  );
}

ReactDOM.render(<Example />, document.getElementById('root'));

</script>