1

I currently try to a dashed line in a canvas.

What I tried is ctx.setLineDash([5,5]) and well, if I draw very fast it works, but that's all. The method seems to not work, when I draw a line very slow. So on my canvas, I can draw myself with the mouse, and I want that drawn line to be dashed. Only when I move my mouse very fast, it works, when I move it slow, I just get a straight through line.

What can I do to get it working, even when drawing slowly?

Code:

<html>
    <script type="text/javascript">
    var canvas, ctx, flag = false,
        prevX = 0,
        currX = 0,
        prevY = 0,
        currY = 0,
        dot_flag = false;

    var x = "black",
        y = 2;

    function init() {
        canvas = document.getElementById('can');
        ctx = canvas.getContext("2d");
        ctx.setLineDash([5,5]); //here I try to set the line to a dashed line
        w = canvas.width;
        h = canvas.height;

        canvas.addEventListener("mousemove", function (e) {
            findxy('move', e)
        }, false);
        canvas.addEventListener("mousedown", function (e) {
            findxy('down', e)
        }, false);
        canvas.addEventListener("mouseup", function (e) {
            findxy('up', e)
        }, false);
        canvas.addEventListener("mouseout", function (e) {
            findxy('out', e)
        }, false);
    }

    function color(obj) {
        switch (obj.id) {
            case "green":
                x = "green";
                break;
            case "blue":
                x = "blue";
                break;
            case "red":
                x = "red";
                break;
            case "yellow":
                x = "yellow";
                break;
            case "orange":
                x = "orange";
                break;
            case "black":
                x = "black";
                break;
            case "white":
                x = "white";
                break;
        }
        if (x == "white") y = 14;
        else y = 2;

    }

    function draw() {
        ctx.beginPath();
        ctx.moveTo(prevX, prevY);
        ctx.lineTo(currX, currY);
        ctx.strokeStyle = x;
        ctx.lineWidth = y;
        ctx.stroke();
        ctx.closePath();
    }

    function erase() {
        var m = confirm("Want to clear");
        if (m) {
            ctx.clearRect(0, 0, w, h);
            document.getElementById("canvasimg").style.display = "none";
        }
    }

    function save() {
        document.getElementById("canvasimg").style.border = "2px solid";
        var dataURL = canvas.toDataURL();
        document.getElementById("canvasimg").src = dataURL;
        document.getElementById("canvasimg").style.display = "inline";
    }

    function findxy(res, e) {
        if (res == 'down') {
            prevX = currX;
            prevY = currY;
            currX = e.clientX - canvas.offsetLeft;
            currY = e.clientY - canvas.offsetTop;

            flag = true;
            dot_flag = true;
            if (dot_flag) {
                ctx.beginPath();
                ctx.fillStyle = x;
                ctx.fillRect(currX, currY, 2, 2);
                ctx.closePath();
                dot_flag = false;
            }
        }
        if (res == 'up' || res == "out") {
            flag = false;
        }
        if (res == 'move') {
            if (flag) {
                prevX = currX;
                prevY = currY;
                currX = e.clientX - canvas.offsetLeft;
                currY = e.clientY - canvas.offsetTop;
                draw();
            }
        }
    }
    </script>
    <body onload="init()">
        <canvas id="can" width="400" height="400" style="position:absolute;top:10%;left:10%;border:2px solid;"></canvas>
        <div style="position:absolute;top:12%;left:43%;">Choose Color</div>
        <div style="position:absolute;top:15%;left:45%;width:10px;height:10px;background:green;" id="green" onclick="color(this)"></div>
        <div style="position:absolute;top:15%;left:46%;width:10px;height:10px;background:blue;" id="blue" onclick="color(this)"></div>
        <div style="position:absolute;top:15%;left:47%;width:10px;height:10px;background:red;" id="red" onclick="color(this)"></div>
        <div style="position:absolute;top:17%;left:45%;width:10px;height:10px;background:yellow;" id="yellow" onclick="color(this)"></div>
        <div style="position:absolute;top:17%;left:46%;width:10px;height:10px;background:orange;" id="orange" onclick="color(this)"></div>
        <div style="position:absolute;top:17%;left:47%;width:10px;height:10px;background:black;" id="black" onclick="color(this)"></div>
        <div style="position:absolute;top:20%;left:43%;">Eraser</div>
        <div style="position:absolute;top:22%;left:45%;width:15px;height:15px;background:white;border:2px solid;" id="white" onclick="color(this)"></div>
        <img id="canvasimg" style="position:absolute;top:10%;left:52%;" style="display:none;">
        <input type="button" value="save" id="btn" size="30" onclick="save()" style="position:absolute;top:55%;left:10%;">
        <input type="button" value="clear" id="clr" size="23" onclick="erase()" style="position:absolute;top:55%;left:15%;">
    </body>
    </html>
gman
  • 100,619
  • 31
  • 269
  • 393
nameless
  • 1,483
  • 5
  • 32
  • 78
  • 3
    add a snippet, what you did till now – Durga Jul 17 '17 at 06:37
  • There's not much I can add, it'S basically a drawing solution like here: https://stackoverflow.com/a/8398189/3375021 And then I simply tried to use `ctx.setLineDash([5,5])` to set the line to dashed style, but it doesn't work when drawing slowly – nameless Jul 17 '17 at 06:39
  • Please add a snippet, we can't rely on external codes, moreover when you modified it. From the description and a fast reading of the linked post, I would guess you are continuously drawing over the previous lines => when the new line is shorter than one dash, you get a continuous line. To solve that, store the points in an array, clear your context at each draw, and redraw the whole path, from the very first point to the very last. – Kaiido Jul 17 '17 at 06:52
  • @Kaiido added the snippet. But why does it work when I draw faster then with the same path? – nameless Jul 17 '17 at 06:56
  • because the distance between the last point and the new one is bigger than your dash-array. – Kaiido Jul 17 '17 at 06:57
  • @Kaiido okay, can you help me with code on how to do the thing you suggested? Might be complicated, because if I store the points and redraw everything and then switch back to normal lines it won't work any more (then everything is normal again) – nameless Jul 17 '17 at 07:00

2 Answers2

4

Your problem is that you are currently drawing a new Path at every mousemove. Dashes are made from the path starting point, to its end.
When you do move your mouse slowly, your are actually generating a lot of really small pathes, smaller than the 5px of your dash-array. While when you move your mouse faster, the distance between two points is greater than 10px, and so, you can see the dash.

The solution for this problem is to store your points in an array, and redraw a single path from these stored points every time you do a redraw. This way, your pathes will simply get longer, and your dash will be working fine.

In the following example, I even save a new path at each mouseup, so they can have their own dashed property :

const ctx = c.getContext('2d');

const pathes = []; // this is where we will store all our pathes
let mouse_down = false; // shall we draw ?
c.onmousedown = e => {
  // add a new path object
  pathes.push({
    pts: [], // an array of points
    dashed: check.checked // boolean
  });
  mouse_down = true; // we should draw
}
c.onmouseup = c.onmouseleave = e => mouse_down = false;

c.onmousemove = throttle(e => {
  if (!mouse_down) {
    return;
  } else {
    const rec = c.getBoundingClientRect();
    // add a new point
    addPoint(e.clientX - rec.left, e.clientY - rec.top);
    redraw(); // redraw everything
  }
});

function redraw() {
  ctx.clearRect(0, 0, c.width, c.height); // we clear everything
  // and draw every pathes
  pathes.forEach(path => {
    ctx.setLineDash(path.dashed ? [5, 5] : [0]);
    ctx.beginPath();
    path.pts.forEach(pt => ctx.lineTo(pt.x, pt.y));
    ctx.stroke();
  })
}

function addPoint(x, y) {
  // append to the last one
  const points = pathes[pathes.length - 1].pts;
  points.push({
    x: x,
    y: y
  });
}


// just to avoid unnecessary drawings
function throttle(callback) {
  if (typeof callback !== 'function')
    throw 'A callback function must be passed';
  var active = false;
  var evt;
  var handler = function() {
    active = false;
    callback(evt);
  };
  return function handleEvent(e) {
    evt = e;
    if (!active) {
      active = true;
      requestAnimationFrame(handler);
    }
  };
}
canvas {
  border: 1px solid
}
<label>dashed : <input type="checkbox" id="check" checked></label><br>
<canvas id="c" width="500" height="500"></canvas>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
0
<canvas id='asdf' width='200px' height='200px'></canvas>
var canvas = document.getElementById("asdf");
var abc = canvas.getContext("2d");
abc.setLineDash([5, 3]);
xlm
  • 6,854
  • 14
  • 53
  • 55
Dee_wab
  • 1,171
  • 1
  • 10
  • 23
  • 1
    `Only when I move my mouse very fast, it works, when I move it slow, I just get a straight through line.` – Ionut Necula Jul 17 '17 at 06:45
  • That's exactly what I tried, but it just works, when a line is drawed technically, not when I draw it myself with the mouse (at least when I draw like a normal person, and don't move my mouse very fast) – nameless Jul 17 '17 at 06:48
  • abc.beginPath(); abc.moveTo(0,100); abc.lineTo(300, 100); abc.stroke(); – Dee_wab Jul 17 '17 at 06:51
  • @Dee_wab yes, this works and I know that, but I want to draw the lines with my mouse, not with commands – nameless Jul 17 '17 at 06:58