Marker shaped canvas pen - Issues with two silmutaneous strokes - javascript

I am trying to create a marker style pencil with which one can draw on a canvas: a slightly rotated rectangle as pencil tip. My idea was to simply have to simultaneous strokes on mousedown, one of which is moved to the right and to the top a little bit so that is appears like a marker.
This is my code:
function draw(e) {
if (e.buttons !== 1) return;
ctx.beginPath();
ctx.lineWidth = 5;
ctx.lineCap = 'round';
ctx.moveTo(pos.x, pos.y);
setPosition(e);
ctx.lineTo(pos.x, pos.y);
ctx.moveTo(pos.x + 2, pos.y + 2);
setPosition(e);
ctx.lineTo(pos.x + 2, pos.y+1);
ctx.stroke();
}
When I draw slowly, it works perfectly. When I speed up the cursor, however, one of the two strokes is not ecxecuted properly:
Does someone have an idea how I can get both strokes to work?
Thank you!
Edit: Removing some redundancy produced a new issue with the line:
function draw(e) {
if (e.buttons !== 1) return;
ctx.beginPath();
ctx.lineWidth = 5;
ctx.lineCap = 'round';
ctx.moveTo(pos.x, pos.y);
setPosition(e);
ctx.moveTo(pos.x + 2, pos.y + 2);
ctx.stroke();
}
EDIT: example:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
resize();
var pos = { x: 0, y: 0 };
window.addEventListener('resize', resize);
document.addEventListener('mousemove', draw);
document.addEventListener('mousedown', setPosition);
document.addEventListener('mouseenter', setPosition);
function setPosition(e) {
pos.x = e.clientX + $(document).scrollLeft();
pos.y = e.clientY + $(document).scrollTop();
}
function resize() {
ctx.canvas.width = $('#canvas').width();
ctx.canvas.height = $('#canvas').height();
}
function draw(e) {
if (e.buttons !== 1) return;
ctx.beginPath();
ctx.lineWidth = 5;
ctx.lineCap = 'round';
ctx.strokeStyle = '#000000';
ctx.moveTo(pos.x, pos.y);
setPosition(e);
ctx.lineTo(pos.x + 2, pos.y+2);
ctx.stroke();
}
#canvas {
width: 200px;
height: 200px;
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<canvas id="canvas"></canvas>

Related

Using touch events on canvas

I have a program to draw on a HTML canvas with JS. It works fine with the mouse, but when I try the same on on a touch-based device, it draws only a dot. How do I fix this?
Here's the code that handles the mouse events:
let m = document.getElementById('m-container');
const onMouseMove = (e) =>{
m.style.left = e.pageX + 'px';
m.style.top = e.pageY + 'px';
}
m.addEventListener('mousemove', draw);
m.addEventListener('mousedown', setPosition);
m.addEventListener('mouseenter', setPosition);
function setPosition(e) {
pos.x = parseInt(document.getElementById('m-container').style.left, 10);
pos.y = parseInt(document.getElementById('m-container').style.top, 10);
}
function draw(e) {
// mouse left button must be pressed
if (e.buttons !== 1 || isDragging) return;
if(mode=="pen"){
ctx.globalCompositeOperation="source-over";
} else {
ctx.globalCompositeOperation="destination-out";
}
ctx.beginPath(); // begin
ctx.lineWidth = currentDrawWidth;
ctx.lineCap = 'round';
ctx.strokeStyle = currentDrawColour;
ctx.moveTo(pos.x, pos.y); // from
setPosition(e);
ctx.lineTo(pos.x, pos.y); // to
ctx.stroke(); // draw it!
}
Here's the code to handle the touch events:
m.addEventListener('touchmove', touchdraw);
m.addEventListener('touchstart', function (e) {
setPositionTouch(e)
});
m.addEventListener('touchend', function (e) {
setPositionTouch(e)
});
function setPositionTouch(e) {
pos.x = e.clientX;
pos.y = e.clientY;
}
function touchdraw(e) {
if(mode=="pen"){
ctx.globalCompositeOperation="source-over";
} else {
ctx.globalCompositeOperation="destination-out";
}
ctx.beginPath(); // begin
ctx.lineWidth = currentDrawWidth;
ctx.lineCap = 'round';
ctx.strokeStyle = currentDrawColour;
ctx.moveTo(pos.x, pos.y); // from
setPositionTouch(e);
ctx.lineTo(pos.x, pos.y); // to
ctx.stroke(); // draw it!
}
mode is declared separately.

In JavaScript Canvas, How do I keep my drawing inside my canvas after shifting it with a function?

I am trying to move my drawing with "up", "down", "left", "right" and "reset". However, I would like to keep it inside of the canvas at all times, so when I click any of the buttons and move the drawing, it would touch the borders but not go outside of the border. Here is what I have so far...
HTML
<canvas id="myCanvas" width="250" height="250" style="border:1px solid #000000;"></canvas></br>
<button id="up">Up</button><br>
<button id="left">Left</button>
<button id="right">Right</button>
<button id="reset">Reset</button></br>
<button id="down">Down</button>
JavaScript
let c, ctx, pos, centerX, centerY, radius, eyeRadius, eyeXOffset, eyeYOffset
window.onload = function() {
c = document.getElementById("myCanvas");
ctx = c.getContext("2d");
centerX = c.width / 2;
centerY = c.height / 2;
radius = 70;
eyeRadius = 10;
eyeXOffset = 25;
eyeYOffset = 20;
reset();
}
function drawFace(){
//my drawObject() from last assignment - separated into 3 different parts
// Draw the yellow circle
ctx.beginPath();
ctx.arc(centerX + pos.left, centerY + pos.top, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'yellow';
ctx.fill();
ctx.lineWidth = 5;
ctx.strokeStyle = 'black';
ctx.stroke();
ctx.closePath();
}
function drawEyes(){
//my drawObject() from last assignment - separated into 3 different parts
// Draw the eyes
let eyeX = centerX + pos.left - eyeXOffset;
let eyeY = centerY + pos.top - eyeYOffset;
ctx.beginPath();
ctx.arc(eyeX, eyeY, eyeRadius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'black';
ctx.fill();
ctx.closePath();
ctx.beginPath();
eyeX = centerX + pos.left + eyeXOffset;
ctx.arc(eyeX, eyeY, eyeRadius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'black';
ctx.fill();
ctx.closePath();
}
function drawMouth(){
//my drawObject() from last assignment - separated into 3 different parts
// Draw the mouth
ctx.beginPath();
ctx.arc(centerX + pos.left, centerY + pos.top, 50, 0, Math.PI, false);
ctx.stroke();
ctx.closePath();
}
function draw() {
clear();
drawFace();
drawEyes();
drawMouth();
}
function clear() {
ctx.clearRect(0, 0, c.width, c.height);
}
function reset() {
pos = {
left: 0,
top: 0
}
draw();
}
function up() {
pos.top -= 20;
draw();
}
function down() {
pos.top += 20;
draw();
}
function left() {
pos.left -= 20;
draw();
}
function right() {
pos.left += 20;
draw();
}
document.getElementById("reset").onclick = reset;
document.getElementById("up").onclick = up;
document.getElementById("down").onclick = down;
document.getElementById("left").onclick = left;
document.getElementById("right").onclick = right;

Adjust canvas background in Javascript

I'm trying to drawn a rect on canvas, but I want that canvas has lightly transparent background, but that drawn rect has no background.
What I will is something as follows:
I have code as follows:
var canvas = document.getElementById('canvas');
var img = document.getElementById('photo');
var ctx = canvas.getContext('2d');
var rect = {};
var drag = false;
var update = true; // when true updates canvas
var original_source = img.src;
img.src = original_source;
function init() {
img.addEventListener('load', function(){
canvas.width = img.width;
canvas.height = img.height;
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
});
// start the rendering loop
requestAnimationFrame(updateCanvas);
}
// main render loop only updates if update is true
function updateCanvas(){
if(update){
drawCanvas();
update = false;
}
requestAnimationFrame(updateCanvas);
}
// draws a rectangle with rotation
function drawRect(){
ctx.setTransform(1,0,0,1,rect.startX + rect.w / 2, rect.startY + rect.h / 2);
ctx.rotate(rect.rotate);
ctx.beginPath();
ctx.rect(-rect.w/2, -rect.h/2, rect.w, rect.h);
/* ctx.fill(); */
ctx.stroke();
}
// clears canvas sets filters and draws rectangles
function drawCanvas(){
// restore the default transform as rectangle rendering does not restore the transform.
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawRect()
}
// create new rect add to array
function mouseDown(e) {
rect = {
startX : e.offsetX,
startY : e.offsetY,
w : 1,
h : 1,
rotate : 0,
};
drag = true;
}
function mouseUp() { drag = false; buttons_shown = true; update = true; }
function mouseMove(e) {
if (drag) {
rect.w = (e.pageX - this.offsetLeft) - rect.startX;
rect.h = (e.pageY - this.offsetTop) - rect.startY;
update = true;
}
}
init();
.hide{
display: none !important;
}
canvas{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display:inline-block;
background:rgba(0,0,0,0.3);
}
<div style="position: relative; overflow: hidden;display:inline-block;">
<img id="photo" src="http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg"/>
<canvas id="canvas"></canvas>
</div>
<div id="buttons" class="hide"></div>
In my example I set the background of the canvas to what I will but I cannot remove that background for the drawn rect, it has the same color as the canvas.
Here is the fiddle.
Any idea how to solve it?
This could be achieved in several ways (compositing, clip-path...) but the easiest for such a simple path is probably to use the "evenodd" fill-rule parameter of fill() method which will allow us to draw this rectangle with a hole.
The process is simply to draw a first rect the size of the canvas, then, in the same path declaration, draw your own smaller rectangle. The fill-rule will then exclude this smaller inner rectangle from the bigger one.
function drawRect() {
ctx.beginPath(); // a single path
// the big rectangle, covering the whole canvas
ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
// your smaller, inner rectangle
ctx.setTransform(1, 0, 0, 1, rect.startX + rect.w / 2, rect.startY + rect.h / 2);
ctx.rotate(rect.rotate);
ctx.rect(-rect.w / 2, -rect.h / 2, rect.w, rect.h);
// set the fill-rule to evenodd
ctx.fill('evenodd');
// stroke
// start a new Path declaration
ctx.beginPath
// redraw only the small rect
ctx.rect(-rect.w / 2, -rect.h / 2, rect.w, rect.h);
ctx.stroke();
}
var canvas = document.getElementById('canvas');
var img = document.getElementById('photo');
var ctx = canvas.getContext('2d');
var rect = {};
var drag = false;
var update = true; // when true updates canvas
var original_source = img.src;
img.src = original_source;
function init() {
img.addEventListener('load', function() {
canvas.width = img.width;
canvas.height = img.height;
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
// set our context's styles here
ctx.fillStyle = 'rgba(0,0,0,.5)';
ctx.strokeStyle = 'white';
ctx.lineWidth = 2;
});
// start the rendering loop
requestAnimationFrame(updateCanvas);
}
// main render loop only updates if update is true
function updateCanvas() {
if (update) {
drawCanvas();
update = false;
}
requestAnimationFrame(updateCanvas);
}
// draws a rectangle with rotation
// clears canvas sets filters and draws rectangles
function drawCanvas() {
// restore the default transform as rectangle rendering does not restore the transform.
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawRect()
}
// create new rect add to array
function mouseDown(e) {
rect = {
startX: e.offsetX,
startY: e.offsetY,
w: 1,
h: 1,
rotate: 0,
};
drag = true;
}
function mouseUp() {
drag = false;
buttons_shown = true;
update = true;
}
function mouseMove(e) {
if (drag) {
rect.w = (e.pageX - this.offsetLeft) - rect.startX;
rect.h = (e.pageY - this.offsetTop) - rect.startY;
update = true;
}
}
init();
.hide {
display: none !important;
}
canvas {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: inline-block;
background: rgba(0, 0, 0, 0.3);
}
<div style="position: relative; overflow: hidden;display:inline-block;">
<img id="photo" src="http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg" />
<canvas id="canvas"></canvas>
</div>
<div id="buttons" class="hide"></div>
Try filling your rect before calling ctx.stroke(), like this:
ctx.fillStyle = "rgba(255, 255, 255, 0.3)";
ctx.fill();
This will produce similar effect to what you have shown in your question. Now the inside of rectangle has both css and fillStyle effect, so it's not ideal - for even better effect you would have to fill outside of rect with desired style instead of setting background in css.
first, draw a full canvas with semi-transparent background, like
ctx.fillStyle = 'rgba(32, 32, 32, 0.7)';
ctx.fillRect(0, 0, width, height);
Then just clear your rectangular form this canvas
ctx.clearRect(x, y, mini_width, mini_height);

Drawing circle/ellipse on HTML5 canvas using mouse events

I want something like ellipse option in paint for drawing on my canvas. I have achieved this partially. The problem is I am not able to get radius of circle; currently I have hard coded it to 15. Also I want to draw an ellipse (like in paint) not exact circle.
This is my code for drawing circle on canvas using mouse events. Please help me with code to achieve my above mentioned requirements.
function tool_circle() {
var tool = this;
this.started = false;
this.mousedown = function (ev) {
tool.started = true;
tool.x0 = ev._x;
tool.y0 = ev._y;
};
this.mousemove = function (ev) {
if (!tool.started) {
return;
}
context.fillStyle = 'red';
var distance = Math.sqrt(Math.pow(tool.x0 - ev._x, 2) + Math.pow(tool.y0 - ev._y));
context.beginPath();
context.arc(tool.x0, tool.y0,15, 0, Math.PI * 2, false);
context.stroke();
context.fill();
};
this.mouseup = function (ev) {
if (tool.started) {
tool.mousemove(ev);
tool.started = false;
img_update();
}
};
}
I would something similar as with markE's answer however, using Bezier curve will draw ellipses but it won't give you the exact radius that you probably would need.
For that a function to draw a manual ellipse is needed, and it's rather simple -
This function will take a corner start point and and end point and draw an ellipse exactly within that boundary:
Live demo
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();
}
The demo marks the rectangle area too to show that the ellipse is exactly within it.
Draw
To handle mouse operation that will let you draw the ellipse you can do:
var canvas = document.getElementById('myCanvas'),
ctx = canvas.getContext('2d'),
w = canvas.width,
h = canvas.height,
x1, /// start points
y1,
isDown = false; /// if mouse button is down
/// handle mouse down
canvas.onmousedown = function(e) {
/// get corrected mouse position and store as first point
var rect = canvas.getBoundingClientRect();
x1 = e.clientX - rect.left;
y1 = e.clientY - rect.top;
isDown = true;
}
/// clear isDown flag to stop drawing
canvas.onmouseup = function() {
isDown = false;
}
/// draw ellipse from start point
canvas.onmousemove = function(e) {
if (!isDown) return;
var rect = canvas.getBoundingClientRect(),
x2 = e.clientX - rect.left,
y2 = e.clientY - rect.top;
/// clear canvas
ctx.clearRect(0, 0, w, h);
/// draw ellipse
drawEllipse(x1, y1, x2, y2);
}
A tip can be to create a top canvas on top of your main canvas and do the drawing itself there. When mouse button is released then transfer the drawing to your main canvas. This way you don't have to redraw everything when drawing a new shape.
Hope this helps!
Here's an example of how to drag-draw an oval.
Demo: http://jsfiddle.net/m1erickson/3SFJy/
Example code using 2 Bezier curves to drag-draw an oval:
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; padding:0px;}
#canvas{ border:1px solid blue; }
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var startX;
var startY;
var isDown=false;
function drawOval(x,y){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.beginPath();
ctx.moveTo(startX, startY + (y-startY)/2);
ctx.bezierCurveTo(startX, startY, x, startY, x, startY + (y-startY)/2);
ctx.bezierCurveTo(x, y, startX, y, startX, startY + (y-startY)/2);
ctx.closePath();
ctx.stroke();
}
function handleMouseDown(e){
e.preventDefault();
e.stopPropagation();
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
isDown=true;
}
function handleMouseUp(e){
if(!isDown){ return; }
e.preventDefault();
e.stopPropagation();
isDown=false;
}
function handleMouseOut(e){
if(!isDown){ return; }
e.preventDefault();
e.stopPropagation();
isDown=false;
}
function handleMouseMove(e){
if(!isDown){ return; }
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
drawOval(mouseX,mouseY);
}
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});
}); // end $(function(){});
</script>
</head>
<body>
<h4>Drag to create a circle or oval</h4>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
Here's my way of drawing an ellipse onto a canvas with mouse drag.
It uses radius of 1, but dynamic scaling to get the ellipse effect :)
https://jsfiddle.net/richardcwc/wdf9cocz/
//Canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
//Variables
var scribble_canvasx = $(canvas).offset().left;
var scribble_canvasy = $(canvas).offset().top;
var scribble_last_mousex = scribble_last_mousey = 0;
var scribble_mousex = scribble_mousey = 0;
var scribble_mousedown = false;
//Mousedown
$(canvas).on('mousedown', function(e) {
scribble_last_mousex = parseInt(e.clientX-scribble_canvasx);
scribble_last_mousey = parseInt(e.clientY-scribble_canvasy);
scribble_mousedown = true;
});
//Mouseup
$(canvas).on('mouseup', function(e) {
scribble_mousedown = false;
});
//Mousemove
$(canvas).on('mousemove', function(e) {
scribble_mousex = parseInt(e.clientX-scribble_canvasx);
scribble_mousey = parseInt(e.clientY-scribble_canvasy);
if(scribble_mousedown) {
ctx.clearRect(0,0,canvas.width,canvas.height); //clear canvas
//Save
ctx.save();
ctx.beginPath();
//Dynamic scaling
var scalex = 1*((scribble_mousex-scribble_last_mousex)/2);
var scaley = 1*((scribble_mousey-scribble_last_mousey)/2);
ctx.scale(scalex,scaley);
//Create ellipse
var centerx = (scribble_last_mousex/scalex)+1;
var centery = (scribble_last_mousey/scaley)+1;
ctx.arc(centerx, centery, 1, 0, 2*Math.PI);
//Restore and draw
ctx.restore();
ctx.strokeStyle = 'black';
ctx.lineWidth = 5;
ctx.stroke();
}
//Output
$('#output').html('current: '+scribble_mousex+', '+scribble_mousey+'<br/>last: '+scribble_last_mousex+', '+scribble_last_mousey+'<br/>mousedown: '+scribble_mousedown);
});
canvas {
cursor: crosshair;
border: 1px solid #000000;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" width="800" height="500"></canvas>
<div id="output"></div>

HTML5 Canvas drawing ellipse increasingly

I am writing on whiteboard using HTML5 drawing.
The problem is when I am trying to draw ellipse, so press down and drag, many ellipse are drawn.
ctx.moveTo(startX, startY + (y-startY)/2);
ctx.bezierCurveTo(startX, startY, x, startY, x, startY + (y-startY)/2);
ctx.bezierCurveTo(x, y, startX, y, startX, startY + (y-startY)/2);
ctx.stroke();
I want to show only one ellipse every time.
Any help?
This might help - this is my version of drawing an ellipse using only arc and scaling.
https://jsfiddle.net/richardcwc/wdf9cocz/
//Canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
//Variables
var scribble_canvasx = $(canvas).offset().left;
var scribble_canvasy = $(canvas).offset().top;
var scribble_last_mousex = scribble_last_mousey = 0;
var scribble_mousex = scribble_mousey = 0;
var scribble_mousedown = false;
//Mousedown
$(canvas).on('mousedown', function(e) {
scribble_last_mousex = parseInt(e.clientX-scribble_canvasx);
scribble_last_mousey = parseInt(e.clientY-scribble_canvasy);
scribble_mousedown = true;
});
//Mouseup
$(canvas).on('mouseup', function(e) {
scribble_mousedown = false;
});
//Mousemove
$(canvas).on('mousemove', function(e) {
scribble_mousex = parseInt(e.clientX-scribble_canvasx);
scribble_mousey = parseInt(e.clientY-scribble_canvasy);
if(scribble_mousedown) {
ctx.clearRect(0,0,canvas.width,canvas.height); //clear canvas
//Save
ctx.save();
ctx.beginPath();
//Dynamic scaling
var scalex = 1*((scribble_mousex-scribble_last_mousex)/2);
var scaley = 1*((scribble_mousey-scribble_last_mousey)/2);
ctx.scale(scalex,scaley);
//Create ellipse
var centerx = (scribble_last_mousex/scalex)+1;
var centery = (scribble_last_mousey/scaley)+1;
ctx.arc(centerx, centery, 1, 0, 2*Math.PI);
//Restore and draw
ctx.restore();
ctx.strokeStyle = 'black';
ctx.lineWidth = 5;
ctx.stroke();
}
//Output
$('#output').html('current: '+scribble_mousex+', '+scribble_mousey+'<br/>last: '+scribble_last_mousex+', '+scribble_last_mousey+'<br/>mousedown: '+scribble_mousedown);
});
canvas {
cursor: crosshair;
border: 1px solid #000000;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" width="800" height="500"></canvas>
<div id="output"></div>

Categories

Resources