- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
在我尝试使用通过 React 实现的 HTML5 Canvas 创建手绘图之后,我想继续添加撤消和重做功能 onclick 分别单击撤消和重做按钮。如果能提供任何帮助,我将不胜感激。
function App(props) {
const canvasRef = useRef(null);
const contextRef = useRef(null);
const [isDrawing, setIsDrawing] = useState(false);
useEffect(() => {
const canvas = canvasRef.current;
canvas.width = window.innerWidth * 2;
canvas.height = window.innerHeight * 2;
canvas.style.width = `${window.innerWidth}px`;
canvas.style.height = `${window.innerHeight}px`;
const context = canvas.getContext('2d');
context.scale(2, 2);
context.lineCap = 'round';
context.strokeStyle = 'black';
context.lineWidth = 5;
contextRef.current = context;
}, []);
const startDrawing = ({ nativeEvent }) => {
const { offsetX, offsetY } = nativeEvent;
contextRef.current.beginPath();
contextRef.current.moveTo(offsetX, offsetY);
setIsDrawing(true);
};
const finishDrawing = () => {
contextRef.current.closePath();
setIsDrawing(false);
};
const draw = ({ nativeEvent }) => {
if (!isDrawing) {
return;
}
const { offsetX, offsetY } = nativeEvent;
contextRef.current.lineTo(offsetX, offsetY);
contextRef.current.stroke();
};
return <canvas onMouseDown={startDrawing} onMouseUp={finishDrawing} onMouseMove={draw} ref={canvasRef} />;
}
最佳答案
您有多种选择。
A 将鼠标松开后用于渲染每个笔划的所有点保存在缓冲区(数组)中。要撤消,请清除 Canvas 并重新绘制所有笔划直至适当的撤消位置。要重做,只需在撤消缓冲区中绘制下一个笔画即可。
注意这种方法需要一个无限大(远大于所有可能的笔划)的撤消缓冲区,否则它将不起作用。
B 在鼠标向上时保存 Canvas 像素并存储在缓冲区中。不要使用 getImageData因为缓冲区未压缩并且会很快消耗大量内存。而是将像素数据存储为 blob或 DataURL .默认图像格式是 PNG,它是无损和压缩的,因此大大减少了所需的 RAM。要撤消/重做,请清除 Canvas ,创建图像并将源设置为适当撤消位置的 blob 或 dataURL。加载图像后,将其绘制到 Canvas 上。
注意必须撤销 blob,因此撤消缓冲区必须确保在丢失引用之前撤销任何已删除的引用。
C 以上两种方法的结合。保存笔画并时常保存像素。
您可以实现一个通用的撤销缓冲区对象来存储任何数据。
撤消缓冲区独立于 react 的状态
示例片段显示了它的使用方式。
注意 undo 函数接受参数all
如果这是真的那么调用undo 返回从第一次更新到当前位置- 1< 的所有缓冲区
。如果您需要重建图像,这是必需的。
function UndoBuffer(maxUndos = Infinity) {
const buffer = [];
var position = 0;
const API = {
get canUndo() { return position > 0 },
get canRedo() { return position < buffer.length },
update(data) {
if (position === maxUndos) {
buffer.shift();
position--;
}
if (position < buffer.length) { buffer.length = position }
buffer.push(data);
position ++;
},
undo(all = true) {
if (API.canUndo) {
if (all) {
const buf = [...buffer];
buf.length = --position;
return buf;
}
return buffer[--position];
}
},
redo() {
if (API.canRedo) { return buffer[position++] }
},
};
return API;
}
使用上面的 UndoBuffer
来使用缓冲笔划实现撤消和重做。
const ctx = canvas.getContext("2d");
undo.addEventListener("click", undoDrawing);
redo.addEventListener("click", redoDrawing);
const undoBuffer = UndoBuffer();
updateUndo();
function createImage(w, h){
const can = document.createElement("canvas");
can.width = w;
can.height = h;
can.ctx = can.getContext("2d");
return can;
}
const drawing = createImage(canvas.width, canvas.height);
const mouse = {x : 0, y : 0, button : false, target: canvas};
function mouseEvents(e){
var updateTarget = false
if (mouse.target === e.target || mouse.button) {
mouse.x = e.pageX;
mouse.y = e.pageY;
if (e.type === "mousedown") { mouse.button = true }
updateTarget = true;
}
if (e.type === "mouseup" && mouse.button) {
mouse.button = false;
updateTarget = true;
}
updateTarget && update(e.type);
}
["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
const stroke = [];
function drawStroke(ctx, stroke, r = false) {
var i = 0;
ctx.lineWidth = 5;
ctx.lineCap = ctx.lineJoin = "round";
ctx.strokeStyle = "black";
ctx.beginPath();
while (i < stroke.length) { ctx.lineTo(stroke[i++],stroke[i++]) }
ctx.stroke();
}
function updateView() {
ctx.globalCompositeOperation = "copy";
ctx.drawImage(drawing, 0, 0);
ctx.globalCompositeOperation = "source-over";
}
function update(event) {
var i = 0;
if (mouse.button) {
updateView()
stroke.push(mouse.x - 1, mouse.y - 29);
drawStroke(ctx, stroke);
}
if (event === "mouseup") {
drawing.ctx.globalCompositeOperation = "copy";
drawing.ctx.drawImage(canvas, 0, 0);
drawing.ctx.globalCompositeOperation = "source-over";
addUndoable(stroke);
stroke.length = 0;
}
}
function updateUndo() {
undo.disabled = !undoBuffer.canUndo;
redo.disabled = !undoBuffer.canRedo;
}
function undoDrawing() {
drawing.ctx.clearRect(0, 0, drawing.width, drawing.height);
undoBuffer.undo(true).forEach(stroke => drawStroke(drawing.ctx, stroke, true));
updateView();
updateUndo();
}
function redoDrawing() {
drawStroke(drawing.ctx, undoBuffer.redo());
updateView();
updateUndo();
}
function addUndoable(data) {
undoBuffer.update([...data]);
updateUndo();
}
function UndoBuffer(maxUndos = Infinity) {
const buffer = [];
var position = 0;
const API = {
get canUndo() { return position > 0 },
get canRedo() { return position < buffer.length },
update(data) {
if (position === maxUndos) {
buffer.shift();
position--;
}
if (position < buffer.length) { buffer.length = position }
buffer.push(data);
position ++;
},
reset() { position = buffer.length = 0 },
undo(all = true) {
if (API.canUndo) {
if (all) {
const buf = [...buffer];
buf.length = --position;
return buf;
}
return buffer[--position];
}
},
redo() {
if (API.canRedo) { return buffer[position++] }
},
};
return API;
}
canvas {
position : absolute;
top : 28px;
left : 0px;
border: 1px solid black;
}
button {
position : absolute;
top: 4px;
}
#undo {
left: 4px;
}
#redo {
left: 60px;
}
<canvas id="canvas"></canvas>
<button id="undo">Undo</button>
<button id="redo">Redo</button>
关于javascript - Reactjs 中的 Canvas 手绘撤消和重做功能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64611155/
我是一名优秀的程序员,十分优秀!