如何为HTML Canvas实现撤销(Undo)与重做(Redo)功能?
Fixing Undo/Redo for Your Canvas Drawing App
Hey there! No worries about your English at all—let's get that undo/redo functionality working for your canvas app. Looking at your code, I notice a few key pieces missing that are keeping things from working as expected. Let's walk through the fixes step by step.
The Core Issues in Your Current Code
- You're not actually storing the drawing paths anywhere (the
pointsvariable isn't defined or populated) - The
redrawAll()function is missing entirely—this is critical for rebuilding the canvas when undoing/redoing - Your current drawing logic draws directly to the canvas without saving path data, so you can't "undo" individual strokes later
Updated Full Code with Working Undo/Redo
First, let's update your HTML to hook up the redo button:
<th><img src="images/undo.png" onclick="undoLastPoint()"></th> <th><img src="images/redo.png" onclick="redoLastPoint()"></th>
Now here's the revised JavaScript with all the necessary pieces:
var canvas = document.getElementById('mycanvas'); var ctx = canvas.getContext('2d'); var isDrawing = false; // Store all completed drawing strokes var drawingHistory = []; // Store strokes that were undone (for redo) var redoStack = []; // Track the current stroke being drawn var currentStroke = []; function pencil() { canvas.onmousedown = function(e) { var pos = getMousePos(canvas, e); isDrawing = true; // Start a new stroke currentStroke = [{x: pos.x, y: pos.y}]; ctx.beginPath(); ctx.moveTo(pos.x, pos.y); }; canvas.onmousemove = function(e) { var pos = getMousePos(canvas, e); if (isDrawing) { // Add the current point to the active stroke currentStroke.push({x: pos.x, y: pos.y}); ctx.lineTo(pos.x, pos.y); ctx.stroke(); } }; canvas.onmouseup = function() { if (isDrawing) { isDrawing = false; // Save the completed stroke to history drawingHistory.push(currentStroke); // Clear redo stack when a new stroke is drawn redoStack = []; currentStroke = []; } }; } function undoLastPoint() { if (drawingHistory.length > 0) { // Move the last stroke from history to redo stack var undoneStroke = drawingHistory.pop(); redoStack.push(undoneStroke); // Redraw the canvas with remaining strokes redrawAll(); } } function redoLastPoint() { if (redoStack.length > 0) { // Move the last undone stroke back to history var redoneStroke = redoStack.pop(); drawingHistory.push(redoneStroke); // Redraw the canvas with restored strokes redrawAll(); } } function redrawAll() { // Clear the entire canvas first ctx.clearRect(0, 0, canvas.width, canvas.height); // Redraw every stroke in the history drawingHistory.forEach(stroke => { if (stroke.length > 0) { ctx.beginPath(); ctx.moveTo(stroke[0].x, stroke[0].y); stroke.forEach(point => { ctx.lineTo(point.x, point.y); }); ctx.stroke(); } }); } function getMousePos(canvas, evt) { var rect = canvas.getBoundingClientRect(); return { x: evt.clientX - rect.left, y: evt.clientY - rect.top }; }
Key Changes Explained
- Tracking Strokes: We now use
drawingHistoryto store every completed stroke (each stroke is an array of points).currentStroketracks the line you're actively drawing. - Redraw Logic:
redrawAll()clears the canvas and redraws every stroke from the history—this is how undo/redo "resets" the canvas to the correct state. - Undo/Redo Flow:
- When you undo, we take the last stroke from
drawingHistoryand move it toredoStack, then redraw. - When you redo, we take the last stroke from
redoStackand move it back todrawingHistory, then redraw. - Any new drawing clears the redo stack (since you can't redo after starting a new stroke—this matches standard undo/redo behavior).
- When you undo, we take the last stroke from
Just call your pencil() function to initialize the drawing tool, and your undo/redo buttons should work perfectly now!
内容的提问来源于stack exchange,提问作者wendykr




