Reputation: 1724
I have a canvas that is adding dynamically to the page on on load. I want to draw user's mouse path on the canvas, but I found that if I clear the canvas before drawing, it will draw smooth lines, otherwise, it will draw ugly lines like following screenshot!
To test the problem, please uncomment first line of draw_on_canvas
function in the code to see the difference.
$(document).ready(function() {
//Create DRAWING environment
var canvasWidth = 400;
var canvasHeight = 200;
var drawn_shape_list = [];
var current_shape_info = {};
var is_painting = false;
function add_path_to_drawn_shape_list() {
if (current_shape_info.path && current_shape_info.path.length > 0) {
drawn_shape_list.push(current_shape_info);
}
current_shape_info = {};
}
function add_path(x, y) {
current_shape_info.color = "#000000";
current_shape_info.size = 2;
if (!current_shape_info.path) {
current_shape_info.path = [];
}
current_shape_info.path.push({
"x": x,
"y": y
});
}
function draw_on_canvas() {
//Uncomment following line to have smooth drawing!
//context.clearRect(0, 0, context.canvas.width, context.canvas.height); //clear canvas
context.strokeStyle = current_shape_info.color;
context.lineWidth = current_shape_info.size;
context.beginPath();
context.moveTo(current_shape_info.path[0].x, current_shape_info.path[0].y);
for (var i = 1; i < current_shape_info.path.length; i++) {
context.lineTo(current_shape_info.path[i].x, current_shape_info.path[i].y);
}
context.stroke();
}
//Create canvas node
var canvas_holder = document.getElementById('canvas_holder');
canvas = document.createElement('canvas');
canvas.setAttribute('width', canvasWidth);
canvas.setAttribute('height', canvasHeight);
canvas.setAttribute('id', 'whitboard_canvas');
canvas_holder.appendChild(canvas);
if (typeof G_vmlCanvasManager != 'undefined') {
canvas = G_vmlCanvasManager.initElement(canvas);
}
context = canvas.getContext("2d");
$('#canvas_holder').mousedown(function(e) {
var mouseX = e.pageX - this.offsetLeft;
var mouseY = e.pageY - this.offsetTop;
is_painting = true;
add_path(mouseX, mouseY, false);
draw_on_canvas();
});
$('#canvas_holder').mousemove(function(e) {
if (is_painting) {
var mouseX = e.pageX - this.offsetLeft;
var mouseY = e.pageY - this.offsetTop;
var can = $('#whitboard_canvas');
add_path(mouseX, mouseY, true);
draw_on_canvas();
}
});
$('#canvas_holder').mouseup(function(e) {
is_painting = false;
add_path_to_drawn_shape_list();
});
$('#canvas_holder').mouseleave(function(e) {
is_painting = false;
add_path_to_drawn_shape_list();
});
});
#canvas_holder {
border: solid 1px #eee;
}
canvas {
border: solid 1px #ccc;
}
<HTML>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="canvas_holder"></div>
</body>
</HTML>
You can see an example of my code here with two canvas.
I have tried context.lineJoin = "round";
and context.lineCap = 'round';
, but the result didn't change.
Is it normal canvas behavior or I should set something?
Upvotes: 3
Views: 1623
Reputation: 136708
how to draw smooth lines on canvas without clearing it?
You don't. Clearing and redrawing is the way to go.
Is it normal canvas behavior
Yes completely. Unless you perform some action that should clear the canvas, it won't be cleared. So when you draw multiple times over the same area using semi transparent color, the pixels will become darker and darker.
Don't be afraid of performances, having to deal with the compositing of previous drawings may even be slower than drawing a single more complex path.
One thing you can do to improve performances is to use a single Path, so that at every frame only a single paint operation occurs:
const canvas = document.getElementById( 'canvas' );
const ctx = canvas.getContext( '2d' );
const path = new Path2D();
const mouse = {};
function draw() {
// clear all
ctx.clearRect( 0, 0, canvas.width, canvas.height );
// draw the single path
ctx.stroke( path );
// tell we need to redraw next frame
mouse.dirty = false;
}
canvas.onmousedown = (evt) => {
mouse.down = true;
// always use the same path
path.moveTo( evt.offsetX, evt.offsetY );
};
document.onmouseup = (evt) => {
mouse.down = false;
};
document.onmousemove = (evt) => {
if( mouse.down ) {
const rect = canvas.getBoundingClientRect();
path.lineTo( evt.clientX - rect.left, evt.clientY - rect.top );
}
if( !mouse.dirty ) {
mouse.dirty = true;
requestAnimationFrame(draw);
}
};
canvas { border: 1px solid }
<canvas id="canvas" width="500" height="500"></canvas>
If you need to have different path styles, then you can create one path per style.
const canvas = document.getElementById( 'canvas' );
const ctx = canvas.getContext( '2d' );
const makePath = (color) => ({
color,
path2d: new Path2D()
});
const pathes = [makePath('black')];
const mouse = {};
function draw() {
// clear all
ctx.clearRect( 0, 0, canvas.width, canvas.height );
pathes.forEach( (path) => {
// draw the single path
ctx.strokeStyle = path.color;
ctx.stroke( path.path2d );
} );
// tell we need to redraw next frame
mouse.dirty = false;
}
document.getElementById('inp').onchange = (evt) =>
pathes.push( makePath( evt.target.value ) );
canvas.onmousedown = (evt) => {
mouse.down = true;
const path = pathes[ pathes.length - 1 ].path2d;
// always use the same path
path.moveTo( evt.offsetX, evt.offsetY );
};
document.onmouseup = (evt) => {
mouse.down = false;
};
document.onmousemove = (evt) => {
if( mouse.down ) {
const rect = canvas.getBoundingClientRect();
const path = pathes[ pathes.length - 1 ].path2d;
path.lineTo( evt.clientX - rect.left, evt.clientY - rect.top );
}
if( !mouse.dirty ) {
mouse.dirty = true;
requestAnimationFrame(draw);
}
};
canvas { border: 1px solid }
<input type="color" id="inp"><br>
<canvas id="canvas" width="500" height="500"></canvas>
Upvotes: 1