I'm trying to draw a rectangle (or other path) at the cursors position.
The issue is, if you move your mouse fast enough, the drawing lags behind the cursor considerably (chases it).
To reproduce/ test the issue, I've tried to produce code that is as lean as possible. However there's still a noticeable gap [between the cursor and the rectangle] due to rendering latency, even on a computer with decent specs (Chrome Beta 37, Fedora 20, i7 4770k, etc)
Can anyone posit to the cause, or suggest improvements to the following code to reduce latency:
http://jsfiddle.net/AGp2w/4/
var canvas = document.getElementsByTagName('canvas')[0];
var canvasDim = {
width: canvas.width,
height: canvas.height
};
var canvasOffset = canvas.getBoundingClientRect();
var context = canvas.getContext('2d');
context.stroke = "#000000";
context.fill = "#000000";
var currentPosition = {x:0, y:0};
var previousPosition = currentPosition;
var onlyClearPreviousPositon = true;
canvas.onmousemove = function(e){
currentPosition = {
x: e.clientX - canvasOffset.left,
y: e.clientY - canvasOffset.top
};
};
function drawAtCursor(){
if (onlyClearPreviousPositon){
// experiment - try not clearing the whole canvas
context.clearRect(previousPosition.x - 4, previousPosition.y - 4, 8, 8);
previousPosition = currentPosition;
} else {
context.clearRect(0, 0, canvasDim.width, canvasDim.height);
}
context.fillRect(currentPosition.x - 4, currentPosition.y - 4, 8, 8);
window.requestAnimationFrame(drawAtCursor);
}
drawAtCursor();
This has a tiny bit less latency, but is not useful in a real app:
function handleMouseMove(e){
ctx.clearRect(mouseX-1,mouseY-1,9,9);
mouseX=e.clientX-offsetX;
mouseY=e.clientY-offsetY;
ctx.fillRect(mouseX,mouseY,8,8);
}
The mouse pointer will always be quicker than drawing, so your best bet is not to give the user's eye a reason to perceive latency:
Turn off the mouse cursor while the user is drawing.
http://jsfiddle.net/m1erickson/Cf5TX/
The moving rectangle will act as the mouse cursor, but if the user needs a visual guide, you can:
Also draw crosshairs using a couple of lines.
Related
I am using React and javascript, I have a canvas with a 'player' square and an 'enemy' square. The player position is determined by the mouse coordinates, while I want the 'enemy' to have a pre determined path.
const Canvas = props => {
useEffect( () => {
setEnemy1X(enemy1X+1);
setEnemy1Y(enemy1Y+1);
},[tempGame])
useEffect(() =>{
//canvas variables
const canvas = canvasRef.current
const context = canvas.getContext('2d')
canvas.width = canvas.getBoundingClientRect().width
canvas.height = canvas.getBoundingClientRect().height
setCanvasWidth(canvas.width);
setCanvasHeight(canvas.height);
var player1 = { x: playerX, y: playerY, draggable: gameStatus }
var enemy1 = { x: enemy1X, y: enemy1Y, width: enemy3Width, height: enemy4Height}
// this function is running forever
const drawFun = () => {
context.clearRect(0,0, canvasWidth, canvasHeight)
context.beginPath();
context.fillStyle = '#00f4cc'
context.fillRect(player1.x, player1.y, playerWidth, playerHeight)
context.beginPath();
context.fillStyle = 'red'
context.fillRect(enemy1.x, enemy1.y, enemy1Width, enemy1Height)
requestAnimationFrame(drawFun);
}
drawFun();
//on mouse move
canvas.addEventListener('mousemove', e => {
e.preventDefault();
var rect = e.target.getBoundingClientRect();
var x = e.clientX - rect.left; //x position within the element.
var y = e.clientY - rect.top; //y position within the element.
//if game is running
if( gameStatus ){
setPlayerX(x - playerWidth/2);
setPlayerY(y - playerHeight/2);
The issue I am having right now is I am trying to update the X and Y coordinates of the red square by incrementing the values by 1 using setEnemy1X(enemy1X+1) and setEnemy1Y(enemy1Y+1) if I place this under mousemove event, the animation looks smooth and slow. If i try updating the coordinates within drawFun or the UseEffect at the beginning, the whole canvas flickers as if it has to be redrawn to fast.
To be more clear, I am trying to have that red square move diagonally across the screen by updating the X and Y coordinates. If I do this via a mousemove event, the animation looks nice and smooth, anywhere else I try to update the animation the whole canvas starts flickering because of what I'm assuming is the canvas being updated to many times a second. I tried to include everything I think is relevant to the question, any help greatly appreciated!
I'm trying to achieve something similar to an air hockey table effect with Famo.us. The idea is to have multiple circle bodies that can collide (see battle).
Right now my first concern is getting the ball's vector attributes to zero out on drag start.
I'm trying to use the 'reset()' method from Particle.reset, but am running into tons of issues. Here's some of the code from the codepen I have so far:
ball.draggable.on("start", function(pos) {
var zeroV = new Vector(0, 0, 0);
// need to remove force here
ball.circle.reset(pos.position, zeroV, ball.circle.orientation, zeroV);
});
Any idea how to best zero out the force on the ball once I begin a drag? Also how might I determine velocity on release relative to how fast the user is dragging before release?
The answer to both of your questions lie in the adding and removing a body particle from the physics engine in Famo.us.
Here is example code: jsBin code
Note: This example does not solve your whole solution, but does answer your questions and should help you to get to your desired effect.
Any idea how to best zero out the force on the ball once I begin a drag?
Rather than zero out the force, you will detach the particle from the engine temporarily.
physicsEngine.removeBody(this.particle);
In the example, I am doing this on click of a created circle surface.
ball.particle = new Circle({
radius: radius,
position: [x, y, 0]
});
ball.physicsID = physicsEngine.addBody(ball.particle);
physicsEngine.attach(collision, balls, ball.particle);
ball.on('click', function(){
if (!this.stopped) {
physicsEngine.removeBody(this.particle);
} else {
this.physicsID = physicsEngine.addBody(this.particle);
physicsEngine.attach(collision, balls, this.particle);
balls.push(this.particle);
}
console.log('balls', balls);
this.stopped = !this.stopped;
});
How might I determine velocity on release relative to how fast the user is dragging before release?
When you drag the square surface and on('end'... you pass the velocity to the creation of your particle. You use the velocity from the drag end to start your particle in motion with setVelocity.
ball.particle.setVelocity(velocity);
As you can see in the example code:
sync.on('end', function(data){
if (!surface.createdBall && data.velocity){
var velocity = data.velocity;
surface.createdBall = true;
var endX = position[0] + 0;
var endY = position[1] + 0;
createBall(endX, endY, velocity);
}
});
...
function createBall(x, y, velocity) {
var ball = new Surface({
size: [radius * 2, radius * 2],
properties: {
backgroundColor: 'blue',
borderRadius: (radius * 2) + 'px'
}
});
ball.particle = new Circle({
radius: radius,
position: [x, y, 0]
});
ball.physicsID = physicsEngine.addBody(ball.particle);
physicsEngine.attach(collision, balls, ball.particle);
node.add(ball.particle).add(ball);
balls.push(ball.particle);
console.log('created ball', velocity);
ball.particle.setVelocity(velocity);
surface.createdBall = false;
ball.on('click', function(){
if (!this.stopped) {
physicsEngine.removeBody(this.particle);
} else {
this.physicsID = physicsEngine.addBody(this.particle);
physicsEngine.attach(collision, balls, this.particle);
balls.push(this.particle);
}
console.log('balls', balls);
this.stopped = !this.stopped;
});
}
I have a paint-style application that works with touch events.
The JavaScript code is...
var RADIUS = 10;
var ctx = document.querySelector("canvas").getContext("2d");
var getRandomColorFragment = function () {
return Math.floor(Math.random() * 255);
};
document.body.addEventListener("touchstart", function (event) {
ctx.fillStyle = "rgb(" + [getRandomColorFragment(), getRandomColorFragment(), getRandomColorFragment()].join() + ")";
});
document.body.addEventListener("touchmove", function (event) {
// Prevent default behavior of scrolling elements.
event.preventDefault();
// Get a reference to the first touch placed.
var touch = event.touches[0];
// Draw a circle at the location of the finger.
ctx.beginPath();
ctx.arc(touch.pageX - RADIUS, touch.pageY - RADIUS, RADIUS, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
});
jsFiddle.
You can test this on a platform that doesn't support touch events by using Chrome and opening the Web Inspector's settings and choosing Emulate touch events.
When the finger/pointer moves very fast, it fails to paint the canvas continually like which would be expected (see screenshot above). It seems I can only get the touches' coordinates only as fast as the touchmove event is triggered.
I thought that I could determine if there is a big enough gap between arcs using the Distance Formula (c2 = a2 + b2) and determining if it's larger than the RADIUS constant. This part worked well. The next thing I needed to figure out is how to interpolate between the two points: the previous registered touch's coordinates and the newly registered coordinates'.
So, essentially, I'd like to know how I could interpolate between the two points I have to determine how to fill-in the gap.
You want to draw a line from last mouse position to the new mouse postition. But you are drawing a circle at the new position instead. Why?
Why don't you do it this way?
onTouchStart: ctx.moveTo(x,y);
onTouchMove: ctx.lineTo(x,y);
All interpolation, antialiasing etc. is already included in "lineTo" function. Use ctx.lineCap = "round"; to have round corners.
If you still want to interpolate numbers, here is the function:
function interpolate(a, b, frac) // points A and B, frac between 0 and 1
{
var nx = a.x+(b.x-a.x)*frac;
var ny = a.y+(b.y-a.y)*frac;
return {x:nx, y:ny};
}
interpolate({x:2, y:2}, {x:4,y:4}, 0.5); // returns {x:3, y:3}
I do have a fairly complex figure painted on canvas. (~ 1000 polygons). The repainting time for all of them is about 1 sec (very slow). Now I need to let user move over that figure and display a vertical and horizontal lines (cross hairs) from under mouse position. What is the best technique to paint only those 2 lines without going over all polygons and repaint everything at every mouse move.
Thx
This answer is broken.
You want to draw lines and move them without touching the underlying painting.
In the good old days, the method used was to paint with xor composition on top of the drawing, and draw the same pattern (here it would be lines) the same way at the same location to remove it from the screen, again without touching the real painting.
I tried to use this method with the code below to test it out, but it doesn't seem to work. Hopefully, someone with a better knowledge of canvas and js will come forth and fix things up.
function onmousemove(e){
// firt run
var tcanvas = document.getElementById("testCanvas");
var tcontext = tcanvas.getContext("2d");
var pos = {x : e.clientX, y : e.clientY,
w : tcanvas.width, h : tcanvas.height };
var comp = tcontext.globalCompositeOperation;
var paintline = function (p){
tcontext.save();
tcontext.lineWidth = 1;
tcontext.globalCompositeOperation = 'xor';
tcontext.fillStyle = "#000000";
tcontext.moveTo(0,p.y);
tcontext.lineTo(p.w,p.y);
tcontext.moveTo(p.x,0);
tcontext.lineTo(p.x,p.h);
tcontext.stroke();
tcontext.restore();
};
tcontext.save();
paintline(pos);
tcontext.restore();
var next = function(e){
var comp = tcontext.globalCompositeOperation;
paintline(pos);
pos = {x : e.clientX, y : e.clientY,
w : tcanvas.width, h : tcanvas.height };
paintline(pos);
};
document.onmousemove = next;
}
(function draw_stuff(){
// setup canvas
var tcanvas = document.getElementById("testCanvas");
var tcontext = tcanvas.getContext("2d");
// draw square
tcontext.fillStyle = "#FF3366";
tcontext.fillRect(15,15,70,70);
// set composite property
tcontext.globalCompositeOperation = 'xor';
// draw text
tcontext.fillStyle="#0099FF";
tcontext.font = "35px sans-serif";
tcontext.fillText("test", 22, 25);
// draw circle
tcontext.fillStyle = "#0099FF";
tcontext.beginPath();
tcontext.arc(75,75,35,0,Math.PI*2,true);
tcontext.fill();
document.onmousemove = onmousemove;
})();
Also, there are problem with compositing depending on the browser.
I have a canvas element that automatically fills the entire browser window of the client when loaded. On it you can draw with the mouse, like in the result of any "make a drawing board"-tutorial out there. What I want to do however is to make it so that if you move the mouse to any extreme of the canvas (or maybe select a certain "move"-tool, you can drag the canvas in any direction you'd like), it scrolls. In particular, I want it to be possible to in theory scroll forever, so I can't really pre-generate, I have to generate "more canvas" on the fly. Does any one have any idea on how to do this?
If it helps, this is the client-side javascript right now: (the html is just a canvas-tag)
$(document).ready(function() {
init();
});
function init() {
var canvas = document.getElementById('canvas')
, ctx = canvas.getContext('2d')
, width = window.innerWidth
, height = window.innerHeight;
// Sets the canvas size to be the same as the browser size
canvas.width = width;
canvas.height = height;
// Binds mouse and touch events to functions
$(canvas).bind({
'mousedown': startDraw,
'mousemove': draw,
'mouseup': stopDraw,
});
};
// Triggered on mousedown, sets draw to true and updates X, Y values.
function startDraw(e) {
this.draw = true;
this.X = e.pageX;
this.Y = e.pageY;
};
// Triggered on mousemove, strokes a line between this.X/Y and e.pageX/Y
function draw(e) {
if(this.draw) {
with(ctx) {
beginPath();
lineWidth = 4;
lineCap = 'round';
moveTo(this.X, this.Y);
lineTo(e.pageX, e.pageY);
stroke();
}
this.X = e.pageX;
this.Y = e.pageY;
}
};
// Triggered on mouseup, sets draw to false
function stopDraw() {
this.draw = false;
};
The canvas element uses real memory of your computer, so there is no infinite canvas which scrolls forever. But, you may simulate this behavior using a virtual canvas. Just record the xy coords captured by draw() into an array and calculate a new center of the virtual canvas if the mouse touches the border. Then filter out the xy coords which fit into center +- screen size and draw them.
However, the array recording the xy coords can not grow infinitely and the filter code will get slower over the size of the array. Are 10,000 points enough?
More optimized code will turn the mouse coords into splines and saves only points needed to redraw the (smoothed) path of the mouse.