Как мне использовать массив для добавления списка отмен? - PullRequest
0 голосов
/ 26 апреля 2019

Я пытаюсь сделать программу рисования на JavaScript и хочу включить функцию отмены (не ластика). Как добавить все события в массив, а затем сделать возможным их удаление по одному?

У меня есть выпадающий список инструментов (пока работают только четыре). Я добавил кнопку отмены с идентификатором. Я часами пытался выяснить, как это сделать. Я нашел несколько примеров, и я думаю, что мне придется использовать как 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

Ответы [ 2 ]

1 голос
/ 26 апреля 2019

Перед тем, как вносить каждое изменение, вам необходимо отслеживать полное состояние картины, чтобы вы могли ее восстановить. Таким образом, у вас будет массив отмены, и всякий раз, когда вы изменяете холст, справа от до вы вносите изменение, вы помещаете текущее состояние холста в массив (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();
}
1 голос
/ 26 апреля 2019

Ваша текущая реализация не использует массив 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>

Это не очень эффективно, хотя.Он будет хранить полный битовый холст для каждого действия, поэтому он потребляет много памяти.Лучше всего, чтобы инструменты рисования создавали экземпляр формы, который можно вызвать для перерисовки на холсте по требованию.

...