Как реализовать функцию отмены для холста HTML с помощью JavaScript?

Я делаю приложение для рисования в HTML, используя JavaScript и Flask-Python. В настоящее время я могу без проблем рисовать множество карандашных рисунков и фигур, таких как прямоугольники/круги. Следующая функциональность, которую я пытаюсь реализовать для этого приложения, — это функция отменить.

Я храню штрихи и данные рисования холста в объекте JS следующим образом:

canvas_data = { "pencil": [], "line": [], "rectangle": [], "circle": [], "eraser": [], "last_action": -1 };

Все названия клавиш должны быть понятными, кроме last_action. Я использую эту переменную last_action, чтобы узнать последнюю категорию, использованную пользователем, чтобы позже я мог использовать эту информацию для реализации функции отмены.

var canvas = document.getElementById("paint");

var ctx = canvas.getContext("2d");

var pi2 = Math.PI * 2;
var resizerRadius = 8;
var rr = resizerRadius * resizerRadius;

var width = canvas.width;
var height = canvas.height;
var curX, curY, prevX, prevY;
var hold = false;
ctx.lineWidth = 2;
var fill_value = true;
var stroke_value = false;
var canvas_data = {
  "pencil": [],
  "line": [],
  "rectangle": [],
  "circle": [],
  "eraser": [],
  "last_action": -1
};

// //connect to postgres client
// var pg = require('pg');
// var conString = "postgres://postgres:database1@localhost:5432/sketch2photo";
// client = new pg.Client(conString);


function color(color_value) {
  ctx.strokeStyle = color_value;
  ctx.fillStyle = color_value;
}

function add_pixel() {
  ctx.lineWidth += 1;
}

function reduce_pixel() {
  if (ctx.lineWidth == 1) {
    ctx.lineWidth = 1;
  } else {
    ctx.lineWidth -= 1;
  }
}

function fill() {
  fill_value = true;
  stroke_value = false;
}

function outline() {
  fill_value = false;
  stroke_value = true;
}

function reset() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  canvas_data = {
    "pencil": [],
    "line": [],
    "rectangle": [],
    "circle": [],
    "eraser": [],
    "last_action": -1
  };
}

// pencil tool

function pencil(data, targetX, targetY, targetWidth, targetHeight) {

  canvas.onmousedown = function(e) {
    curX = e.clientX - canvas.offsetLeft;
    curY = e.clientY - canvas.offsetTop;
    hold = true;

    prevX = curX;
    prevY = curY;
    ctx.beginPath();
    ctx.moveTo(prevX, prevY);
  };

  canvas.onmousemove = function(e) {
    if (hold) {
      curX = e.clientX - canvas.offsetLeft;
      curY = e.clientY - canvas.offsetTop;
      draw();
    }
  };

  canvas.onmouseup = function(e) {
    hold = false;
  };

  canvas.onmouseout = function(e) {
    hold = false;
  };

  function draw() {
    ctx.lineTo(curX, curY);
    ctx.stroke();
    canvas_data.pencil.push({
      "startx": prevX,
      "starty": prevY,
      "endx": curX,
      "endy": curY,
      "thick": ctx.lineWidth,
      "color": ctx.strokeStyle
    });
    canvas_data.last_action = 0;
  }
}

// line tool

function line() {

  canvas.onmousedown = function(e) {
    img = ctx.getImageData(0, 0, width, height);
    prevX = e.clientX - canvas.offsetLeft;
    prevY = e.clientY - canvas.offsetTop;
    hold = true;
  };

  canvas.onmousemove = function linemove(e) {
    if (hold) {
      ctx.putImageData(img, 0, 0);
      curX = e.clientX - canvas.offsetLeft;
      curY = e.clientY - canvas.offsetTop;
      ctx.beginPath();
      ctx.moveTo(prevX, prevY);
      ctx.lineTo(curX, curY);
      ctx.stroke();
      canvas_data.line.push({
        "startx": prevX,
        "starty": prevY,
        "endx": curX,
        "endY": curY,
        "thick": ctx.lineWidth,
        "color": ctx.strokeStyle
      });
      ctx.closePath();
      canvas_data.last_action = 1;
    }
  };

  canvas.onmouseup = function(e) {
    hold = false;
  };

  canvas.onmouseout = function(e) {
    hold = false;
  };
}

// rectangle tool

function rectangle() {

  canvas.onmousedown = function(e) {
    img = ctx.getImageData(0, 0, width, height);
    prevX = e.clientX - canvas.offsetLeft;
    prevY = e.clientY - canvas.offsetTop;
    hold = true;
  };

  canvas.onmousemove = function(e) {
    if (hold) {
      ctx.putImageData(img, 0, 0);
      curX = e.clientX - canvas.offsetLeft - prevX;
      curY = e.clientY - canvas.offsetTop - prevY;
      ctx.strokeRect(prevX, prevY, curX, curY);
      if (fill_value) {
        ctx.fillRect(prevX, prevY, curX, curY);
      }
      canvas_data.rectangle.push({
        "startx": prevX,
        "starty": prevY,
        "width": curX,
        "height": curY,
        "thick": ctx.lineWidth,
        "stroke": stroke_value,
        "stroke_color": ctx.strokeStyle,
        "fill": fill_value,
        "fill_color": ctx.fillStyle
      });
      canvas_data.last_action = 2;

    }
  };

  canvas.onmouseup = function(e) {
    hold = false;
  };

  canvas.onmouseout = function(e) {
    hold = false;
  };
}

// circle tool

function circle() {

  canvas.onmousedown = function(e) {
    img = ctx.getImageData(0, 0, width, height);
    prevX = e.clientX - canvas.offsetLeft;
    prevY = e.clientY - canvas.offsetTop;
    hold = true;
  };

  canvas.onmousemove = function(e) {
    if (hold) {
      ctx.putImageData(img, 0, 0);
      curX = e.clientX - canvas.offsetLeft;
      curY = e.clientY - canvas.offsetTop;
      ctx.beginPath();
      ctx.arc(Math.abs(curX + prevX) / 2, Math.abs(curY + prevY) / 2, Math.sqrt(Math.pow(curX - prevX, 2) + Math.pow(curY - prevY, 2)) / 2, 0, Math.PI * 2, true);
      ctx.closePath();
      ctx.stroke();
      if (fill_value) {
        ctx.fill();
      }
      canvas_data.circle.push({
        "startx": prevX,
        "starty": prevY,
        "radius": curX - prevX,
        "thick": ctx.lineWidth,
        "stroke": stroke_value,
        "stroke_color": ctx.strokeStyle,
        "fill": fill_value,
        "fill_color": ctx.fillStyle
      });
      canvas_data.last_action = 3;
    }
  };

  canvas.onmouseup = function(e) {
    hold = false;
  };

  canvas.onmouseout = function(e) {
    hold = false;
  };
}

// eraser tool

function eraser() {

  canvas.onmousedown = function(e) {
    curX = e.clientX - canvas.offsetLeft;
    curY = e.clientY - canvas.offsetTop;
    hold = true;

    prevX = curX;
    prevY = curY;
    ctx.beginPath();
    ctx.moveTo(prevX, prevY);
  };

  canvas.onmousemove = function(e) {
    if (hold) {
      curX = e.clientX - canvas.offsetLeft;
      curY = e.clientY - canvas.offsetTop;
      draw();
    }
  };

  canvas.onmouseup = function(e) {
    hold = false;
  };

  canvas.onmouseout = function(e) {
    hold = false;
  };

  function draw() {
    ctx.lineTo(curX, curY);
    var curr_strokeStyle = ctx.strokeStyle;
    ctx.strokeStyle = "#ffffff";
    ctx.stroke();
    canvas_data.pencil.push({
      "startx": prevX,
      "starty": prevY,
      "endx": curX,
      "endy": curY,
      "thick": ctx.lineWidth,
      "color": ctx.strokeStyle
    });
    canvas_data.last_action = 4;
    ctx.strokeStyle = curr_strokeStyle;
  }
}


// Function to undo the last action by the user 
function undo_pixel() {
  // Print that function has been called 
  console.info("undo_pixel() called");
  // Print the last action that was performed
  console.info(canvas_data.last_action);

  switch (canvas_data.last_action) {
    case 0:
    case 4:
      console.info("Case 0 or 4");
      canvas_data.pencil.pop();
      canvas_data.last_action = -1;
      break;
    case 1:
      //Undo the last line drawn
      console.info("Case 1");
      canvas_data.line.pop();
      canvas_data.last_action = -1;
      break;
    case 2:
      //Undo the last rectangle drawn
      console.info("Case 2");
      canvas_data.rectangle.pop();
      canvas_data.last_action = -1;
      break;
    case 3:
      //Undo the last circle drawn
      console.info("Case 3");
      canvas_data.circle.pop();
      canvas_data.last_action = -1;
      break;

    default:
      break;

  }
  // Redraw the canvas
  redraw_canvas();

}

// Function to redraw all the shapes on the canvas 
function redraw_canvas() {
  // Redraw all the shapes on the canvas 
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // Redraw the pencil data 
  canvas_data.pencil.forEach(function(p) {
    ctx.beginPath();
    ctx.moveTo(p.startx, p.starty);
    ctx.lineTo(p.endx, p.endy);
    ctx.lineWidth = p.thick;
    ctx.strokeStyle = p.color;
    ctx.stroke();
  });
  // Redraw the line data
  canvas_data.line.forEach(function(l) {
    ctx.beginPath();
    ctx.moveTo(l.startx, l.starty);
    ctx.lineTo(l.endx, l.endy);
    ctx.lineWidth = l.thick;
    ctx.strokeStyle = l.color;
    ctx.stroke();
  });
  // Redraw the rectangle data
  canvas_data.rectangle.forEach(function(r) {
    ctx.beginPath();
    ctx.rect(r.startx, r.starty, r.width, r.height);
    ctx.lineWidth = r.thick;
    ctx.strokeStyle = r.color;
    if (r.fill) {
      ctx.fillStyle = r.fill_color;
      ctx.fillRect(startx, starty, width, height);
    }
    ctx.stroke();
  });
  // Redraw the circle data
  canvas_data.circle.forEach(function(c) {
    // "startx": prevX, "starty": prevY, "radius": curX - prevX, "thick": ctx.lineWidth, "stroke": stroke_value, "stroke_color": ctx.strokeStyle, "fill": fill_value, "fill_color": ctx.fillStyle
    ctx.beginPath();
    ctx.arc(c.startx, c.starty, c.radius, 0, 2 * Math.PI);
    ctx.closePath();
    ctx.stroke();
    if (c.fill) {
      ctx.fillStyle = c.fill_color;
      ctx.fill();
    }
  });
}

$("#paint1").mousedown(function(e) {
  handleMouseDown(e);
});
$("#paint1").mouseup(function(e) {
  handleMouseUp(e);
});
$("#paint1").mouseout(function(e) {
  handleMouseOut(e);
});
$("#paint1").mousemove(function(e) {
  handleMouseMove(e);
});
html {
  min-width: 1500px;
  position: relative;
}

#toolset {
  width: 100px;
  height: 340px;
  position: absolute;
  left: 0px;
  top: 50px;
  background: #35d128;
}

#paint {
  position: absolute;
  left: 130px;
  top: 50px;
}

#colorset {
  position: absolute;
  left: 0px;
  top: 450px;
  width: 300px;
}

#title {
  position: absolute;
  left: 500px;
}

#penciltool {
  background: #358128;
  color: #f3f3f3;
  width: 80px;
  height: 25px;
  border: 1px solid #33842a;
  -webkit-border-radius: 0 15px 15px 0;
  -moz-border-radius: 0 15px 15px 0;
  box-shadow: rgba(0, 0, 0, .75) 0 2px 6px;
}

#linetool {
  background: #358128;
  color: #f3f3f3;
  width: 80px;
  height: 25px;
  border: 1px solid #33842a;
  -webkit-border-radius: 0 15px 15px 0;
  -moz-border-radius: 0 15px 15px 0;
  box-shadow: rgba(0, 0, 0, .75) 0 2px 6px;
}

#rectangletool {
  background: #358128;
  color: #f3f3f3;
  width: 80px;
  height: 25px;
  border: 1px solid #33842a;
  -webkit-border-radius: 0 15px 15px 0;
  -moz-border-radius: 0 15px 15px 0;
  box-shadow: rgba(0, 0, 0, .75) 0 2px 6px;
}

#circletool {
  background: #358128;
  color: #f3f3f3;
  width: 80px;
  height: 25px;
  border: 1px solid #33842a;
  -webkit-border-radius: 0 15px 15px 0;
  -moz-border-radius: 0 15px 15px 0;
  box-shadow: rgba(0, 0, 0, .75) 0 2px 6px;
}

#erasertool {
  background: #358128;
  color: #f3f3f3;
  width: 80px;
  height: 25px;
  border: 1px solid #33842a;
  -webkit-border-radius: 0 15px 15px 0;
  -moz-border-radius: 0 15px 15px 0;
  box-shadow: rgba(0, 0, 0, .75) 0 2px 6px;
}

#resettool {
  background: #358128;
  color: #f3f3f3;
  width: 80px;
  height: 25px;
  border: 1px solid #33842a;
  -webkit-border-radius: 0 15px 15px 0;
  -moz-border-radius: 0 15px 15px 0;
  box-shadow: rgba(0, 0, 0, .75) 0 2px 6px;
}
<html>

<head>
  <title>Paint App</title>
</head>

<body>


  <p style = "text-align:left; font: bold 35px/35px Georgia, serif;">
    PaintApp



    <div align = "right">



      <link rel = "stylesheet" type = "text/css" href = "style.css">

      <body onload = "pencil(`{{ data }}`, `{{ targetx }}`, `{{ targety }}`, `{{ sizex }}`, `{{ sizey }}`)">

        <p>

          <table>

            <tr>

              <td>
                <fieldset id = "toolset" style = "margin-top: 3%;">
                  <br>
                  <br>
                  <button id = "penciltool" type = "button" style = "height: 15px; width: 100px;" onclick = "pencil()">Pencil</button>
                  <br>
                  <br>
                  <br>
                  <button id = "linetool" type = "button" style = "height: 15px; width: 100px;" onclick = "line()">Line</button>
                  <br>
                  <br>
                  <br>
                  <button id = "rectangletool" type = "button" style = "height: 15px; width: 100px;" onclick = "rectangle()">Rectangle</button>
                  <br>
                  <br>
                  <br>
                  <button id = "circletool" type = "button" style = "height: 15px; width: 100px;" onclick = "circle()">Circle</button>
                  <br>
                  <br>
                  <br>
                  <button id = "erasertool" type = "button" style = "height: 15px; width: 100px;" onclick = "eraser()">Eraser</button>
                  <br>
                  <br>
                  <br>
                  <button id = "resettool" type = "button" style = "height: 15px; width: 100px;" onclick = "reset()">Reset</button>
                </fieldset>
              </td>

              <td>
                <canvas id = "paint" width = "500vw" height = "350vw" style = "border: 5px solid #000000; margin-top: 3%;"></canvas>
              </td>
            </tr>
          </table>
        </p>
        <fieldset id = "colorset" style = "margin-top: 1.8%;">

          <table>
            <tr>
              <td><button style = "height: 15px; width: 80px;" onclick = "fill()">Fill</button>
                <td><button style = "background-color: #000000; height: 15px; width: 15px;" onclick = "color('#000000')"></button>
                  <td><button style = "background-color: #B0171F; height: 15px; width: 15px;" onclick = "color('#B0171F')"></button>
                    <td><button style = "background-color: #DA70D6; height: 15px; width: 15px;" onclick = "color('#DA70D6')"></button>
                      <td><button style = "background-color: #8A2BE2; height: 15px; width: 15px;" onclick = "color('#8A2BE2')"></button>
                        <td><button style = "background-color: #0000FF; height: 15px; width: 15px;" onclick = "color('#0000FF')"></button>
                          <td><button style = "background-color: #4876FF; height: 15px; width: 15px;" onclick = "color('#4876FF')"></button>
                            <td><button style = "background-color: #CAE1FF; height: 15px; width: 15px;" onclick = "color('#CAE1FF')"></button>
                              <td><button style = "background-color: #6E7B8B; height: 15px; width: 15px;" onclick = "color('#6E7B8B')"></button>
                                <td><button style = "background-color: #00C78C; height: 15px; width: 15px;" onclick = "color('#00C78C')"></button>
                                  <td><button style = "background-color: #00FA9A; height: 15px; width: 15px;" onclick = "color('#00FA9A')"></button>
                                    <td><button style = "background-color: #00FF7F; height: 15px; width: 15px;" onclick = "color('#00FF7F')"></button>
                                      <td><button style = "background-color: #00C957; height: 15px; width: 15px;" onclick = "color('#00C957')"></button>
                                        <td><button style = "background-color: #FFFF00; height: 15px; width: 15px;" onclick = "color('#FFFF00')"></button>
                                          <td><button style = "background-color: #CDCD00; height: 15px; width: 15px;" onclick = "color('#CDCD00')"></button>
                                            <td><button style = "background-color: #FFF68F; height: 15px; width: 15px;" onclick = "color('#FFF68F')"></button>
                                              <td><button style = "background-color: #FFFACD; height: 15px; width: 15px;" onclick = "color('#FFFACD')"></button>
                                                <td><button style = "background-color: #FFEC8B; height: 15px; width: 15px;" onclick = "color('#FFEC8B')"></button>
                                                  <td><button style = "background-color: #FFD700; height: 15px; width: 15px;" onclick = "color('#FFD700')"></button>
                                                    <td><button style = "background-color: #F5DEB3; height: 15px; width: 15px;" onclick = "color('#F5DEB3')"></button>
                                                      <td><button style = "background-color: #FFE4B5; height: 15px; width: 15px;" onclick = "color('#FFE4B5')"></button>
                                                        <td><button style = "background-color: #EECFA1; height: 15px; width: 15px;" onclick = "color('#EECFA1')"></button>
                                                          <td><button style = "background-color: #FF9912; height: 15px; width: 15px;" onclick = "color('#FF9912')"></button>
                                                            <td><button style = "background-color: #8E388E; height: 15px; width: 15px;" onclick = "color('#8E388E')"></button>
                                                              <td><button style = "background-color: #7171C6; height: 15px; width: 15px;" onclick = "color('#7171C6')"></button>
                                                                <td><button style = "background-color: #7D9EC0; height: 15px; width: 15px;" onclick = "color('#7D9EC0')"></button>
                                                                  <td><button style = "background-color: #388E8E; height: 15px; width: 15px;" onclick = "color('#388E8E')"></button>

            </tr>

            <tr>
              <td><button style = "height: 15px; width: 80px" onclick = "outline()">Outline</button>
                <td><button style = "background-color: #71C671; height: 15px; width: 15px;" onclick = "color('#71C671')"></button>
                  <td><button style = "background-color: #8E8E38; height: 15px; width: 15px;" onclick = "color('#8E8E38')"></button>
                    <td><button style = "background-color: #C5C1AA; height: 15px; width: 15px;" onclick = "color('#C5C1AA')"></button>
                      <td><button style = "background-color: #C67171; height: 15px; width: 15px;" onclick = "color('#C67171')"></button>
                        <td><button style = "background-color: #555555; height: 15px; width: 15px;" onclick = "color('#555555')"></button>
                          <td><button style = "background-color: #848484; height: 15px; width: 15px;" onclick = "color('#848484')"></button>
                            <td><button style = "background-color: #F4F4F4; height: 15px; width: 15px;" onclick = "color('#F4F4F4')"></button>
                              <td><button style = "background-color: #EE0000; height: 15px; width: 15px;" onclick = "color('#EE0000')"></button>
                                <td><button style = "background-color: #FF4040; height: 15px; width: 15px;" onclick = "color('#FF4040')"></button>
                                  <td><button style = "background-color: #EE6363; height: 15px; width: 15px;" onclick = "color('#EE6363')"></button>
                                    <td><button style = "background-color: #FFC1C1; height: 15px; width: 15px;" onclick = "color('#FFC1C1')"></button>
                                      <td><button style = "background-color: #FF7256; height: 15px; width: 15px;" onclick = "color('#FF7256')"></button>
                                        <td><button style = "background-color: #FF4500; height: 15px; width: 15px;" onclick = "color('#FF4500')"></button>
                                          <td><button style = "background-color: #F4A460; height: 15px; width: 15px;" onclick = "color('#F4A460')"></button>
                                            <td><button style = "background-color: #FF8000; height: 15px; width: 15px;" onclick = "color('FF8000')"></button>
                                              <td><button style = "background-color: #FFD700; height: 15px; width: 15px;" onclick = "color('#FFD700')"></button>
                                                <td><button style = "background-color: #8B864E; height: 15px; width: 15px;" onclick = "color('#8B864E')"></button>
                                                  <td><button style = "background-color: #9ACD32; height: 15px; width: 15px;" onclick = "color('#9ACD32')"></button>
                                                    <td><button style = "background-color: #66CD00; height: 15px; width: 15px;" onclick = "color('#66CD00')"></button>
                                                      <td><button style = "background-color: #BDFCC9; height: 15px; width: 15px;" onclick = "color('#BDFCC9')"></button>
                                                        <td><button style = "background-color: #76EEC6; height: 15px; width: 15px;" onclick = "color('#76EEC6')"></button>
                                                          <td><button style = "background-color: #40E0D0; height: 15px; width: 15px;" onclick = "color('#40E0D0')"></button>
                                                            <td><button style = "background-color: #9B30FF; height: 15px; width: 15px;" onclick = "color('#9B30FF')"></button>
                                                              <td><button style = "background-color: #EE82EE; height: 15px; width: 15px;" onclick = "color('#EE82EE')"></button>
                                                                <td><button style = "background-color: #FFC0CB; height: 15px; width: 15px;" onclick = "color('#FFC0CB')"></button>
                                                                  <td><button style = "background-color: #7CFC00; height: 15px; width: 15px;" onclick = "color('#7CFC00')"></button>
            </tr>
            <tr>
              <td><label>Line Width</label></td>
              <td><button id = "pixel_plus" type = "button" onclick = "add_pixel()" style = "width: 25px;">+</button></td>
              <td><button id = "pixel_minus" type = "button" onclick = "reduce_pixel()" style = "width: 25px;">-</button></td>
              <td><button id = "undo" type = "button" onclick = "undo_pixel()" style = "width: 75px;">Undo</button></td>

            </tr>
          </table>
          <br>

        </fieldset>
        <script src = "//code.jquery.com/jquery-1.8.3.js"></script>
        <script src = "script.js"></script>
      </body>

</html>

Вот что я пробовал:

  1. Во-первых, я создал функцию undo_pixel(), которая использует переменную last_action для извлечения последнего элемента, введенного в соответствующий стек предыдущего действия.

  2. Затем я перерисовываю холст с помощью функции redraw_canvas(), которая очищает холст, а затем перерисовывает его, используя все точки данных, хранящиеся в объекте canvas_data.

Но что это вызывает какое-то неожиданное поведение, которое я не могу понять полностью. Вот что происходит:

Набросок перед отменой: Как реализовать функцию отмены для холста HTML с помощью JavaScript?

Эскиз после отмены: Как реализовать функцию отмены для холста HTML с помощью JavaScript?

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

Вероятно, вам лучше предоставить рабочий пример с помощью инструмента codesnippet здесь.

Lee Taylor 02.04.2022 16:06

Спасибо за ваше предложение, я добавил ссылку на скрипт JS. Дайте мне знать, если я должен добавить что-нибудь еще.

Ravish Jha 02.04.2022 16:33

Теперь я добавил его с помощью инструмента codenippet.

Ravish Jha 02.04.2022 16:46

Не связано: похоже, вы используете какой-то механизм шаблонов ({{ ... }}), который, вероятно, не работает во фрагменте стека. Также в вашем HTML есть два элемента body, которые делают его недействительным. При нажатии «Отменить» возникает ошибка, связанная с startx. Вы должны сначала исправить эту ошибку. Наконец, отмена никогда не будет работать правильно, если у вас нет стека действий все. Наличие ссылки только на последнее действие сделает невозможным отмену двух действий.

trincot 02.04.2022 17:36

Привет, по вашим первым двум пунктам я исправил тег body и удалил строки, которые могут вызывать проблему с зависимостями, но для меня это работает так же, как и раньше, во фрагменте кода, а также в ссылке JS Fiddle. Это мой первый раз, когда я использую функцию фрагмента кода здесь, поэтому я могу ошибаться, но у меня она работает нормально (я также не получаю никаких ошибок относительно startx), дайте мне знать, если я делаю что-то неправильно. Наконец, я понимаю вашу точку зрения относительно невозможности отменить более одного действия, это изменит это. Но почему это все еще не работает только для одного действия?

Ravish Jha 02.04.2022 18:06

В блоке, который следует сразу за этим комментарием «Перерисовать данные карандашом», я думаю, вы пропустили closePath после stroke. Ваше изображение выглядит так, как обычно, когда возникает проблема с началом или окончанием пути обводки.

enhzflep 02.04.2022 19:11

У меня есть рабочий ответ, но фрагмент продолжает удалять css, потому что все слишком длинное. Это тот случай, когда минимальный рабочий пример очень помог бы. Я немного подрежу вам код, чтобы фрагмент заработал, но остальная часть ответа остается в силе.

Dave Pritlove 02.04.2022 19:37
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
7
61
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Непосредственная проблема, вызывающая форму веера, которую вы наблюдаете, вызвана тем, что startx и starty остаются одинаковыми для каждой записи объекта в массиве canvas_data.pencil. Это связано с тем, что startx и starty присваиваются значения, хранящиеся в prevX и prevY, но они были установлены в прослушивателе событий mousedown и не обновляются в прослушивателе событий mousemove.

Это фиксируется следующим образом:

Во-первых, хотя это и не является строго необходимым для работы, удалите ссылки на prevX и prevY из прослушивателя событий mousedown в функции карандаша и установите ctx.moveTo(curX, curY) — это немного менее запутанно, потому что отрисовка начинается с нажатия мыши:

// inside pencil function;

  canvas.onmousedown = function(e) {
    curX = e.clientX - canvas.offsetLeft;
    curY = e.clientY - canvas.offsetTop;
    hold = true;

    ctx.beginPath();
    ctx.moveTo(curX, curY); // prev -> cur;
  };

Далее в слушателе mousemove добавьте строки для обновления prevX и prevY:

// inside pencil function;

  canvas.onmousemove = function(e) {
    if (hold) {
      curX = e.clientX - canvas.offsetLeft;
      curY = e.clientY - canvas.offsetTop;
      draw();
      prevX = curX; // new
      prevY = curY; // new
    }
  };

Эти изменения формируют правильный массив объектов данных, при этом значения startx и starty каждого объекта теперь устанавливаются равными значениям endx и endy предыдущего объекта, как требуется.

Однако есть еще одна непосредственная проблема: функция undo_pixel полностью выполняется только при первом щелчке, чтобы удалить последний объект массива карандашей (в результате чего последняя полоска строки, захваченная последним событием mousemove, исчезает, как предполагалось), но последующие щелчки (для удалить последовательные части строки) прерываются.

Я предполагаю, что функция undo_pixel предназначена для удаления полоски для каждого щелчка, а не всей линии карандаша (см. Позже, если нет).

Причиной прерывания процесса является сброс флага canvas_data.last_action внутри undo_pixel:

canvas_data.last_action = -1`

Поскольку в операторе switch для case нет блока -1, при последующих нажатиях кнопки undo ничего не происходит;

Вместо этого футляр для карандаша должен быть:

case 4:
canvas_data.pencil.pop();
canvas_data.last_action = 4; // not -1;
break;

Вы должны оставить его на 4 до тех пор, пока не будет выбрано другое действие для отмены или пока вся линия карандаша не будет удалена, а затем перейти к объекту, нарисованному непосредственно перед линией линзы (для чего вам нужно будет отслеживать порядок, в котором элементы были нарисовано).

Я сделал рабочий фрагмент с приведенными выше предложениями. Вам нужно будет отобразить его на всю страницу, так как окно предварительного просмотра слишком маленькое, чтобы увидеть строку, когда вы нажимаете кнопку отмены. Если вы нарисуете короткую завитушку карандашом и несколько раз нажмете кнопку отмены, вы увидите, что линия «не рисуется».

(Мне пришлось удалить некоторые из ваших других функций, так как я превысил разрешенный лимит символов для ответа)

var canvas = document.getElementById("paint");

var ctx = canvas.getContext("2d");

var pi2 = Math.PI * 2;
var resizerRadius = 8;
var rr = resizerRadius * resizerRadius;

var width = canvas.width;
var height = canvas.height;
var curX, curY, prevX, prevY;
var hold = false;
ctx.lineWidth = 2;
var fill_value = true;
var stroke_value = false;
var canvas_data = {
  "pencil": [],
  "line": [],
  "rectangle": [],
  "circle": [],
  "eraser": [],
  "last_action": -1
};

// //connect to postgres client
// var pg = require('pg');
// var conString = "postgres://postgres:database1@localhost:5432/sketch2photo";
// client = new pg.Client(conString);


function color(color_value) {
  ctx.strokeStyle = color_value;
  ctx.fillStyle = color_value;
}

function add_pixel() {
  ctx.lineWidth += 1;
}

function reduce_pixel() {
  if (ctx.lineWidth == 1) {
    ctx.lineWidth = 1;
  } else {
    ctx.lineWidth -= 1;
  }
}

function fill() {
  fill_value = true;
  stroke_value = false;
}

function outline() {
  fill_value = false;
  stroke_value = true;
}

function reset() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  canvas_data = {
    "pencil": [],
    "line": [],
    "rectangle": [],
    "circle": [],
    "eraser": [],
    "last_action": -1
  };
}

// pencil tool

function pencil(data, targetX, targetY, targetWidth, targetHeight) {
    //prevX = 0; // new
    //prevY = 0; // new


  canvas.onmousedown = function(e) {
    curX = e.clientX - canvas.offsetLeft;
    curY = e.clientY - canvas.offsetTop;
    hold = true;

    ctx.beginPath();
    ctx.moveTo(curX, curY); // prev -> cur;
  };

  canvas.onmousemove = function(e) {
    if (hold) {
      curX = e.clientX - canvas.offsetLeft;
      curY = e.clientY - canvas.offsetTop;
      draw();
      prevX = curX; // new
      prevY = curY; // new
    }
  };

  canvas.onmouseup = function(e) {
    hold = false;
  };

  canvas.onmouseout = function(e) {
    hold = false;
  };

  function draw() {
    ctx.lineTo(curX, curY);
    ctx.stroke();
    canvas_data.pencil.push({
      "startx": prevX,
      "starty": prevY,
      "endx": curX,
      "endy": curY,
      "thick": ctx.lineWidth,
      "color": ctx.strokeStyle
    });
    canvas_data.last_action = 0;
  }
}


function undo_pixel() {

  switch (canvas_data.last_action) {
    case 0:
    case 4:
      canvas_data.pencil.pop();
      canvas_data.last_action = 4; // not -1;
      break;
    case 1:
      //Undo the last line drawn
      console.info("Case 1");
      canvas_data.line.pop();
      canvas_data.last_action = -1;
      break;
    case 2:
      //Undo the last rectangle drawn
      console.info("Case 2");
      canvas_data.rectangle.pop();
      canvas_data.last_action = -1;
      break;
    case 3:
      //Undo the last circle drawn
      console.info("Case 3");
      canvas_data.circle.pop();
      canvas_data.last_action = -1;
      break;

    default:
      break;

  }

  redraw_canvas();

}

function redraw_canvas() {
  // Redraw all the shapes on the canvas 
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // Redraw the pencil data 
  canvas_data.pencil.forEach(function(p) {
    ctx.beginPath();
    ctx.moveTo(p.startx, p.starty);
    ctx.lineTo(p.endx, p.endy);
    ctx.lineWidth = p.thick;
    ctx.strokeStyle = p.color;
    ctx.stroke();
  });
  // Redraw the line data
  canvas_data.line.forEach(function(l) {
    ctx.beginPath();
    ctx.moveTo(l.startx, l.starty);
    ctx.lineTo(l.endx, l.endy);
    ctx.lineWidth = l.thick;
    ctx.strokeStyle = l.color;
    ctx.stroke();
  });

}
<html>

    <head>
      <title>Paint App</title>

     <script src = "main.js" defer></script>
    <link rel = "stylesheet" href = "styles.css">
    </head>

    <body>

      <body>
        <p style = "text-align:left; font: bold 35px/35px Georgia, serif;">
          PaintApp

      </body>

    </body>

    <div align = "right">



      <link rel = "stylesheet" type = "text/css" href = "{{ url_for('static', filename='style.css') }}">

      <body onload = "pencil(`{{ data }}`, `{{ targetx }}`, `{{ targety }}`, `{{ sizex }}`, `{{ sizey }}`)">

        <p>

          <table>

            <tr>

              <td>
                <fieldset id = "toolset" style = "margin-top: 3%;">
                  <br>
                  <br>
                  <button id = "penciltool" type = "button" style = "height: 15px; width: 100px;" onclick = "pencil()">Pencil</button>
                  <br>
                  <br>
                  <br>
                  <button id = "linetool" type = "button" style = "height: 15px; width: 100px;" onclick = "line()">Line</button>
                  <br>
                  <br>
                  <br>
                  <button id = "rectangletool" type = "button" style = "height: 15px; width: 100px;" onclick = "rectangle()">Rectangle</button>
                  <br>
                  <br>
                  <br>
                  <button id = "circletool" type = "button" style = "height: 15px; width: 100px;" onclick = "circle()">Circle</button>
                  <br>
                  <br>
                  <br>
                  <button id = "erasertool" type = "button" style = "height: 15px; width: 100px;" onclick = "eraser()">Eraser</button>
                  <br>
                  <br>
                  <br>
                  <button id = "resettool" type = "button" style = "height: 15px; width: 100px;" onclick = "reset()">Reset</button>
                </fieldset>
              </td>

              <td>
                <canvas id = "paint" width = "500vw" height = "350vw" style = "border: 5px solid #000000; margin-top: 3%;"></canvas>
              </td>
            </tr>
          </table>
        </p>
        <fieldset id = "colorset" style = "margin-top: 1.8%;">

          <table>
            <tr>
              <td><button style = "height: 15px; width: 80px;" onclick = "fill()">Fill</button>
                <td><button style = "background-color: #000000; height: 15px; width: 15px;" onclick = "color('#000000')"></button>
                  <td><button style = "background-color: #B0171F; height: 15px; width: 15px;" onclick = "color('#B0171F')"></button>
                    <td><button style = "background-color: #DA70D6; height: 15px; width: 15px;" onclick = "color('#DA70D6')"></button>
                      <td><button style = "background-color: #8A2BE2; height: 15px; width: 15px;" onclick = "color('#8A2BE2')"></button>
                        <td><button style = "background-color: #0000FF; height: 15px; width: 15px;" onclick = "color('#0000FF')"></button>
                          <td><button style = "background-color: #4876FF; height: 15px; width: 15px;" onclick = "color('#4876FF')"></button>
                            <td><button style = "background-color: #CAE1FF; height: 15px; width: 15px;" onclick = "color('#CAE1FF')"></button>
                              <td><button style = "background-color: #6E7B8B; height: 15px; width: 15px;" onclick = "color('#6E7B8B')"></button>
                                <td><button style = "background-color: #00C78C; height: 15px; width: 15px;" onclick = "color('#00C78C')"></button>
                                  <td><button style = "background-color: #00FA9A; height: 15px; width: 15px;" onclick = "color('#00FA9A')"></button>
                                    <td><button style = "background-color: #00FF7F; height: 15px; width: 15px;" onclick = "color('#00FF7F')"></button>
                                      <td><button style = "background-color: #00C957; height: 15px; width: 15px;" onclick = "color('#00C957')"></button>
                                        <td><button style = "background-color: #FFFF00; height: 15px; width: 15px;" onclick = "color('#FFFF00')"></button>
                                          <td><button style = "background-color: #CDCD00; height: 15px; width: 15px;" onclick = "color('#CDCD00')"></button>
                                            <td><button style = "background-color: #FFF68F; height: 15px; width: 15px;" onclick = "color('#FFF68F')"></button>
                                              <td><button style = "background-color: #FFFACD; height: 15px; width: 15px;" onclick = "color('#FFFACD')"></button>
                                                <td><button style = "background-color: #FFEC8B; height: 15px; width: 15px;" onclick = "color('#FFEC8B')"></button>
                                                  <td><button style = "background-color: #FFD700; height: 15px; width: 15px;" onclick = "color('#FFD700')"></button>
                                                    <td><button style = "background-color: #F5DEB3; height: 15px; width: 15px;" onclick = "color('#F5DEB3')"></button>
                                                      <td><button style = "background-color: #FFE4B5; height: 15px; width: 15px;" onclick = "color('#FFE4B5')"></button>
                                                        <td><button style = "background-color: #EECFA1; height: 15px; width: 15px;" onclick = "color('#EECFA1')"></button>
                                                          <td><button style = "background-color: #FF9912; height: 15px; width: 15px;" onclick = "color('#FF9912')"></button>
                                                            <td><button style = "background-color: #8E388E; height: 15px; width: 15px;" onclick = "color('#8E388E')"></button>
                                                              <td><button style = "background-color: #7171C6; height: 15px; width: 15px;" onclick = "color('#7171C6')"></button>
                                                                <td><button style = "background-color: #7D9EC0; height: 15px; width: 15px;" onclick = "color('#7D9EC0')"></button>
                                                                  <td><button style = "background-color: #388E8E; height: 15px; width: 15px;" onclick = "color('#388E8E')"></button>

            </tr>

            <tr>
              <td><button style = "height: 15px; width: 80px" onclick = "outline()">Outline</button>
                <td><button style = "background-color: #71C671; height: 15px; width: 15px;" onclick = "color('#71C671')"></button>
                  <td><button style = "background-color: #8E8E38; height: 15px; width: 15px;" onclick = "color('#8E8E38')"></button>
                    <td><button style = "background-color: #C5C1AA; height: 15px; width: 15px;" onclick = "color('#C5C1AA')"></button>
                      <td><button style = "background-color: #C67171; height: 15px; width: 15px;" onclick = "color('#C67171')"></button>
                        <td><button style = "background-color: #555555; height: 15px; width: 15px;" onclick = "color('#555555')"></button>
                          <td><button style = "background-color: #848484; height: 15px; width: 15px;" onclick = "color('#848484')"></button>
                            <td><button style = "background-color: #F4F4F4; height: 15px; width: 15px;" onclick = "color('#F4F4F4')"></button>
                              <td><button style = "background-color: #EE0000; height: 15px; width: 15px;" onclick = "color('#EE0000')"></button>
                                <td><button style = "background-color: #FF4040; height: 15px; width: 15px;" onclick = "color('#FF4040')"></button>
                                  <td><button style = "background-color: #EE6363; height: 15px; width: 15px;" onclick = "color('#EE6363')"></button>
                                    <td><button style = "background-color: #FFC1C1; height: 15px; width: 15px;" onclick = "color('#FFC1C1')"></button>
                                      <td><button style = "background-color: #FF7256; height: 15px; width: 15px;" onclick = "color('#FF7256')"></button>
                                        <td><button style = "background-color: #FF4500; height: 15px; width: 15px;" onclick = "color('#FF4500')"></button>
                                          <td><button style = "background-color: #F4A460; height: 15px; width: 15px;" onclick = "color('#F4A460')"></button>
                                            <td><button style = "background-color: #FF8000; height: 15px; width: 15px;" onclick = "color('FF8000')"></button>
                                              <td><button style = "background-color: #FFD700; height: 15px; width: 15px;" onclick = "color('#FFD700')"></button>
                                                <td><button style = "background-color: #8B864E; height: 15px; width: 15px;" onclick = "color('#8B864E')"></button>
                                                  <td><button style = "background-color: #9ACD32; height: 15px; width: 15px;" onclick = "color('#9ACD32')"></button>
                                                    <td><button style = "background-color: #66CD00; height: 15px; width: 15px;" onclick = "color('#66CD00')"></button>
                                                      <td><button style = "background-color: #BDFCC9; height: 15px; width: 15px;" onclick = "color('#BDFCC9')"></button>
                                                        <td><button style = "background-color: #76EEC6; height: 15px; width: 15px;" onclick = "color('#76EEC6')"></button>
                                                          <td><button style = "background-color: #40E0D0; height: 15px; width: 15px;" onclick = "color('#40E0D0')"></button>
                                                            <td><button style = "background-color: #9B30FF; height: 15px; width: 15px;" onclick = "color('#9B30FF')"></button>
                                                              <td><button style = "background-color: #EE82EE; height: 15px; width: 15px;" onclick = "color('#EE82EE')"></button>
                                                                <td><button style = "background-color: #FFC0CB; height: 15px; width: 15px;" onclick = "color('#FFC0CB')"></button>
                                                                  <td><button style = "background-color: #7CFC00; height: 15px; width: 15px;" onclick = "color('#7CFC00')"></button>
            </tr>
            <tr>
              <td><label>Line Width</label></td>
              <td><button id = "pixel_plus" type = "button" onclick = "add_pixel()" style = "width: 25px;">+</button></td>
              <td><button id = "pixel_minus" type = "button" onclick = "reduce_pixel()" style = "width: 25px;">-</button></td>
              <td><button id = "undo" type = "button" onclick = "undo_pixel()" style = "width: 75px;">Undo</button></td>

            </tr>
          </table>
          <br>

        </fieldset>
        <script src = "//code.jquery.com/jquery-1.8.3.js"></script>
        <script src = " {{ url_for('static', filename='script.js') }}"></script>
      </body>

    </html>

Примечание о предполагаемой функции отмены Если я неправильно понял и действие undo должно удалить всю последнюю фигуру карандаша за один клик, вам нужно изменить способ структурирования массива объектов pencil.

В настоящее время массив имеет следующую структуру:

[
  {
    "startx": 148,
    "starty": 281,
    "endx": 148,
    "endy": 280,
    "thick": 2,
    "color": "#000000"
  },

// more data objects for each mousemove captured;
  {
    "startx": 148,
    "starty": 281,
    "endx": 148,
    "endy": 280,
    "thick": 2,
    "color": "#000000"
  }
]

это означает, что удаление последнего объекта (что делает ваш .pop в case 4) удаляет только полоску карандашной линии, захваченную во время последнего события mousemove. Это нормально, если вы хотите точно настроить линию (я думаю, что это хорошая функция, и предположил, что вы этого хотели), но вызывает проблему, если нарисовано более одной отдельной карандашной линии. Если есть две линии карандаша, они объединяются в одну внутри массива данных pencil.

Чтобы справиться с этим, вам придется реструктурировать массив pencil, чтобы он содержал дискретные внутренние массивы для каждой строки следующим образом:

[ 
 // first line array:
  [
  {
    "startx": 148,
    "starty": 281,
    "endx": 148,
    "endy": 280,
    "thick": 2,
    "color": "#000000"
  },

// more data objects for each mousemove captured;
  {
    "startx": 148,
    "starty": 281,
    "endx": 148,
    "endy": 280,
    "thick": 2,
    "color": "#000000"
  }
  ], 

// more line arrays;

// last line array:
  [
  {
    "startx": 148,
    "starty": 281,
    "endx": 148,
    "endy": 280,
    "thick": 2,
    "color": "#000000"
  },

// more data objects for each mousemove captured;
  {
    "startx": 148,
    "starty": 281,
    "endx": 148,
    "endy": 280,
    "thick": 2,
    "color": "#000000"
  }
  ] 

] // end of pencil array;

В этой структуре .pop удалит целую строку (что может быть тем, что вы хотели). Это также устранит ошибку, из-за которой в настоящее время перерисовка соединяет любые отдельные строки в одну строку.

Большое спасибо за подробный ответ и подробное объяснение, это мне очень помогло. Я действительно намеревался использовать .pop для удаления всей строки, поэтому сейчас я работаю над изменением типа данных, чтобы он был таким, как вы упомянули в ответе. Я столкнулся с ошибкой, о которой вы упомянули, когда перерисовка объединяет любые отдельные строки в одну строку, я надеюсь, что это тоже будет решено. Спасибо еще раз.

Ravish Jha 02.04.2022 20:02

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

Dave Pritlove 02.04.2022 20:06

Другие вопросы по теме