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

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

  1. 在我选择 pdf 文件后,该文件被翻译成画布并使用我之前提到的 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. ma​​rker.clearRect 发生时,红色矩形重新出现在新点击的区域,但我丢失了该画布上的所有其他 pdf 数据(文本和其他所有内容),它就变成了空白。

【问题讨论】:

    标签: html reactjs canvas


    【解决方案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}
            width="360"
            height="382"
          ></canvas>
        </div>
      );
    }
    
    ReactDOM.render(<Example />, document.getElementById('root'));
    
    </script>

    【讨论】:

    • 感谢您的回答,但如果您能再澄清一点,我将不胜感激。提前谢谢你。
    • @mne_web_dev 我已经为您编写了一个带有 cmets 的工作示例并将其添加到我的答案中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多