I wrote a little Javascript app to track and display the x- and y- components of the mouse's velocity. It subtracts the cursor's previous position from its current position, and divides by time. Pretty simply stuff. Here is the complete working source:
<!DOCTYPE html5>
<html>
<head>
<meta charset="UTF-8">
<style>
* {
background-color:#000000;
}
html {
width:100%;
height:100%;
}
#readout {
background-color:#FFFFFF;
border: 8px solid #34a1ff;
width: 162px;
height: 100px;
position:absolute;
top:0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
padding: 16px;
}
.text {
background-color:#FFFFFF;
}
</style>
</head>
<body>
<div id="readout">
<span id="xLabel" class="text">X: </span>
<span id="xValue" class="text"></span>
<br>
<span id="yLabel" class="text">Y: </span>
<span id="yValue" class="text"></span>
</div>
<script>
// Where the speed will be displayed
window.xDisplay = document.getElementById("xValue");
window.yDisplay = document.getElementById("yValue");
// Keep track of last time mouse was moved
window.lastTime = new Date().getTime();
window.lastDeltaTime = 0;
window.lastMouseX = 0;
window.lastMouseY = 0;
window.lastVX = 0; // for smoothing
window.lastVY = 0;
// Listen for mouse move event
document.addEventListener('mousemove', function(e){
// Get current mouse position
var currentX = e.clientX || e.pageX;
var currentY = e.clientY || e.pageY;
// Get distance travelled from last mouse position
var deltaX = currentX - lastMouseX;
var deltaY = currentY - lastMouseY;
// Update mouse position
lastMouseX = currentX;
lastMouseY = currentY;
// Get current time
var currentTime = new Date().getTime();
// Get time elapsed since last mouse event
var deltaTime = currentTime - lastTime;
// Update last time
lastTime = currentTime;
// Get velocity components
var xSpeed = deltaX / deltaTime;
var ySpeed = deltaY / deltaTime;
// Smooth out velocity
var xSmooth = (xSpeed*2 + lastVX)/3;
var ySmooth = (ySpeed*2 + lastVY)/3;
// Update previous components
lastVX = xSpeed;
lastVY = ySpeed;
// Display velocity
xDisplay.innerHTML = xSmooth.toFixed(3);
yDisplay.innerHTML = ySmooth.toFixed(3);
}, false);
</script>
</body>
</html>
This will be used in an app that is intended to run in fullscreen mode. The problem I encounter is when the cursor ends up all the way at an edge of the screen, and the user keeps moving the mouse in that direction. (Example: the cursor is all the way at the right edge of the screen, but the user keeps moving their mouse toward the right).
In the above scenario, the app displays a velocity of zero, since the cursor position is not being updated. However, I am in need to a solution that continues to display the actual mouse velocity even after the cursor has reached the edge (none of the similar questions on this site address this issue).
This is important since the use case will be in a WebGL context in which the mouse is used to control rotation from a first person view. The user needs to be able to keep on rotating their view around as many times as they want where rotation velocity is based on mouse velocity (not position!) and this simply doesn't work by calculating mouse speed from cursor position.
This may require some creativity but I'm sure it can be done. Thanks in advance for any solutions!
Found the solution, in case anyone ever stumbles upon this later.
The Pointer Lock API does exactly what I needed.
Related
I have two DIV's of different widths on top of each other. The top DIV displayDIV is wider than the bottom DIV captureDIV.
In the displayDIV I'm drawing a dot who's X position is proportionate to the mouse position within captureDIV.
As you move the mouse in captureDIV the dot moves proportionately in DisplayDIV.
It makes much more sense if you look at this fiddle
My code is as follows...
let capture = document.getElementById('captureDIV');
let display = document.getElementById('displayDIV');
let circle = document.getElementById('circle');
capture.addEventListener('mousemove', handleMouseMove);
function handleMouseMove(event) {
const captureRect = capture.getBoundingClientRect();
const captureWidth = captureRect.right - captureRect.left;
const relativeX = event.x - captureRect.left;
let percent = (relativeX / captureWidth) * 100;
let roundedPercent = parseFloat(Math.round(percent * 100) / 100).toFixed(2);
moveDotTo(roundedPercent);
}
function moveDotTo(percentage) {
const displayRect = display.getBoundingClientRect();
const displayWidth = displayRect.right - displayRect.left;
const circleX = displayRect.left + displayWidth * (percentage / 100);
const circleY = displayRect.top + (displayRect.height / 2);
const style = `top:${circleY}px;left:${circleX}px;`;
circle.setAttribute('style', style);
}
I also have a number of buttons that can set the position of the dot within DisplayDIV such as...
let move20 = document.getElementById('move20');
move20.addEventListener('click', function(event) {
moveDotTo(20);
});
Using Vanilla JS not CSS tricks, how can I create a function to animate (rather than move) the dot from its existing position to the new position.
function animateDotTo(percentage) {
// clever code here
}
I need to be able to call the animateDotTo(percentage) function from either a button or from the mousemove event handler.
The dot should always animate to its new position regardless of how the move is triggered. For instance if the mouse is moved out of the left side of the captureDIV round the bottom and then into the right side of the captureDIV the dot should animate across the DisplayDIV not jump as it does now. Equally pressing one of the move to x% buttons should animate the dot from its current position to the new one.
If you are drawing a circle and moving it around, I would suggest drawing to a <canvas> element instead of moving a <div> by setting its top and left properties. Even using transform: translate(x, y) might be better.
In order to smoothly transition your dot from one location to another, using JavaScript, you will want:
The dot's current position as x and y coordinates,
The dot's target position as x and y coordinates, and
The speed at which the dot moves as a scalar.
Updating the current position is done at every animation frame with window.requestAnimationFrame. With these in hand, and a way of applying the resulting calculated position to the dot, you can use a method like this one: How to move an object using X and Y coordinates in JavaScript to move your dot (the example moves a canvas, but if you know the x and y, then you can set them to top and bottom).
Answering my own question, with thanks to Billy Brown for pointing me in the right direction. Using window.requestAnimationFrame is the way to go.
var currentPercentage;
var startPercentage;
var targetPercentage;
function animateDotTo(percentage) {
targetPercentage = percentage;
startPercentage = currentPercentage;
window.requestAnimationFrame(step);
}
function step(timestamp) {
var fps = 7;
var maxStep = 30;
var distStartToTarget = Math.abs(startPercentage - targetPercentage);
var stepSize = Math.min(distStartToTarget / fps, maxStep);
if (targetPercentage < startPercentage) {
currentPercentage -= stepSize,0;
if (currentPercentage > targetPercentage) {
window.requestAnimationFrame(step);
}
} else if (targetPercentage > startPercentage) {
currentPercentage += stepSize,100;
if (currentPercentage < targetPercentage) {
window.requestAnimationFrame(step);
}
} else {
return;
}
if (currentPercentage > 100 ) { currentPercentage = 100; }
if (currentPercentage < 0 ) { currentPercentage = 0; }
moveDotTo(currentPercentage);
}
Updated fiddle
A simple trick in css transition will fix this.
Of course. You don't want it to animate when you're actually moving the mouse. So what I did is that I separate the transition css property on another class and then remove that class on mouse move, re-attaching it when we click the move buttons.
CSS
#circle {
position: absolute;
left: -100px;
top: -100px;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #000;
transition: none;
}
#circle.animate{
transition: 500ms ease;
}
JS
move20.addEventListener('click', function(event) {
moveDotTo(20); animateDotTo();
});
move60.addEventListener('click', function(event) {
moveDotTo(60);animateDotTo();
});
move80.addEventListener('click', function(event) {
moveDotTo(80);animateDotTo();
});
function moveDotTo(percentage) {
circle.classList.remove("animate");
const displayRect = display.getBoundingClientRect();
const displayWidth = displayRect.right - displayRect.left;
const circleX = displayRect.left + displayWidth * (percentage / 100);
const circleY = displayRect.top + (displayRect.height / 2);
const style = `top:${circleY}px;left:${circleX}px;`;
circle.setAttribute('style', style);
}
function animateDotTo(percentage) {
circle.classList.add("animate");
}
http://jsfiddle.net/8pm2grjd/
If you want it to animate even if you're triggering the movement using mousemove, you can disregard the class approach and just slap the transition property on the css. But this will simulate the annoying mouse delay effect similar to input delay on video games due to V-Sync.
So I have been trying endlessly to try and do something similar too what this site is doing (http://whois.domaintools.com/). I'm trying to get a webpage, so wherever the mouse moves over the webpage, that kind of effect follows it (I'm sorry I don't know what I would call the effect).
I've read how to ask questions on here, but I don't know what too look for so it's difficult for me to attempt this. So far this link (http://p5js.org/learn/demos/Hello_P5_Drawing.php) I've used the code from this and played around with it but i'm just puzzled as too how I would go about doing this.
Thanks for any help, I've been banging my head against a brick wall for a good couple of days now.
This seems to be some kind of particle system. I would start the following way: First create a class for a particle, it should have a random x and y coordinate, and it should change it's postion periodically to a random new postion. Then create a lot of instances of the particle and distribute them over the page.
http://jsfiddle.net/aggoh0s1/3/
/* each particle will move in a 100px100px square */
var gutterWidth = 100;
/* class definition */
var Particle = function(x, y) {
var t = this;
t.x = x;
t.y = y;
t.elem = $('<div class="particle" />');
t.elem.css({ left: x+"px", top: y+"px"});
$('body').append(t.elem);
/* create a new position every 500-1000 milliseconds */
var milliSecs = 500 + Math.random() * 500;
t.ptinterval = setInterval(function() {
var dx = Math.round(Math.random() * gutterWidth);
var dy = Math.round(Math.random() * gutterWidth);
t.elem.animate({left: (t.x + dx)+"px", top: (t.y + dy) + "px"}, 600);
}, milliSecs);
};
/* create a 1000px1000px area where particles are placed each 100px */
var particles = [];
var newParticle;
for(var x = 0; x < 1000; x = x + gutterWidth) {
for(var y = 0; y < 1000; y = y + gutterWidth) {
newParticle = new Particle(x,y);
particles.push(newParticle);
}
}
CSS:
.particle {
width: 2px;
height: 2px;
background-color: black;
position: absolute;
}
Using this logic, you could also use a canvas to display the particles instead of a html div like it is done on whois.domaintools.com. The next step should be to connect the particles with lines to each other, and after that some code should hide all particles that are some distance away from the mouse position.
I've developed the following solution for the effect which you are referring. This is done using jQuery using the event mousemove(). Bind this event to your body where the content is.
Method :
Create an element with the following css on your body. You can create the element onthefly using jQuery as well.
<div class='hover'></div>
CSS
.hover{
position:absolute;
width:100px;
height:100px;
background-color:#fff;
}
The add the following code to your page.
$('body').mousemove(function(event){
$('.hover').css({
'top' : event.pageY,
'left': event.pageX
})
});
The above code will bind an event to your mouse move and I change the element position according to the mouse coordinates.
This fiddle shows a running example
I've given you the basic idea of the solution! You will have to medle with the css and jquery to add the looks and feels of the effect which you refer to.
See the simple example
<img id="imgMove" src="Images/img1.jpg" height="100" width="100" style="position: absolute;" />
JQuery
$(document).ready(function () {
$(document).mousemove(function (e) {
$("#imgMove").css({ "top": e.pageY - 50, "left": e.pageX - 50 }); // e.pageX - Half of Image height, width
})
})
I've set up a jsfiddle illustrating my situation: http://jsfiddle.net/j5o0w5qc/1/
Basically, I've got three nested HTML elements: a viewport div on the outside, a stage div in the middle, and a canvas on the inside. The stage div provides a perspective setting for 3d transformations applied to the canvas. The viewport has overflow: hidden; so we don't see anything outside of the viewport. It also has a listener attached, listening for mousedown.
In my actual app that I'm building, the canvas might be transformed to any arbitrary 3d transformation, involving translation and rotation in 3d space. What I would like to happen is for the viewport div to intercept a click, and draw a spot on the canvas in the place you clicked. I'm intercepting the event with the viewport div, and I'm using offsetX and offsetY in Chrome. This works great for Chrome, but I know I can't rely on offsetX and offsetY in other browsers, so I'd like to use pageX and pageY, normalized via jQuery, but I'm not sure quite how to do that.
What I've currently got in the jsfiddle works great in Chrome, except when you click in the viewport NOT on the canvas. When you click on the canvas, it draws a dot there, regardless of the canvas's transformation. Chrome is doing all the hard work and giving me exactly what I want with offsetX and offsetY. However, when you click in the viewport NOT on the canvas, I guess it's giving me offsetX and offsetY values relative to the viewport, rather than the canvas, and then interpreting that and drawing a dot on the canvas. For example, if I transform the canvas and then click in the upper right corner of the viewport, a dot appears in the upper right corner of the canvas, regardless of where that corner actually appears on the page.
In Firefox, however, it works great as long as there is no transformation applied to the canvas, but as soon as the canvas is transformed, all of a sudden, the dot being drawn is displaced, and I can't figure out how to take my pageX and pageY values and figure out exactly where in the canvas I am clicking.
Does anyone have any brilliant solutions? I've been bashing my head against this problem for far too long. I'm pretty sure I need to manually calculate some 3d transformation matrices or something, and I've spent hours writing methods to return the inverse of a matrix, and to multiply a matrix by a vector, and all sorts of stuff, but none of it has actually solved the problem for me, and I'm not sure what I'm missing.
Stackoverflow says code is required with jsfiddle links, so here's all my code:
HTML:
<div id="viewport">
<div id="stage">
<canvas id="myCanvas" width="300" height="300"></canvas>
</div>
</div>
<div id="stuff">
<button onclick="transformMe()">Transform</button>
<input id="blah" type="text" size="45"></input>
</div>
CSS:
#viewport, #stage, #myCanvas {
width: 300px;
height: 300px;
position: absolute;
top: 0;
left: 0;
}
#viewport {
border: 1px solid #000;
overflow: hidden;
}
#stage {
perspective: 1000px;
transform-style: preserve-3d;
}
#myCanvas {
background-color: green;
transform-style: preserve-3d;
}
#stuff {
position: absolute;
top: 350px;
}
Javascript:
var counter = 0;
$('#viewport').mousedown(function _drawOnCanvas (e)
{
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var xpos, ypos;
if (typeof e.offsetX=='undefined')
{
xpos = e.pageX - $('#myCanvas').offset().left;
ypos = e.pageY - $('#myCanvas').offset().top;
}
else
{
xpos = e.offsetX;
ypos = e.offsetY;
}
ctx.fillRect(xpos-5, ypos-5, 10, 10);
});
function transformMe()
{
counter++;
var angle = (counter * 30) % 360;
$('#myCanvas').css('transform','perspective(1000px) rotate3d(5,6,7,' + angle + 'deg)');
$('input').val('counter: ' + counter + ', angle: ' + angle);
};
For Firefox, you can use event.layerX and event.layerY. Think of them as Firefox's versions of offsetX & offsetY.
DEMO: http://jsfiddle.net/dirtyd77/j5o0w5qc/3/
JAVASCRIPT:
var counter = 0;
$('#viewport').mousedown(function _drawOnCanvas (e)
{
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var xpos, ypos;
if (typeof e.offsetX=='undefined')
{
xpos = e.originalEvent.layerX;
ypos = e.originalEvent.layerY;
}
else
{
xpos = e.offsetX;
ypos = e.offsetY;
}
ctx.fillRect(xpos-5, ypos-5, 10, 10);
});
function transformMe()
{
counter++;
var angle = (counter * 30) % 360;
$('#myCanvas').css('transform','perspective(1000px) rotate3d(5,6,7,' + angle + 'deg)');
$('input').val('counter: ' + counter + ', angle: ' + angle);
};
If you change viewport to myCanvas in line 3 of the either Kyle S or Dom's jsfiddles:
$('#myCanvas').mousedown(function _drawOnCanvas (e)
it no longer places a dot when you "click in the viewport NOT on the canvas."
It seems there's a new issue with Firefox - if there's a transformation it only lets you paint on half ( the bottom left of diagonal - but depends on transformation ).
Is there any way to improve the following html5 example, or is the browser
just to slow in handling mouse events?
Its a grid, and on the point you move the mouse to you see a red rectangle..
But this rectangle is a kind of lagging behind the mouse, so moving to slow to its position.
(if the mouse is moved pretty fast)
http://jsfiddle.net/191rmac8/
Here the code:
<body>
<canvas id="canvas" width="400" height="400">error or not supported.</canvas>
<script>
var lineSize = 10;
var rasterSize = 5;
var bx = 0;
var by = 0;
g2d = document.getElementById("canvas").getContext("2d");
g2d.setFillColor("rgb(10, 10, 10)");
g2d.fillRect(0, 0, g2d.canvas.width, g2d.canvas.height);
g2d.setStrokeColor("rgb(0, 0, 255)");
g2d.setLineWidth(lineSize);
function repaint(){
g2d.clearRect(0, 0, g2d.canvas.width, g2d.canvas.height);
g2d.beginPath();
for(i = 0; i < rasterSize + 1; i++){
g2d.moveTo(0, (lineSize / 2) + i * (g2d.canvas.height - lineSize) / (rasterSize));
g2d.lineTo(g2d.canvas.width, (lineSize / 2) + i * (g2d.canvas.height - lineSize) / (rasterSize));
g2d.moveTo((lineSize / 2) + i * (g2d.canvas.width - lineSize) / (rasterSize), 0);
g2d.lineTo((lineSize / 2) + i * (g2d.canvas.width - lineSize) / (rasterSize), g2d.canvas.height);
}
g2d.stroke();
g2d.setFillColor("red");
g2d.fillRect(bx - 5, by - 5, 11, 11);
}
repaint();
g2d.canvas.addEventListener("mousemove", function(e){
bx = e.offsetX;
by = e.offsetY;
repaint();
});
</script>
</body>
body {
margin: 0;
width: 100%;
height: 100%;
display: block;
background: black;
}
canvas {
margin: auto;
margin-top: 50px;
display: block;
}
You can separate the mouse events from the drawing to increase performance.
Create an array to hold mouse points
In mousemove, push the current mouse position into the array.
Depending on your design, you might use Aboca's idea of capping the capture rate of the points.
Create a loop using requestAnimationFrame.
In the loop, draw all points since the loop was last executed as 1 path.
The benefits are:
requestAnimationFrame is efficient at drawing.
You are drawing a polyline through a batch of points instead of 1 point at a time.
Changing context state is somewhat expensive, and this lets you change state only once.
You can cap the rate of the repaint like I did here:
http://jsfiddle.net/sh6o91g4/1/
Adjust as you see fit as it will fasten the perfomance but it will reduce the quality of the rendering too (skipping frames has it's drawbacks)
var now = new Date().getTime();
if(now - time > 10){
time = now;
bx = e.offsetX;
by = e.offsetY;
repaint();
}
I've been fooling around with JavaScript/JQuery, and decided to create a little program which would animate a ball bouncing around a rectangular boundary area. I believe that the logic of my code should make sense, but for some reason I can't get it to change directions. What is even stranger is that I put the ball's x and y positions as text on it, but it seems statically stuck (it doesn't change), but I see when I inspect the element that it's left and top css parameters are changing over time.
Here's the code:
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js">
</script>
<style>
.ball {
width: 50px;
height: 50px;
background: red;
-moz-border-radius: 50px;
-webkit-border-radius: 50px;
border-radius: 50px;
position: absolute;
}
.boundary {
width: 700px;
height: 500px;
background: #AAAAAA;
border-style:solid;
border-width:5px;
position: absolute;
}
</style>
<script>
$(document).ready(function(){
var b = new ball(1, 1, 10, 10);
for(i = 0; i < 1000; i++)
b.moveBall();
});
function ball(xPos, yPos, xVel, yVel)
{
this.xPos = xPos;
this.yPos = yPos;
this.xVel = xVel;
this.yVel = yVel;
this.rightBound = false
this.leftBound = false;
this.upBound = false;
this.downBound = false;
this.width = 50;
this.height = 50;
this.moveBall = moveBall;
function moveBall()
{
var h = 500;
var w = 700;
//detect if it is at x bounds
if(this.xPos + this.width + this.xVel > w)
{this.rightBound = true;}
else if(this.xPos + this.xVel < -1)
this.leftBound = true;
else
{
this.rightBound = false;
this.leftBound = false;
}
//detect if it is at y bounds
if(this.yPos + this.height + this.yVel > h)
{this.downBound = true;}
else if(this.yPos + this.yVel < -1)
this.upBound = true;
else
{
this.upBound = false;
this.downBound = false;
}
//handle velocity changes for bounds
//so you switch the x direction if x bound is met, same for y
if(this.rightBound || this.leftBound)
this.xVel *= -1;
if(this.upBound || this.downBound)
this.yVel *= -1;
//now give motion
this.xPos += xVel;
this.yPos += yVel;
//now draw
$(".ball").animate({
left:this.xPos + 'px',
top:this.yPos + 'px'
}, 150).text(this.xPos + "," + this.yPos);
}
}
</script>
</head>
<body>
<div class="boundary">
<div class="ball"></div>
</div>
</body>
</html>
The weird thing is that it seems to automatically put the end value of 10,001, 10,001 (assuming it never changes direction) as it's (x,y) coordinates from the very beginning. Anything that could point me in the right direction would be appreciated! And sorry if it's some basic error, I tried to ensure it wasn't but sometimes they slip through!
You're doing your ball.moveBall in a loop, not on a timer. So it's going as fast as computably possible. Yes, computably isn't a word. Moving on.
By calling $.animate you're saying you want jQuery to handle moving the object from one spot to another. jQuery goes way way way way way slower than the computer. Hang on, this is getting confusing. Let me put it simply.
You loop through ball.moveBall 1000 times. How long does that take? Virtually no time at all. That's why the coordinates stay stuck at 1000 the whole time. It actually gets there super super super fast. So fast, it gets there basically before jQuery has time to start moving the ball. And.... then the ball starts moving. Why? Why doesn't it actually move to position 1000,1000 right away? Well, the coordinates do get to 1000,1000. But jQuery is like, "Oh, okay, move the ball to position 1000,1000. I can do that! Really really slowly...". You're probably tired of hearing the explanation, so here's the fix:
Change $(".ball").animate to $(".ball").css. And change your loop to window.setInterval(function(){b.moveBall()},1000) or something like that. I've set up a fiddle for you here: http://jsfiddle.net/uXbwR/
You'll notice it moves really slowly. That's cause I set the interval to 1000 milliseconds, which means it only moves once every second. For a game you'll want something like 1000/60 (once every 60th of a second), but I tried that and it makes the ball move super fast. You've got the ball's speed really high. You might wanna try turning that down a bit.
That's all I've got.
Edit
Computationally. That's the word I was looking for.
You should call the next step of the animation only when the preceding has completed. You are telling animate to take 150ms, but the while loop completes almost instantly, without waiting each step.
[EDIT]
#Samuel answer is complete and already suggested you a good workaround. I guess this is going to be beyond the purposes of your application, but if you're interested in setting up a proper Javascript game mainloop these are some useful resources followed by an implementation of mine:
Fabien Sanglard, Game timers: Issues and solutions
Paul Irish, requestAnimationFrame for smart animating.
var RENDERING_FRAME_TIME = 1000/60; // ms
var PHYSICS_FRAME_TIME = 5; // ms
var currentTime = new Date().getTime();
var accumulator = 0;
(function mainloop(){
newTime = new Date().getTime();
accumulator = newTime - currentTime;
currentTime = newTime;
while (accumulator > PHYSICS_FRAME_TIME) {
integrate(PHYSICS_FRAME_TIME);
accumulator -= PHYSICS_FRAME_TIME;
}
requestAnimationFrame(mainloop);
render();
})();