I am trying to build a simple drawing app with Paper.js. I can draw with the mouse but I have a gap between the cursor and the line drawn on the canvas.
I have realised that the navigation menu on top of the webpage is pushing down the cursor (100px) I assume that the mouse coordinates are taken from top-left (0-0) of the window and the same coordinates are used on the canvas which measured these points from its own top-left corner (see the screenshot). I have tried setting the canvas position to absolute, which helps to get it to the top left corer, but I need it in the center of the window.
How could I fix this?
Thanks!
script:
...
var doc = $(document),
win = $(window),
canvas = $('#paper'),
ctx = canvas[0].getContext('2d')
doc.on('mousemove',function(e){
if($.now() - lastEmit > 30){
socket.emit('mousemove',{
'x': e.pageX,
'y': e.pageY,
'drawing': drawing,
'id': id
});
lastEmit = $.now();
}
// Draw a line for the current user's movement, as it is
// not received in the socket.on('moving') event above
// (because he only broadcats (not receiving))
if(drawing){
drawLine(prev.x, prev.y, e.pageX, e.pageY);
prev.x = e.pageX;
prev.y = e.pageY;
}
});
function drawLine(fromx, fromy, tox, toy){
console.log(fromy + ' ' + toy);
ctx.moveTo(fromx, fromy);
ctx.lineTo(tox, toy);
ctx.stroke();
}
I can repro your issue by using an early version of paperjs - notably this version is the one on jsfiddle. I see the lines offset the way you do with v 0.22 and then correctly rendered with version 0.9.25. Here's my code:
<!-- fails -->
<script type='text/javascript' src='http://cdnjs.cloudflare.com/ajax/libs/paper.js/0.22/paper.js'></script>
<!-- works --> <!--
<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.9.25/paper-full.js'></script>
-->
<style type='text/css'>
#canvas1{
height: 400px;
width: 400px;
border: 1px solid black;
}
</style>
<script type='text/javascript'>
paper.install(window);
window.onload = function() {
paper.setup('canvas1');
var path = new Path();
var tool = new Tool();
path.strokeColor = 'black';
tool.onMouseMove = function(event) {
path.add(event.point);
}
}
</script>
Related
I am learning JavaScript. And I have this task - to draw two rectangles, in one of them, I should move the cursor, and it should appear in the second rectangle. It is not that hard to track the cursor, bu I have no idea how to display it somewhere else. Do I need to create separate canvas? How to display cursor image?
I would be very grateful for any tips!
Here is a simple code that I have for now:
<html>
<body>
<canvas id="myCanvas" width="800" height="600" ></canvas>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.rect(20, 20, 300, 200);
ctx.stroke();
ctx.rect(350, 20, 300, 200);
ctx.stroke();
var cursorX;
var cursorY;
document.onmousemove = function(e){
cursorX = e.pageX;
cursorY = e.pageY;
}
</script>
</body>
You should create an IMG with a cursor in it. Then, when you are moussing over one rectangle, use the img's (or containing DIV's) css to display it over the second rectangle correctly. It is similar to how you might display a tooltip
function (event) {
var x = event.pageX;
var y = event.pageY;
var mouseImg = document.getElementById('mouseImg');
if (mouseImg ) {
$(mouseImg ).css('left',(x + rectangleOffset.x) + 'px');
$(mouseImg ).css('top',y + rectangleOffset.y + 'px');
$(mouseImg ).show();
}
}
I want to draw 200 or more(highly fluid) object in canvas.
and add mouse over, mouse click event each of them.
source code like this...
(valiable k is increase)
'
....
....
for( k = 0 ; k < 200; k++){
start = start[k];
end = end[k];
x1 = centerX-radius*Math.sin(-arg*start)*0.9;
y1 = centerY-radius*Math.cos(-arg*start)*0.9;
x2 = centerX-radius*Math.sin(-arg*start)*0.95;
y2 = centerY-radius*Math.cos(-arg*start)*0.95;
x3 = centerX-radius*Math.sin(-arg*end)*0.95;
y3 = centerY-radius*Math.cos(-arg*end)*0.95;
x4 = centerX-radius*Math.sin(-arg*end)*0.9;
y4 = centerY-radius*Math.cos(-arg*end)*0.9;
Shape(ctx, x1,y1,x2,y2,x3,y3,x4,y4,k);
}
function Shape(ctx, x1,y1,x2,y2,x3,y3,x4,y4, k){
ctx.strokeStyle = "black";
ctx.fillStyle = "red";
ctx.globalAlpha = 1.0;
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.lineTo(x3,y3);
ctx.lineTo(x4,y4);
ctx.lineTo(x1,y1);
ctx.lineWidth = 0.5;
ctx.fill();
ctx.stroke();
ctx.fillText(k,(x2+x3)/2,(y2+y3)/2);
}
....
....
my hope is..
if mouse over on shape, display valiable k
if mouse click on shape, go other url with valiable k parameter
but, I did not want to use image.
please help me.
thanks.
If you keep the shape's coordinates in an array, you can loop trough them and check for collision with the mouse's coordinates.
Canvas are drawn like bitmap. All the changes are made to the pixels, and no trace of line or path would be kept. If you want to see whether a click is on or inside certain path, you need to implement you own hit test. If the shapes you draw can overlap each other, you will need to handle the order by yourself. It's doable, but you are on your own.
The other way out is to use SVG instead. Because SVG are objects, browser will keep track of them for you. You can just add onclick to an SVG element like you do to an HTML element.
The easiest solution would be just use library like d3: http://d3js.org/
Since your shapes are irregular, it would be difficult to do hit-testing mathematically.
Fortunately, the context has the isPointInPath method that will test if the supplied mouseX / mouseY is inside the last defined path.
To hit-test your irregular shapes:
Keep enough information to redefine each path in an object.
Add each shape object to an array.
In the mousemove event handler...
Iterate through the array
Redefine each shape (1 at a time). Note: redefining is drawing without stroke/fill.
Use context.isPointInPath(mouseX,mouseY) to hit-test if the mouse is inside the last defined shape.
Here is example code and a Demo: http://jsfiddle.net/m1erickson/o5xp21t2/
<!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; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var $canvas=$("#canvas");
var canvasOffset=$canvas.offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
ctx.strokeStyle = "black";
ctx.fillStyle = "red";
ctx.globalAlpha = 1.0;
ctx.lineWidth = 0.5;
ctx.font="14px verdana";
var centerX=150;
var centerY=150;
var radius=120;
var arg=1;
var start=0;
var end=Math.PI/8;
var shapes=[];
for(var k=0;k<10;k++){
start+=Math.PI/8;
end+=Math.PI/8;
x1 = centerX-radius*Math.sin(-arg*start)*0.9;
y1 = centerY-radius*Math.cos(-arg*start)*0.9;
x2 = centerX-radius*Math.sin(-arg*start)*0.95;
y2 = centerY-radius*Math.cos(-arg*start)*0.95;
x3 = centerX-radius*Math.sin(-arg*end)*0.95;
y3 = centerY-radius*Math.cos(-arg*end)*0.95;
x4 = centerX-radius*Math.sin(-arg*end)*0.9;
y4 = centerY-radius*Math.cos(-arg*end)*0.9;
var s={x1:x1,y1:y1,x2:x2,y2:y2,x3:x3,y3:y3,x4:x4,y4:y4,k:k};
shapes.push(s);
Shape(s,k,true);
}
$results=$("#results");
$("#canvas").mousemove(function(e){handleMouseMove(e);});
function handleMouseMove(e){
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
for(var k=0;k<shapes.length;k++){
Shape(shapes[k],k,false);
if(ctx.isPointInPath(mouseX,mouseY)){
$results.text("Last mouseover: "+k);
}
}
}
function Shape(s, k, draw){
ctx.fillStyle="red";
ctx.beginPath();
ctx.moveTo(s.x1,s.y1);
ctx.lineTo(s.x2,s.y2);
ctx.lineTo(s.x3,s.y3);
ctx.lineTo(s.x4,s.y4);
ctx.lineTo(s.x1,s.y1);
if(draw){
ctx.fill();
ctx.stroke();
ctx.fillStyle="blue";
ctx.fillText(k,(s.x2+s.x3)/2,(s.y2+s.y3)/2);
}
}
}); // end $(function(){});
</script>
</head>
<body>
<p id=results>Hover mouse over shapes.</p>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
In HTML5 Canvas, what's the simplest way to draw and move a line over an Image (already on the canvas), preserving the image underneath? (e.g. have a vertical line track the mouse X position)
My current canvas:
$(document).ready(function() {
canvas = document.getElementById("myCanvas");
context = canvas.getContext("2d");
imageObj = new Image();
imageObj.onload = function() {
context.drawImage(imageObj, 0,0);
}
imageObj.src = "http://example.com/some_image.png";
$('#myCanvas').click(doSomething);
});
You will have to do most of the ground-work with canvas which in this case you will have to implement the functionality to move the line and then redraw everything.
The steps can be:
Keep the line as an object which can self-render (method on the object)
Listen to mousemove (in this case) in order to move the line
For each move, redraw background (image) then render the line at its new position
You can redraw the background as a whole or you can optimize it to just draw over the last line.
Here is some example code of this and a live demo here:
var canvas = document.getElementById('demo'), /// canvas element
ctx = canvas.getContext('2d'), /// context
line = new Line(ctx), /// our custom line object
img = new Image; /// the image for bg
ctx.strokeStyle = '#fff'; /// white line for demo
/// start image loading, when done draw and setup
img.onload = start;
img.src = 'http://i.imgur.com/O712qpO.jpg';
function start() {
/// initial draw of image
ctx.drawImage(img, 0, 0, demo.width, demo.height);
/// listen to mouse move (or use jQuery on('mousemove') instead)
canvas.onmousemove = updateLine;
}
Now all we need to do is to have a mechnism to update the background and the line for each move:
/// updates the line on each mouse move
function updateLine(e) {
/// correct mouse position so it's relative to canvas
var r = canvas.getBoundingClientRect(),
x = e.clientX - r.left,
y = e.clientY - r.top;
/// draw background image to clear previous line
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
/// update line object and draw it
line.x1 = x;
line.y1 = 0;
line.x2 = x;
line.y2 = canvas.height;
line.draw();
}
The custom line object is in this demo very simple:
/// This lets us define a custom line object which self-draws
function Line(ctx) {
var me = this;
this.x1 = 0;
this.x2 = 0;
this.y1 = 0;
this.y2 = 0;
/// call this method to update line
this.draw = function() {
ctx.beginPath();
ctx.moveTo(me.x1, me.y1);
ctx.lineTo(me.x2, me.y2);
ctx.stroke();
}
}
If you are not gonna do anything specific with the image itself you can also set it as a background-image using CSS. You will still need to clear the canvas before redrawing the line though.
May this is not an actual answer, just in case you need it (in the future). Working with canvas would be better (and easier) with some library. I have tried EaselJS of CreateJS and find myself loving it.
You can have a look at it EaselJS
(I have done an example allow drawing and dragging image using EaselJS long time before)
You can get your "crosshairs" by listening to mousemove events and then:
clear the canvas
draw the image
draw your line at the mouse position
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/jEc7N/
<!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:20px; }
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
ctx.lineWidth=2;
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var img=new Image();
img.onload=function(){
canvas.width=img.width;
canvas.height=img.height;
ctx.drawImage(img,0,0);
}
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/KoolAidMan.png";
function handleMouseMove(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(img,0,0);
ctx.beginPath();
ctx.moveTo(mouseX,0);
ctx.lineTo(mouseX,canvas.height);
ctx.moveTo(0,mouseY);
ctx.lineTo(canvas.width,mouseY);
ctx.stroke();
}
$("#canvas").mousemove(function(e){handleMouseMove(e);});
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
or just use 2 layers:
background layer has image and do not change,
top layer has line, what you can clear and redraw it lots of time without affecting background layer.
I have three arrys:
clickX = [],
clickY = [],
clickDrag = [];
this is what happens when you click down:
$('#canvasCursor').mousedown(function (e) {
console.log('down');
mouseX = e.pageX - this.offsetLeft;
mouseY = e.pageY - this.offsetTop;
paint = true;
addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop);
redraw();
});
here it adds the clicks to the array and draws.:
function redraw() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // Clears the canvas
ctx.strokeStyle = "green";
ctx.lineJoin = "round";
ctx.lineWidth = brushSize*2;
for (var i = 0; i < clickX.length; i++) {
ctx.beginPath();
if (clickDrag[i] && i) {
ctx.moveTo(clickX[i - 1], clickY[i - 1]);
} else {
ctx.moveTo(clickX[i] - 1, clickY[i]);
}
ctx.lineTo(clickX[i], clickY[i]);
ctx.closePath();
ctx.stroke();
}
}
I am trying to get rid of the array way of doing it now because when I change the var brushSize dynamically using a slider it redraws the entire picture in the new size not the size they had at the time. I don't know how to save the size of any specific object in the array and then paint them seperate.
I don't mind if I cant implement the undo function that this way gives me as long as I can fix the change of brush size. Here you can see what I am rambling on about! http://www.taffatech.com/Paint.html
-also it seems slower and im guessing its because its drawing from an array
Do not store painting to array
It slows down drawing critically. Just draw the latest line without clearing canvas. That way lineWeight changes does not affect to before drawings. So remove ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); and for loop. You also might want to change context styles (lineWidth etc.) only when changes occur, not every time you run redraw() function.
Undo support
Making different canvas for every mouse down session and drawing them together you can easily make undo feature. By pressing undo it simply splices latest canvas out of canvases array. Google to learn more about drawing to temporary canvas.
EDIT: Sorry, fixed some typos
Edit 2: And again. It's a bit difficult to test.
There's no reason to redraw each of the points each time. You could modify your listener to do this:
var prevMouseX=null,prevMouseY=null;
$('#canvasCursor').mousedown(function (e) {
paint = true;
console.log('down');
//get current mouse coords
mouseX = e.pageX - this.offsetLeft;
mouseY = e.pageY - this.offsetTop;
if (prevMouseX==null) {
//store these coordinates for next time if they haven't been defined yet
prevMouseX = mouseX;
prevMouseY = mouseY;
}
});
$('#canvasCursor').mousemove(function (e) {
//get current mouse coords
mouseX = e.pageX - this.offsetLeft;
mouseY = e.pageY - this.offsetTop;
if (prevMouseX==null) {
//store these coordinates for next time if they haven't been defined yet
prevMouseX = mouseX;
prevMouseY = mouseY;
}
if (paint) {drawline(mouseX,mouseY,prevMouseX,prevMouseY);}
//store these coordinates for next time
prevMouseX = mouseX;
prevMouseY = mouseY;
});
Where the function drawLine is defined as:
function drawline(x1,y1,x2,y2) {
ctx.strokeStyle = "green";
ctx.lineJoin = "round";
ctx.lineWidth = brushSize*2;
ctx.beginPath();
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.closePath();
ctx.stroke();
}
Here’s how to use canvas to draw like Paint
If you want an undo feature, your best option is to record all line segments drawn by the user.
This is done with a point array that contains all points (polylines) drawn by the user.
To track the brush size and brush color, you need to include this info in your array also.
So each element of the the array will have this info about each line segment:
x: the ending x coordinate of this line segment
y: the ending y coordinate
size: the brush size (lineWidth)
color: the brush color (strokeStyle)
mode: “begin” indicates the beginning of a new line, “end” indicates the end of this line.
How does it work?
When the user is drag-drawing a line segment, each mousemove event is extending the current segment with context.lineTo and context.stroke.
When the user selects a new BrushSize or BrushColor, context.beginPath starts context.beginPath.
When the user holds down the Undo button, the last point in the last line segment is popped off the point array. Then all the surviving line segments are redrawn. The undo button fires every 1/10th of a second when held down.
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/AEYYq/
<!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>
<!--[if lt IE 9]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var lastX;
var lastY;
var mouseX;
var mouseY;
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var isMouseDown=false;
var brushSize=20;
var brushColor="#ff0000";
var points=[];
function handleMouseDown(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
ctx.beginPath();
if(ctx.lineWidth!=brushSize){ctx.lineWidth=brushSize;}
if(ctx.strokeStyle!=brushColor){ctx.strokeStyle=brushColor;}
ctx.moveTo(mouseX,mouseY);
points.push({x:mouseX,y:mouseY,size:brushSize,color:brushColor,mode:"begin"});
lastX=mouseX;
lastY=mouseY;
isMouseDown=true;
}
function handleMouseUp(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseup stuff here
isMouseDown=false;
points.push({x:mouseX,y:mouseY,size:brushSize,color:brushColor,mode:"end"});
}
function handleMouseMove(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousemove stuff here
if(isMouseDown){
ctx.lineTo(mouseX,mouseY);
ctx.stroke();
lastX=mouseX;
lastY=mouseY;
// command pattern stuff
points.push({x:mouseX,y:mouseY,size:brushSize,color:brushColor,mode:"draw"});
}
}
function redrawAll(){
if(points.length==0){return;}
ctx.clearRect(0,0,canvas.width,canvas.height);
for(var i=0;i<points.length;i++){
var pt=points[i];
var begin=false;
if(ctx.lineWidth!=pt.size){
ctx.lineWidth=pt.size;
begin=true;
}
if(ctx.strokeStyle!=pt.color){
ctx.strokeStyle=pt.color;
begin=true;
}
if(pt.mode=="begin" || begin){
ctx.beginPath();
ctx.moveTo(pt.x,pt.y);
}
ctx.lineTo(pt.x,pt.y);
if(pt.mode=="end" || (i==points.length-1)){
ctx.stroke();
}
}
ctx.stroke();
}
function undoLast(){
points.pop();
redrawAll();
}
ctx.lineJoin = "round";
ctx.fillStyle=brushColor;
ctx.lineWidth=brushSize;
$("#brush5").click(function(){ brushSize=5; });
$("#brush10").click(function(){ brushSize=10; });
// Important! Brush colors must be defined in 6-digit hex format only
$("#brushRed").click(function(){ brushColor="#ff0000"; });
$("#brushBlue").click(function(){ brushColor="#0000ff"; });
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
// hold down the undo button to erase the last line segment
var interval;
$("#undo").mousedown(function() {
interval = setInterval(undoLast, 100);
}).mouseup(function() {
clearInterval(interval);
});
}); // end $(function(){});
</script>
</head>
<body>
<p>Drag to draw. Use buttons to change lineWidth/color</p>
<canvas id="canvas" width=300 height=300></canvas><br>
<button id="undo">Hold this button down to Undo</button><br><br>
<button id="brush5">5px Brush</button>
<button id="brush10">10px Brush</button>
<button id="brushRed">Red Brush</button>
<button id="brushBlue">Blue Brush</button>
</body>
</html>
I took this question and used it to create a full featured "coloring book" solution, which I posted on Github. https://github.com/collinph/jl-coloringbook
It handles all of the mouse issues + undo and other things that we were discussing + a lot of sizing issues that could come up. It does store the coordinates in an array and is not slow as some suggested it would be-- not in Chrome or Safari anyway.
I am trying to draw over a canvas by clicking and dragging the mouse. My problem is that, apart from the fact that the line has a very poor quality (I want a more pronounced border) it only respects the mouse position when this is at 0,0. As I move the mouse to the lower corner, the line increments its distance from it as much as when I am in the middle of the canvas, the line is already out of it.
I have my code at: http://jsfiddle.net/ajTkP/12/
I will also post it here:
var MDown = false;
var Color = 'blue';
var Canvas = document.getElementById('canvas');
var Context = Canvas.getContext('2d');
Canvas.onselectstart = function() { return false; };
Canvas.unselectable = "on";
Canvas.style.MozUserSelect = "none";
Canvas.onmousedown = function(e) {
MDown = true;
Context.strokeStyle = Color;
Context.lineWidth = 3;
Context.lineCap = 'round';
Context.beginPath();
Context.moveTo(e.pageX - Position(Canvas).left, e.pageY - 5);
}
Canvas.onmouseup = function() { MDown = false; };
Canvas.onmousemove = function(e) {
if (MDown) {
Context.lineTo(e.pageX - Position(Canvas).left, e.pageY - 5);
Context.stroke();
}
}
function Position(el) {
var position = {left: 0, top: 0};
if (el) {
if (!isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
position.left += el.offsetLeft;
position.top += el.offsetTop;
}
}
return position;
}
Thanks for your help!
You need to set an explicit width and height on the canvas. The default dimensions of a canvas are a width of 300 and a height of 150 (see the spec here). By setting the width and height via CSS you are just stretching the canvas.
Either do:
<canvas id="canvas" width="300" height="200"></canvas>
or set the width/height via JavaScript:
canvas.width = 300;
canvas.height = 200;
See the updated jsfiddle: http://jsfiddle.net/ajTkP/13/
It looks like jimr beat me to the punch about the canvas height and width.
The poor quality of the line though is due to how you're drawing the line. You'll notice that you're calling stroke() on every onmousemove event. Keep in mind that it's keeping track of the path of the line from when you beginPath() to when you closePath(), so you're basically stroking the same line multiple times (every time your mouse moves). This is what's giving you the aliased (blocky-looking) lines, instead of the smooth anti-aliased lines you're expecting.