从画布中删除最后绘制的对象。

4

我有一个任务,需要在点击画布(PDF)的区域上放置矩形。我正在使用React,在使用react-pdf模块上传pdf文件后,该文件被转换为canvas元素。我想要在多次点击后删除先前绘制的矩形,以便矩形会更改位置,而不会在屏幕上重复出现。到目前为止,我尝试过以下方法:

  1. After I choose pdf file that file is being translated into canvas and viewed on page using react-pdf module that I mentioned earlier

                            <Document
                                className={classes.pdf_document}
                                file={file}
                                onLoadSuccess={handleOnPdfLoad}                      
                            >
                                <Page
                                    onClick={drawRectangle}
                                    width={400}
                                    pageNumber={currentPage}>
                                </Page>
                            </Document>
    
  2. drawRectangle function is drawing red rectangle on clicked area

    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. I also have rectDrawn that is true or false

    const [rectDrawn, setRectDrawn] = React.useState(false);
    
  4. When marker.clearRect happens red rectangle reappears on newly clicked area but I loose all other pdf data on that canvas ( text and everything else ) it just becomes blank.

1个回答

1
以下是关于如何从画布中获取一个带注释的 ImageData 矩形,并将其存储在 React 状态中,然后就地还原到画布的自包含示例:

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>


谢谢您的回答,但如果您能再澄清一下,我将不胜感激。提前致谢。 - mne_web_dev
@mne_web_dev 我已经为你编写了一个带有注释的工作示例,并将其添加到我的答案中。 - jsejcksn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接