我正在尝试使用JavaScript编写绘画程序,并且我想包括一个撤消功能(而不是橡皮擦)。如何将所有事件添加到数组中,然后可以逐个删除它们?
我有一个下拉列表的工具(到目前为止,只有四个在工作)。我添加了带有ID的撤消按钮。我已经尝试了几个小时(实际上是几天),以了解如何做到这一点。我找到了一些示例,我想我必须同时使用push和空数组才能进一步?
这是工具选择和按钮的代码
<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>
<input type="button" id="cmbDelete" value="Undo last action">
</label>
撤消功能可能是这样的,但是这个功能
var shapes = [];
shapes.push(newShape);
function cmbDeleteClick(){
if(shapes.length > 0){
var selectedShapeIndex = selectShape.selectedIndex;
shapes.splice(selectedShapeIndex,1);
selectShape.options.remove(selectedShapeIndex);
selectShape.selectedIndex = selectShape.options.length - 1;
}
cmbDelete = document.getElementById("cmbDelete");
cmbDelete.addEventListener("click",cmbDeleteClick, false);
fillSelectShapeTypes();
drawCanvas();
}
理想情况下,将画布上绘制的所有内容都添加到下拉菜单中,并且可以通过单击按钮将其删除(撤消)。这是代码JS Bin
的“有效”版本答案 0 :(得分:1)
您当前的实现未利用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>
这不是很有效。它将为每个动作存储画布的完整位图,因此非常消耗内存。最好实际使用绘图工具创建一个形状实例,可以根据需要调用该形状实例以在画布上重画。
答案 1 :(得分:1)
您需要做的是在进行每次更改之前跟踪绘画的完整状态,以便您可以还原它。因此,您将拥有一个undo数组,并且每当修改画布时,就在之前进行修改,然后将当前画布状态推送到数组中(canvas.toDataURL对封装画布非常有帮助。整个图片状态)。然后进行修改。
撤消操作时,可以从撤消数组中弹出最后一个元素,该元素将是画布上最后一次更改之前的数据URL,然后将画布重置为该图像。像这样:
function undoLastChange() {
const canvas = document.getElementById('canvas_ID');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0);
};
img.src = undoArray.pop();
}