Как я могу сохранить несколько базовых фигур, нарисованных на одном холсте, с предварительным просмотром, как в paint?

#javascript #html #css

#javascript #HTML #css — код

Вопрос:

Я пытаюсь создать приложение, подобное paint, в котором вы можете рисовать базовые фигуры с предварительным просмотром. Я реализовал функцию рисования для эллипса, а затем хочу реализовать ее для линии и прямоугольника. Но я не знаю, как сохранить уже нарисованные фигуры и восстановить их после. Также я не знаю, зачем мне нужны два элемента canvas, если я хочу рисовать с предварительным просмотром. Спасибо!

 //JavaScript FILE
 let canvas = document.getElementById("canvas");
    let tempcanvas = document.getElementById("tempcanvas")
    
    var ctx = tempcanvas.getContext('2d');
    var mainctx = canvas.getContext('2d');
    ctx.fillStyle="aqua";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    
    var widthCns = canvas.width, heightCns =  canvas.height, x1, y1;
    var isDown = false; 
    var savedDrawings = [];
    
    //Desenare cu un singur tip de instrument (elipsă)
    function drawEllipse(x1, y1, x2, y2){
        var radiusX = (x2 - x1) * 0.5,   /// radius for x based on input
            radiusY = (y2 - y1) * 0.5,   /// radius for y based on input
            centerX = x1   radiusX,      /// calc center
            centerY = y1   radiusY,
            step = 0.01,                 /// resolution of ellipse
            a = step,                    /// counter
            pi2 = Math.PI * 2 - step;    /// end angle
        
        /// start a new path
        ctx.beginPath();
    
        /// set start point at angle 0
        ctx.moveTo(centerX   radiusX * Math.cos(0),
                   centerY   radiusY * Math.sin(0));
    
        /// create the ellipse    
        for(; a < pi2; a  = step) {
            ctx.lineTo(centerX   radiusX * Math.cos(a),
                       centerY   radiusY * Math.sin(a));
        }
        
        /// close it and stroke it for demo
        ctx.closePath();
        ctx.strokeStyle = '#000';
        ctx.stroke();
    }
    
    //Desenare cu un singur tip de instrument (dreptunghi)
    function drawRect(){
    
    }
    
    //Desenare cu un singur tip de instrument (linie)
    function drawLine(){
        
    }
    
    //Desenare cu mouse cu preview(elipsa, dreptunghi, line)
    //Implementat: elipsa
    
    /// handle mouse down    
    tempcanvas.onmousedown = function(e) {
    
        // if(savedDrawings !== null)
        //     savedDrawings.pop();
        // while( ctx!== null){
        //     ctx.restore();
        // }
        ctx.restore();
        /// get corrected mouse position and store as first point
        var rect = tempcanvas.getBoundingClientRect();
        x1 = e.clientX - rect.left;
        y1 = e.clientY - rect.top;
        isDown = true;
    }
    
    /// clear isDown flag to stop drawing
    tempcanvas.onmouseup = function() {
       // savedDrawings.push({X: x1, Y: y1});
       ctx.save();
        isDown = false;
    }
    
    /// draw ellipse from start point
    tempcanvas.onmousemove = function(e) {
    
        if (!isDown) return;
        
        var rect = tempcanvas.getBoundingClientRect(),
            x2 = e.clientX - rect.left,
            y2 = e.clientY - rect.top;
        
        /// clear temporal canvas
        ctx.clearRect(0, 0, widthCns, heightCns);
    
        /// draw ellipse
        drawEllipse(x1, y1, x2, y2);
    }

//HTML FILE
        <canvas id="canvas" width="800" height="500" padding="15px" position="absolute" background-color="aqua" style="border: 1px solid black">
         </canvas>
        <!-- canvas temporal -->
        <canvas id="tempcanvas" width=800 height=500 padding="15px" position="absolute" background-color="aqua" style="border: 1px solid black"></canvas>
 

Ответ №1:

Вопрос: Зачем вам нужны два холста для рисования с предварительным просмотром?

Ответ: Один холст используется для отображения предварительного просмотра при удержании кнопки мыши. Когда вы перемещаете курсор мыши, весь холст перерисовывается заново. Поэтому его всегда нужно очищать перед рисованием. Если вы прокомментируете строку с помощью ctx.clearRect, вы увидите, что для каждого перемещения мыши выполняется множество фигур.

Второй холст используется для хранения всех нарисованных фигур. Как вы можете видеть в нижнем примере, вы можете расположить временный холст с прозрачным фоном поверх основного холста, чтобы он выглядел так, как будто вы рисуете только в одном из них.

Вопрос: Как сохранить уже нарисованную фигуру на основной холст?

Ответ: Используйте метод drawImage для копирования изображения с временного холста на основной холст.

 const canvas = document.getElementById("canvas");
const tempcanvas = document.getElementById("tempcanvas")

const ctx = tempcanvas.getContext('2d');
const mainctx = canvas.getContext('2d');
mainctx.fillStyle='#cec';
mainctx.fillRect(0, 0, canvas.width, canvas.height);

const widthCns = canvas.width, heightCns = canvas.height;
var isDown = false; 
var savedDrawings = [];

var x1, y1;

//Desenare cu un singur tip de instrument (elipsă)
function drawEllipse(x1, y1, x2, y2){
  let radiusX = (x2 - x1) * 0.5,   /// radius for x based on input
      radiusY = (y2 - y1) * 0.5,   /// radius for y based on input
      centerX = x1   radiusX,      /// calc center
      centerY = y1   radiusY,
      step = 0.01,                 /// resolution of ellipse
      a = step,                    /// counter
      pi2 = Math.PI * 2 - step;    /// end angle

  /// start a new path
  ctx.beginPath();

  /// set start point at angle 0
  ctx.moveTo(centerX   radiusX * Math.cos(0),
             centerY   radiusY * Math.sin(0));

  /// create the ellipse    
  for(; a < pi2; a  = step) {
    ctx.lineTo(centerX   radiusX * Math.cos(a),
               centerY   radiusY * Math.sin(a));
  }

  /// close it and stroke it for demo
  ctx.closePath();
  ctx.strokeStyle = '#000';
  ctx.stroke();
}

/// handle mouse down    
tempcanvas.onmousedown = function(e) {
  ctx.restore();
  
  /// get corrected mouse position and store as first point
  var rect = tempcanvas.getBoundingClientRect();
  x1 = e.clientX - rect.left;
  y1 = e.clientY - rect.top;
  isDown = true;
}

/// clear isDown flag to stop drawing
tempcanvas.onmouseup = function() {  
  mainctx.drawImage(tempcanvas, 0, 0);
  
  ctx.clearRect(0, 0, widthCns, heightCns);
  ctx.save();
  isDown = false;
}

/// draw ellipse from start point
tempcanvas.onmousemove = function(e) {
  if (!isDown) return;

  var rect = tempcanvas.getBoundingClientRect(),
      x2 = e.clientX - rect.left,
      y2 = e.clientY - rect.top;

  /// clear temporal canvas
  ctx.clearRect(0, 0, widthCns, heightCns);

  /// draw ellipse
  drawEllipse(x1, y1, x2, y2);
} 
 #canvas,
#tempcanvas {
  position: absolute;
  top: 0;
  left: 0;
}

#tempcanvas {
  
} 
 <canvas id="canvas" width="800" height="500"></canvas>
<canvas id="tempcanvas" width=800 height=500></canvas> 

Скрипка: https://jsfiddle.net/4v3r9pdt/3 /

Хорошая статья о сохранении и восстановлении контекста холста: https://html5.litten.com/understanding-save-and-restore-for-the-canvas-context /

Документация по рисованию изображений: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage

Комментарии:

1. Это сработало! Спасибо! Есть ли другой способ рисовать фигуры с предварительным просмотром без использования двух элементов canvas?

2. Также как я могу сделать так, чтобы мои кнопки выбирали фигуру при нажатии, а затем рисовали с этой формой?

3. Добро пожаловать. Да, есть способ использовать только один холст. Но, конечно, вы должны где-то сохранить результат, который вы не хотите перерисовывать (возможно, изображение). Для любых других несвязанных проблем, пожалуйста, создайте другое сообщение.