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

Posted

技术标签:

【中文标题】从画布中删除最后绘制的对象【英文标题】:Remove last drawn object from canvas 【发布时间】:2022-01-15 21:35:36 【问题描述】:

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

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

                            <Document
                                className=classes.pdf_document
                                file=file
                                onLoadSuccess=handleOnPdfLoad                      
                            >
                                <Page
                                    onClick=drawRectangle
                                    width=400
                                    pageNumber=currentPage>
                                </Page>
                            </Document>
    

    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();
        
    
    

    我也有 rectDrawn 是真还是假

    const [rectDrawn, setRectDrawn] = React.useState(false);
    

    ma​​rker.clearRect 发生时,红色矩形重新出现在新点击的区域,但我丢失了该画布上的所有其他 pdf 数据(文本和其他所有内容),它就变成了空白。

【问题讨论】:

【参考方案1】:

这是一个自包含和注释的示例,演示如何从画布中获取ImageData 的矩形,将其存储为 React 状态,然后将其就地还原到画布:

我在创建示例时使用了 TypeScript,但我手动删除了下面 sn-p 中的所有类型信息,以防您混淆(您的问题并未表明您使用的是 TypeScript)。但是,如果您对打字版本感兴趣,可以通过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
        
        
      ></canvas>
    </div>
  );


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

</script>

【讨论】:

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

以上是关于从画布中删除最后绘制的对象的主要内容,如果未能解决你的问题,请参考以下文章

flutter画布绘制图片和文字

使用 javafx 创建 Truetype 字体文件

画布绘制对象的鼠标事件

画布可绘制对象和居中缩放

如何从 jpeg 或 png 格式的字节数组在画布上绘制图像

删除 tkinter 画布上的所有元素并重新绘制,还是使用 move 更新元素?