【问题标题】:How can I save multiple basic shapes drawn on the same canvas with preview like in paint?如何保存在同一画布上绘制的多个基本形状,并像在油漆中一样预览?
【发布时间】:2020-12-25 02:49:01
【问题描述】:

我正在尝试创建一个像绘画这样的应用程序,您可以在其中通过预览绘制基本形状。我为椭圆实现了一个绘制函数,接下来我想为一条线和一个矩形实现一个。但我不知道如何存储已经绘制的形状并在之后恢复它们。另外,如果我想使用预览进行绘制,我也不知道为什么需要两个画布元素。谢谢!

//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>

【问题讨论】:

    标签: javascript html css


    【解决方案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

    【讨论】:

    • 成功了!谢谢你!是否有另一种方法可以在不使用两个画布元素的情况下通过预览绘制形状?
    • 另外,我怎样才能让我的按钮在按下时选择一个形状,然后用该形状进行绘制?
    • 不客气。是的,有一种方法只使用一个画布。但是当然你必须在某个地方保存你不想被重绘的结果(也许是图像)。对于任何其他不相关的问题,请创建另一个帖子。
    猜你喜欢
    • 2013-04-04
    • 2019-10-19
    • 1970-01-01
    • 2018-10-14
    • 1970-01-01
    • 2012-02-06
    • 2012-01-14
    • 2021-08-10
    • 1970-01-01
    相关资源
    最近更新 更多