Ваша текущая реализация не использует массив shapes
и не предоставляет возможности перерисовать их после того, как они были созданы.
Таким образом, проще всего сохранить каждое действие как растровое изображение.Итак, вам понадобится массив для растровых изображений, давайте назовем его:
var history = [];
После того, как что-то нарисовано, мы создаем снимок текущего холста и сохраняем его в этом массиве:
history.push(contextTmp.getImageData(0,0,canvasTmp.width,canvasTmp.height))
Если вы хотите отменить действие, вытолкните историю и нарисуйте последнее растровое изображение на холсте:
function cmbDeleteClick(){
history.pop()
contextTmp.putImageData(history[history.length-1],0,0)
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Paint</title>
<style type="text/css">
#content { position: relative; }
#cvs { border: 1px solid #c00; }
#cvsTmp { position: absolute; top: 1px; left: 1px; }
</style>
</head>
<body>
<p>
<label>
Object type:
<select id="selectTool">
<option value="line">Linje</option>
<option value="pencil">Blyant</option>
<option value="rect">Rektangel</option>
<option value="circle">Sirkel</option>
<option value="oval">Oval</option>
<option value="polygon">Polygon</option>
</select>
Shape drawn:
<select id="shapeDrawn">
<option value=""></option>
</select>
History:
<select id="historySelect">
</select>
<input type="button" id="cmbDelete" value="Undo last action">
</label>
</p>
<div id="content">
<canvas id="cvs" width="1024" height="512"></canvas>
</div>
<script type="text/javascript">
if(window.addEventListener) {
window.addEventListener('load', function () {
var canvas;
var context;
var canvasTmp;
var contextTmp;
var tool;
var toolDefault = 'line';
var cmbDelete = null;
var shapes = [];
var history = [];
var historySelect;
// Canvas and temp. canvas
function init () {
canvasTmp = document.getElementById('cvs');
if (!canvasTmp) {
return;
} if (!canvasTmp.getContext) {
return;
}
historySelect = document.getElementById('historySelect')
historySelect.addEventListener('change', ()=>{
restoreHistoryAction(historySelect.value)
})
contextTmp = canvasTmp.getContext('2d');
if (!contextTmp) {
return;
}
// Add the temporary canvas.
var content = canvasTmp.parentNode;
canvas = document.createElement('canvas');
if (!canvas) {
return;
}
canvas.id = 'cvsTmp';
canvas.width = canvasTmp.width;
canvas.height = canvasTmp.height;
content.appendChild(canvas);
context = canvas.getContext('2d');
// Get the tool select input.
var toolSelect = document.getElementById('selectTool');
if (!toolSelect) {
return;
}
toolSelect.addEventListener('change', ev_tool_change, false);
// Activate the default tool.
if (tools[toolDefault]) {
tool = new tools[toolDefault]();
toolSelect.value = toolDefault;
}
// Attach the mousedown, mousemove and mouseup event listeners.
canvas.addEventListener('mousedown', evMouse, false);
canvas.addEventListener('mousemove', evMouse, false);
canvas.addEventListener('mouseup', evMouse, false);
drawCanvas()
}
function evMouse (ev) {
if (ev.layerX || ev.layerX == 0) {
ev._x = ev.layerX;
ev._y = ev.layerY;
}
var evHandler = tool[ev.type];
if (evHandler) {
evHandler(ev);
}
}
// The event handler for any changes made to the tool selector.
function toolChange (ev) {
if (tools[this.value]) {
tool = new tools[this.value]();
}
}
// Updates Canvas on interval timeout
function drawCanvas() {
contextTmp.drawImage(canvas, 0, 0);
history.push(contextTmp.getImageData(0,0,canvasTmp.width,canvasTmp.height))
updateHistorySelection()
context.clearRect(0, 0, canvas.width, canvas.height);
}
function ev_tool_change (ev) {
if (tools[this.value]) {
tool = new tools[this.value]();
}
}
// Get excact position for mouse coordinates in canvas
function mouseAction (ev) {
if (ev.layerX || ev.layerX == 0) {
ev._x = ev.layerX;
ev._y = ev.layerY;
}
// Call the event handler of the tool.
var func = tool[ev.type];
if (func) {
func(ev);
}
}
function selectShapeChange(){
drawCanvas();
}
var tools = {};
// The drawing pencil.
tools.pencil = function () {
var tool = this;
this.started = false;
this.mousedown = function (ev) {
context.beginPath();
context.moveTo(ev._x, ev._y);
tool.started = true;
};
this.mousemove = function (ev) {
if (tool.started) {
context.lineTo(ev._x, ev._y);
context.stroke();
}
};
this.mouseup = function (ev) {
if (tool.started) {
tool.mousemove(ev);
tool.started = false;
drawCanvas();
}
};
};
// The rectangle tool.
tools.rect = function () {
var tool = this;
this.started = false;
this.mousedown = function (ev) {
tool.started = true;
tool.x0 = ev._x;
tool.y0 = ev._y;
};
this.mousemove = function (ev) {
if (!tool.started) {
return;
}
var x = Math.min(ev._x, tool.x0),
y = Math.min(ev._y, tool.y0),
w = Math.abs(ev._x - tool.x0),
h = Math.abs(ev._y - tool.y0);
context.clearRect(0, 0, canvas.width, canvas.height);
if (!w || !h) {
return;
}
context.fillRect(x, y, w, h);
context.fillStyle = 'hsl(' + 360 * Math.random() + ', 50%, 50%)';
};
this.mouseup = function (ev) {
if (tool.started) {
tool.mousemove(ev);
tool.started = false;
drawCanvas();
}
};
};
// The line tool.
tools.line = function () {
var tool = this;
this.started = false;
this.mousedown = function (ev) {
tool.started = true;
tool.x0 = ev._x;
tool.y0 = ev._y;
};
this.mousemove = function (ev) {
if (!tool.started) {
return;
}
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.moveTo(tool.x0, tool.y0);
context.lineTo(ev._x, ev._y);
context.stroke();
context.closePath();
};
this.mouseup = function (ev) {
if (tool.started) {
tool.mousemove(ev);
tool.started = false;
drawCanvas();
}
};
};
// Circle tool
tools.circle = function () {
var tool = this;
this.started = false;
this.mousedown = function (ev) {
tool.started = true;
tool.x0 = ev._x;
tool.y0 = ev._y;
};
this.mousemove = function (ev) {
if (!tool.started) {
return;
}
context.clearRect(0, 0, canvas.width, canvas.height);
var radius = Math.max(
Math.abs(ev._x - tool.x0),
Math.abs(ev._y - tool.y0)
) / 2;
var x = Math.min(ev._x, tool.x0) + radius;
var y = Math.min(ev._y, tool.y0) + radius;
context.beginPath();
context.arc(x, y, radius, 0, Math.PI*2, false);
// context.arc(x, y, 5, 0, Math.PI*2, false);
context.stroke();
context.closePath();
};
this.mouseup = function (ev) {
if (tool.started) {
tool.mousemove(ev);
tool.started = false;
drawCanvas();
}
};
};
// Ellipse/oval tool
// Polygon tool
// Undo button
function cmbDeleteClick(){
if(history.length<=1)
return
history.pop()
contextTmp.putImageData(history[history.length-1],0,0)
updateHistorySelection()
}
function updateHistorySelection(){
historySelect.innerHTML = ''
history.forEach((entry,index)=>{
let option = document.createElement('option')
option.value = index
option.textContent = index===0 ? 'Beginning' : 'Action '+index
historySelect.appendChild(option)
})
historySelect.selectedIndex = history.length-1
}
function restoreHistoryAction(index){
contextTmp.putImageData(history[index],0,0)
}
cmbDelete = document.getElementById("cmbDelete");
cmbDelete.addEventListener("click",cmbDeleteClick, false);
init();
}, false); }
</script>
</body>
</html>
Это не очень эффективно, хотя.Он будет хранить полный битовый холст для каждого действия, поэтому он потребляет много памяти.Лучше всего, чтобы инструменты рисования создавали экземпляр формы, который можно вызвать для перерисовки на холсте по требованию.