【问题标题】:how can I automate the eraser or pen tool on Canvas?如何自动化画布上的橡皮擦或钢笔工具?
【发布时间】:2020-10-22 20:48:22
【问题描述】:

我制作了一个简单的绘图工具,并希望按照我制作的路径自动化橡皮擦工具。

目前是红色背景,可以使用我自己制作的橡皮擦功能(就像illustrator上的橡皮擦工具一样),也就是说我点击的地方会被擦掉。但我有一个特定的路径,并希望橡皮擦沿着路径擦除。 (比如自动化橡皮擦。)

我正在使用 addEventListener mousemove atm,但我想将其替换为自动化。

window.addEventListener("load", () => {
  const canvas = document.querySelector("#canvas");
  const c = canvas.getContext("2d");

  canvas.height = window.innerHeight - 20;
  canvas.width = window.innerWidth;

  c.fillStyle = "red";
  c.fillRect(0, 0, window.innerWidth, window.innerHeight);

  let painting = false;

  function startPosition() {
    painting = true;
  }
  function finishedPosition() {
    painting = false;
    c.beginPath();
  }
  

  function draw(e) {
    if (!painting) return;
    c.lineWidth = 100;
    c.lineCap = "round";

    c.lineTo(e.clientX, e.clientY);
    // c.strokeStyle = "rgba(10,100,0,0.2)";
    c.globalCompositeOperation = 'destination-out';
    c.stroke();

    c.beginPath();
    c.moveTo(e.clientX, e.clientY);
  }

  canvas.addEventListener("mousedown", startPosition);
  canvas.addEventListener("mouseup", finishedPosition);
  canvas.addEventListener("mousemove", draw);
});

【问题讨论】:

    标签: javascript canvas path html5-canvas dom-events


    【解决方案1】:

    队列

    如果我得到你的结果,你可以用queue实现这个效果

    基本上,队列是一个数组,您可以在其中将项目添加到顶部,并从底部获取项目。

    JavaScript 在实现队列方面并不是特别有效,但对于 99% 的需求来说,它足够简单且足够快。

    简单队列示例

    此示例在队列末尾绘制点并从队列末尾擦除它们

    requestAnimationFrame(update);
    
    const BRUSH_SIZE = 10;  // radius Note that eraser brush must be a little bigger to 
                            // cover anti-aliasing edges
    const BRUSH_SIZE_ERASE = BRUSH_SIZE + 1;                        
                            
    const pointQueue = []; // just a simple array
    const queueLength = 48; // howl ong the queue should get before removing items
    
    /* To avoid thrashing the garbage collector we will reuse points by adding 
        old unused points to a pool and taking from the pool rather than create 
        new ones if there are any available
     */
    const pool = [];
    
    /* To define points a Vector object */
    var temp;   
    function Vec2(x = 0, y = (temp = x, x === 0 ? (x = 0 , 0) : (x = x.x, temp.y))) { this.x = x; this.y = y }
    Vec2.prototype = {
        init(x, y = (temp = x, x = x.x, temp.y)) { this.x = x; this.y = y; return this }, // assumes x is a Vec2 if y is undefined
    };
    
    const ctx = canvas.getContext("2d");
    const mouse  = {x: 0, y: 0, ox: 0, oy: 0, button: false}
    var w = 0, h = 0, cw = 0, ch = 0;
    
    function update(timer){
        ctx.setTransform(1,0,0,1,0,0); // reset transform
          if (w !== innerWidth || h !== innerHeight){
            cw = (w = canvas.width = innerWidth) / 2;
            ch = (h = canvas.height = innerHeight) / 2;
            
          }
        if (pointQueue.length >= queueLength) {
            let point = pointQueue.shift(); // get point from start of queue
            ctx.globalCompositeOperation = "destination-out";
            ctx.beginPath();
            ctx.arc(point.x, point.y, BRUSH_SIZE_ERASE, 0, Math.TAU);
            ctx.fill();
            pool.push(point);
            ctx.globalCompositeOperation = "source-over";
        }
            
        if (mouse.button) {
            let point;
            if (pool.length) { point = pool.pop() }
            else { point = new Vec2() }
            point.init(mouse);
            ctx.beginPath();
            ctx.arc(point.x, point.y, BRUSH_SIZE, 0, Math.TAU);
            ctx.fill();
            pointQueue.push(point);
        }
            
        requestAnimationFrame(update);
    }
    
    
    Math.TAU = Math.PI * 2;
    function mouseEvents(e) {
          const bounds = canvas.getBoundingClientRect();
        mouse.ox = mouse.x;
        mouse.oy = mouse.y;
          mouse.x = e.pageX - bounds.left - scrollX;
          mouse.y = e.pageY - bounds.top - scrollY;
        mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
        
    }
    ["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
    canvas { position : absolute; top : 0px; left : 0px; }
    div { pointer-events: none; }
    <canvas id="canvas"></canvas>
    <div>Click drag to draw</div>

    示例 2

    该示例每帧重绘队列中的点,为了擦除结尾,我们简单地从队列中删除项目

    requestAnimationFrame(update);
    
    const BRUSH_SIZE = 10;  // radius                   
                            
    const pointQueue = []; // just a simple array
    const queueLength = 48; // how long the queue should get before removing items
    
    /* To avoid thrashing the garbage collector we will reuse points by adding 
        old unused points to a pool and taking from the pool rather than create 
        new ones if there are any available
     */
    const pool = [];
    
    /* To define points a Vector object */
    var temp;   
    function Vec2(x = 0, y = (temp = x, x === 0 ? (x = 0 , 0) : (x = x.x, temp.y))) { this.x = x; this.y = y }
    Vec2.prototype = {
        init(x, y = (temp = x, x = x.x, temp.y)) { this.x = x; this.y = y; return this }, // assumes x is a Vec2 if y is undefined
    };
    
    const ctx = canvas.getContext("2d");
    const mouse  = {x: 0, y: 0, ox: 0, oy: 0, button: false}
    var w = 0, h = 0, cw = 0, ch = 0;
    
    function drawQueue() {
        ctx.lineJoin = ctx.lineCap = "round";
        ctx.lineWidth = BRUSH_SIZE * 2;
        ctx.beginPath();
        for (const p of pointQueue) {
            ctx.lineTo(p.x, p.y);
        }
        ctx.stroke();
    
    }
    
    function update(timer){
        ctx.setTransform(1,0,0,1,0,0); // reset transform
          if (w !== innerWidth || h !== innerHeight){
            cw = (w = canvas.width = innerWidth) / 2;
            ch = (h = canvas.height = innerHeight) / 2;
            
        } else {
            ctx.clearRect(0,0,w,h);
        }
    
        if (pointQueue.length >= queueLength) {
            pool.push(pointQueue.shift()); // get point from start of queue
        }
            
        if (mouse.button) {
            let point;
            if (pool.length) { point = pool.pop() }
            else { point = new Vec2() }
            point.init(mouse);
            pointQueue.push(point);
        }
    
        drawQueue();
            
        requestAnimationFrame(update);
    }
    
    
    Math.TAU = Math.PI * 2;
    function mouseEvents(e) {
          const bounds = canvas.getBoundingClientRect();
        mouse.ox = mouse.x;
        mouse.oy = mouse.y;
          mouse.x = e.pageX - bounds.left - scrollX;
          mouse.y = e.pageY - bounds.top - scrollY;
        mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
        
    }
    ["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
    canvas { position : absolute; top : 0px; left : 0px; }
    div { pointer-events: none; }
    <canvas id="canvas"></canvas>
    <div>Click drag to draw</div>

    绘图工具有无数种不同的行为。上面只是给出了实现一个简单高效的队列的例子。如何使用它来绘制取决于您。

    • 注意 每次从队列中移动一个项目Array.shift 时,JavaScript 需要将数组中的每个项目向下移动一个索引。对于非常长的数组,这会影响性能。

      最好的队列是通过一个预先分配的数组,其中队列的开始和结束(头和尾)是索引,并且不会添加或删除任何项目。当开始或结束索引超过数组长度时,您只需将它们再次移动到开始处。

      但是这样的队列有一个固定的长度,所以你必须注意不要覆盖尾部。

    【讨论】:

      猜你喜欢
      • 2011-04-16
      • 2013-10-19
      • 1970-01-01
      • 2011-12-30
      • 2015-08-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-11
      • 1970-01-01
      相关资源
      最近更新 更多