从画布中删除最后绘制的对象
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);
当 marker.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 的工作示例并将其添加到我的答案中。以上是关于从画布中删除最后绘制的对象的主要内容,如果未能解决你的问题,请参考以下文章