translating an element goes back to origin place - javascript

I'm trying to make an element moves when the mouse moves in a random 340° direction excluding the 20° of the cursor so the element won't move to the cursor but when I translate It always goes back to the origin place as if there were no translating. here's the code:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>SVG</title>
</head>
<body>
<svg id="m" width="40" height="40">
<circle cx="20" cy="20" r="20" fill="red" stroke="red" stroke-width="1"/>
</svg>
<script>
let m = document.getElementById("m");
let angle = Math.floor(Math.random() * 340) * Math.PI / 180;
var timestamp, lastMouseX, lastMouseY = null;
let setCursorPosition = function(e) {
//mousespeed
if (timestamp === null) {
timestamp = Date.now();
lastMouseX = e.screenX;
lastMouseY = e.screenY;
return;
}
var now = Date.now();
var dt = now - timestamp;
var dx = e.screenX - lastMouseX;
var dy = e.screenY - lastMouseY;
var speedX = Math.round(dx / dt * 100);
var speedY = Math.round(dy / dt * 100);
timestamp = now;
lastMouseX = e.screenX;
lastMouseY = e.screenY;
//
m.style.transform = "translate(" + Math.cos(angle)*speedX + "px," + Math.sin(angle)*speedY + "px)";
};
document.addEventListener("mousemove", e => setCursorPosition(e));
</script>
</body>
</html>
thanks
also tell me if the implementation of the direction of the element is just like I wanted or no, I have a feeling that it's wrong

I'm not entirely sure what you're going for here, but I played around with you code and I think I got it to behave how you want it to. The first thing I noticed (very minor), was that this line:
var timestamp, lastMouseX, lastMouseY = null;
should be:
var timestamp = null, lastMouseX = null, lastMouseY = null;
Otherwise you are not actually setting timestamp and lastMouseX initially.
More importantly, the reason your element continues to return to its origin is that it's getting very small mouse inputs as you decelerate your mouse so speedX and speedY are set to low values at the end of each mouse move.
To fix this I added a this line right before your transform command:
if (Math.abs(speedX) < 40 && Math.abs(speedY) < 40) return;
Also to make the movement a little smoother I also added a throttle to your event listener so that setCursorPosition isn't called quite so often (reduces jittery movement), and I added a transition in the styles.
function throttle(func, interval) {
var lastCall = 0;
return function() {
let curTime = Date.now();
if (lastCall + interval < curTime) {
lastCall = curTime;
return func.apply(this, arguments);
}
};
}
document.addEventListener("mousemove", throttle(setCursorPosition, 20));
/* ^ replace original document.addEventListener() with this */
#m {
transition: transform 140ms;
margin: 200px; //just so I could always see it on the screen
}
The numbers I used for the min speed, throttle, and transition were purely experimental so feel free to play around with them to your liking.

Related

Make the following JavaScript modifications using clearInterval() and setInterval() where appropriate:

Make the following JavaScript modifications using clearInterval() and setInterval() where appropriate:
In startAnimation(), add an if statement that stops the timer with the ID timerId if timerId is not null.
After the if statement in startAnimation() that stops the timer, start a timer that calls moveImage(clickX, clickY) every 10 milliseconds. Save the timer ID in the timerId variable.
Add an if statement in moveImage() that stops the timer with the ID timerId if (imgX, imgY) is equal to (centerX, centerY). Also set timerId to null.
After the modifications are complete, the user should be able to click anywhere in the browser, and the heart will slowly move to the clicked location. If the user clicks on a new location before the heart arrives at the last location, the heart will adjust course and move to the new location.
Here is my code.
I keep get these errors in the photo even though I followed the instrutions
let timerId = null;
window.addEventListener("DOMContentLoaded", function() {
document.addEventListener("click", startAnimation);
});
function startAnimation(e) {
// Get mouse coordinates
let clickX = e.clientX;
let clickY = e.clientY;
// TODO: Modify the code below
if (timerId != null) {
clearInterval(timerId);
}
moveImage(clickX, clickY);
timerId = setInterval(moveImage, 10);
};
function moveImage(x, y) {
const img = document.querySelector("img");
// Determine location of image
let imgX = parseInt(img.style.left);
let imgY = parseInt(img.style.top);
// Determine (x,y) coordinates that center the image
// around the clicked (x, y) coords
const centerX = Math.round(x - (img.width / 2));
const centerY = Math.round(y - (img.height / 2));
// TODO: Add code here
if ((imgX, imgX == centerX) && (imgY == centerY)) {
clearInterval(timerId);
timerId = null;
}
// Move 1 pixel in both directions toward the click
if (imgX < centerX) {
imgX++;
}
else if (imgX > centerX) {
imgX--;
}
if (imgY < centerY) {
imgY++;
}
else if (imgY > centerY) {
imgY--;
}
img.style.left = imgX + "px";
img.style.top = imgY + "px";
};
I keep get these errors in the photo even though I followed the instructions
errors
The errors you're getting are basically saying that the code isn't working, which, if you run the code, you can see.
One handy way to see what's going on with your code is to use console.log and debugger
Note the difference between these two calls:
moveImage(clickX, clickY);
and
timerId = setInterval(moveImage, 10);
In each, you're making the call to moveImage, but in only one of them are you providing arguments for x and y.
We can remedy this by using a higher order function:
let timerId = null;
window.addEventListener("DOMContentLoaded", function() {
document.addEventListener("click", startAnimation);
});
function startAnimation(e) {
// Get mouse coordinates
let clickX = e.clientX;
let clickY = e.clientY;
// TODO: Modify the code below
if (timerId != null) {
clearInterval(timerId);
}
timerId = setInterval(moveImage(clickX, clickY), 10);
};
function moveImage(targetX, targetY) {
const img = document.querySelector("img");
return () => {
// Determine location of image
let imgX = parseInt(img.getBoundingClientRect().x);
let imgY = parseInt(img.getBoundingClientRect().y);
// Determine (x,y) coordinates that center the image
// around the clicked (x, y) coords
const centerX = Math.round(targetX - (img.width / 2));
const centerY = Math.round(targetY - (img.height / 2));
// TODO: Add code here
if ((imgX, imgX == centerX) && (imgY == centerY)) {
clearInterval(timerId);
timerId = null;
}
// Move 1 pixel in both directions toward the click
if (imgX < centerX) {
imgX++;
} else if (imgX > centerX) {
imgX--;
}
if (imgY < centerY) {
imgY++;
} else if (imgY > centerY) {
imgY--;
}
img.style.left = imgX + "px";
img.style.top = imgY + "px";
};
};
img {
position: fixed;
}
<img src="https://i.kym-cdn.com/photos/images/facebook/001/136/185/604.jpg" height="20" weidth="20" />
You can use getBoundingClientRect to find the position of the image, and once its position is set to fixed, changing the left and top properties will cause it to move.

Get mouse moving speed

I didn't get a exact solution/calculation from stackoverflow so i created a question
var timestamp = null;
var mY = 0;
$(document).mousemove(function(e) {
var now = Date.now();
currentmY = e.pageY;
mY = e.pageY;
timestamp = now;
});
I need to get a speed value when mouse move vertical angle.
https://jsfiddle.net/58tjr9o1/
The speed is simply the distance divided by the time it took:
speed = distance / time
The distance is currentmY - mY, while the time is now - timestamp. So in the end, you get:
var timestamp = 0;
var mY = 0;
$(document).mousemove(function(e) {
var now = Date.now();
currentmY = e.screenY;
var dt = now - timestamp;
var distance = Math.abs(currentmY - mY);
var speed = Math.round(distance / dt * 1000);
console.log(dt, distance, speed);
document.getElementById("speed").innerHTML = speed;
mY = currentmY;
timestamp = now;
});
Note the * 1000, since the timestamp is in milliseconds. The speed is here in pixels/second.
See this updated fiddle.
Following code will continuously update mouse's vertical movement speed in the span with id = "update-speed". Code is self explanatory and easy to understand, it just saved current position, previous position, current time and previous time and then calculates the speed using this formula (speed = (pos2 - pos1) / (time2 - time1).
HTML
<span id="update-speed">Update speed</span>
JS
var prev_time = new Date();
var prev_pos_y = 0;
$(document).mousemove(function(e) {
var now = new Date();
current_pos_y = e.pageY;
time_interval = now.getTime() - prev_time.getTime();
if(time_interval != 0)
{
speed = ( Math.abs(current_pos_y - prev_pos_y) / time_interval );
}
else
speed = 0;
console.log(speed);
$('#update-speed').text(speed);
prev_time = now;
prev_pos_y = current_pos_y;
});

Rotate svg line with JavaScript (not CSS)

I've got the following svg line:
<svg height="100%" width="100%">
<line id="skyline" x1="50%" y1="50%" x2="50%" y2="90%" style="stroke:rgb(0,0,0);stroke-width:10" />
</svg>
Trying to rotate the line multiple times seems to add many rotate(X) to the transform attribute, not simply overriding the value every time:
var skyline = document.getElementById("skyline");
for (i = 0; i < 100; i++) {
var rotation = skyline.getAttribute("transform") + i;
skyline.setAttribute("transform", "rotate(" + rotation + ")");
}
How can I properly get the rotate attribute, and how can I properly override in multiple times?
Changing the same attribute in synchronous loop doesn't make sense. If you want some sort of an animation you need to introduce time delay. The most straight forward way is to use setTimeout
var skyline = document.getElementById("skyline");
var angle = getOriginalAngle(),
finalAngle = angle + 100;
function rotate() {
skyline.setAttribute("transform", "rotate(" + (angle++) + "deg)");
(angle < finalAngle) && setTimeout(rotate, 12); // repeat every 12 ms
}
rotate(); // launch animation
UPD Or if you want your changes to be in sync with browser rendering loop you can use requestAnimationFrame
function rotate() {
skyline.setAttribute("transform", "rotate(" + (angle++) + "deg)");
(angle < finalAngle) && requestAnimationFrame(rotate); // +- 60 fps
}
Greensock is a great extension for CSS and JS which allows you to manipulate SVG's based on id's or classnames of the element in question:
GreenSock
Here's an example:
TweenMax.staggerTo('.skyline', 2, {rotation: -90, repeat:-1, yoyo:true});
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.5/TweenMax.min.js"></script>
<svg height="100%" width="100%">
<line class="skyline" x1="50%" y1="50%" x2="50%" y2="90%" style="stroke:rgb(0,0,0);stroke-width:10" />
</svg>
The best part is it will run in IE as well as being simple, easy to implement and easy to understand and well documented! It can be set to run automatically on page load, or when an event is fired such as a hover etc etc. you can choose whether it runs indefinitely or how many times you want it to run, and if you want it to have a yoyo effect or not (animation to run back and forth)
Assuming your line doesn't already have a transform, you can just do:
var skyline = document.getElementById("skyline");
for (i = 0; i < 100; i++) {
skyline.setAttribute("transform", "rotate(" + i + ")");
}
But that doesn't work as is because you aren't giving the browser a chance to render the updated SVG. Also it would run too fast even if it did update.
So you need to use an interval, timeout or requestAnimationFrame().
var i = 0;
function rotate() {
skyline.setAttribute("transform", "rotate(" + (i++) + ")");
if (i < 100)
setTimeout(rotate, 100);
}
setTimeout(rotate, 100);
If your elements could already have a transform, then you will need to get the current transform and manipulate it. For example:
var i = 0;
function rotate() {
var matrixList = skyline.transform.baseVal;
if (matrixList.length === 0) {
skyline.setAttribute("transform", "rotate(" + (i++) + ")");
} else {
var firstTransform = matrixList.getItem(0);
firstTransform.setMatrix(firstTransform.matrix.rotate(1));
i++;
}
if (i < 100)
setTimeout(rotate, 100);
}
setTimeout(rotate, 100);
var skyline = document.getElementById("skyline");
var i = 0;
function rotate() {
var matrixList = skyline.transform.baseVal;
if (matrixList.length === 0) {
skyline.setAttribute("transform", "rotate(" + (i++) + ")");
} else {
var firstTransform = matrixList.getItem(0);
firstTransform.setMatrix(firstTransform.matrix.rotate(1));
i++;
}
if (i < 100)
setTimeout(rotate, 100);
}
setTimeout(rotate, 100);
<svg height="100%" width="100%">
<line id="skyline" x1="50%" y1="50%" x2="50%" y2="90%" style="stroke:rgb(0,0,0);stroke-width:10" transform="rotate(-10)"/>
</svg>
Using: https://css-tricks.com/get-value-of-css-rotation-through-javascript/
function getRotate(el) {
var st = window.getComputedStyle(el, null);
var tr = st.getPropertyValue("-webkit-transform") ||
st.getPropertyValue("-moz-transform") ||
st.getPropertyValue("-ms-transform") ||
st.getPropertyValue("-o-transform") ||
st.getPropertyValue("transform") ||
"fail...";
var values = tr.split('(')[1];
values = values.split(')')[0];
values = values.split(',');
var a = values[0];
var b = values[1];
var c = values[2];
var d = values[3];
var scale = Math.sqrt(a * a + b * b);
// arc sin, convert from radians to degrees, round
var sin = b / scale;
// next line works for 30deg but not 130deg (returns 50);
// var angle = Math.round(Math.asin(sin) * (180/Math.PI));
var angle = Math.round(Math.atan2(b, a) * (180 / Math.PI));
return angle
}
setInterval(rotate, 100);
function rotate() {
var rotation = getRotate(document.getElementById("skyline")) + 5;
console.log(rotation);
skyline.setAttribute("style", "stroke:rgb(255,0,0);stroke-width:2;transform: rotate(" + rotation + "deg);transform-origin: center");
}
<svg height="100%" width="100%">
<line id="skyline" x1="80" y1="20" x2="100" y2="40" style="stroke:rgb(255,0,0);stroke-width:2;transform: rotate(30deg);transform-origin: center" />
</svg>

Raphael transform object diagonally and infinite setIntervals

I'm working on a small animation where the user drags a circle and the circle returns back to the starting point. I figured out a way to have the circle return to the starting point. The only problem is that it will hit one of the sides of the frame before returning. Is it possible for it to go straight back (follow the path of a line drawn between the shape and starting point).
The other problem is that my setInterval doesn't want to stop. If you try pulling it a second time it would pull it back before you release your mouse. It also seems to speed up after every time. I have tried using a while loop with a timer but the results weren't as good. Is this fixable?
var paper = Raphael(0, 0, 320, 200);
//var path = paper.path("M10 10L40 40").attr({stoke:'#000000'});
//var pathArray = path.attr("path");
var circle = paper.circle(50, 50, 20);
var newX;
var newY;
circle.attr("fill", "#f00");
circle.attr("stroke", "#fff");
var start = function () {
this.attr({cx: 50, cy: 50});
this.cx = this.attr("cx"),
this.cy = this.attr("cy");
},
move = function (dx, dy) {
var X = this.cx + dx,
Y = this.cy + dy;
this.attr({cx: X, cy: Y});
},
up = function () {
setInterval(function () {
if(circle.attr('cx') > 50){
circle.attr({cx : (circle.attr('cx') - 1)});
} else if (circle.attr('cx') < 50){
circle.attr({cx : (circle.attr('cx') + 1)});
}
if(circle.attr('cy') > 50){
circle.attr({cy : (circle.attr('cy') - 1)});
} else if (circle.attr('cy') < 50){
circle.attr({cy : (circle.attr('cy') + 1)});
}
path.attr({path: pathArray});
},2);
};
circle.drag(move, start, up);
Here's the Jfiddle: http://jsfiddle.net/Uznp2/
Thanks alot :D
I modified the "up" function to the one below
up = function () {
//starting x, y of circle to go back to
var interval = 1000;
var startingPointX = 50;
var startingPointY = 50;
var centerX = this.getBBox().x + (this.attr("r")/2);
var centerY = this.getBBox().y + (this.attr("r")/2);
var transX = (centerX - startingPointX) * -1;
var transY = (centerY - startingPointY) * -1;
this.animate({transform: "...T"+transX+", "+transY}, interval);
};
and the "start" function as follows:
var start = function () {
this.cx = this.attr("cx"),
this.cy = this.attr("cy");
}
Is this the behavior you are looking for? Sorry if I misunderstood the question.
If the circle need to get back to its initial position post drag, we can achieve that via simple animation using transform attribute.
// Assuming that (50,50) is the location the circle prior to drag-move (as seen in the code provided)
// The animation is set to execute in 1000 milliseconds, using the easing function of 'easeIn'.
up = function () {
circle.animate({transform: 'T50,50'}, 1000, 'easeIn');
};
Hope this helps.

Track mouse speed with JS

What's the best way to track the mouse speed with plain JS/JQuery? I'd like to track how fast a user moves the mouse in all directions (up/down/left/right).
Sparklines has a nifty example of tracking mouse movement and graphing it. Their code is available in the source of their site starting at line 315.
Simple and effective.
Here is the code:
var mrefreshinterval = 500; // update display every 500ms
var lastmousex=-1;
var lastmousey=-1;
var lastmousetime;
var mousetravel = 0;
$('html').mousemove(function(e) {
var mousex = e.pageX;
var mousey = e.pageY;
if (lastmousex > -1)
mousetravel += Math.max( Math.abs(mousex-lastmousex), Math.abs(mousey-lastmousey) );
lastmousex = mousex;
lastmousey = mousey;
});
var timestamp = null;
var lastMouseX = null;
var lastMouseY = null;
document.body.addEventListener("mousemove", function(e) {
if (timestamp === null) {
timestamp = Date.now();
lastMouseX = e.screenX;
lastMouseY = e.screenY;
return;
}
var now = Date.now();
var dt = now - timestamp;
var dx = e.screenX - lastMouseX;
var dy = e.screenY - lastMouseY;
var speedX = Math.round(dx / dt * 100);
var speedY = Math.round(dy / dt * 100);
timestamp = now;
lastMouseX = e.screenX;
lastMouseY = e.screenY;
});
With current modern browser we can now use movementX or movementY to detect mouse's movement speed. Before you want to use it you should see the compatibility table because older browser will have a prefix like webkitMovementX.
document.addEventListener("mousemove", function(ev){
console.log(`Movement X: ${ev.movementX}, Y: ${ev.movementY}`);
}, false);
The result above is not an average speed like pixel/second but it's total movement between triggered mousemove event. If you need px/s then you can do it like below:
var totalX = 0;
var totalY = 0;
var moveX = 0;
var moveY = 0;
document.addEventListener("mousemove", function(ev){
totalX += Math.abs(ev.movementX);
totalY += Math.abs(ev.movementY);
moveX += ev.movementX;
moveY += ev.movementY;
}, false);
setInterval(function(){
console.log(`Speed X: ${totalX}px/s, Y: ${totalY}px/s`);
console.log(`Movement X: ${moveX}px/s, Y: ${moveY}px/s`);
moveX = moveY = totalX = totalY = 0;
}, 1000);
Negative number represent movement to the left or top, while positive represent movement to the bottom or right direction.
Same way you get speed for anything else:
speed = distance / time
acceleration = speed / time
And use:
$(document).mousemove(function(e){
var xcoord = e.pageX;
var ycoord = e.pageY;
});
To get the mouse coordinates whenever the mouse moves.
This is a method to counter the fact you could start tracking, pause and then move your finger or mouse very quickly (suppose a sudden flick on a touch screen).
var time = 200
var tracker = setInterval(function(){
historicTouchX = touchX;
}, time);
document.addEventListener("touchmove", function(){
speed = (historicTouchX - touchX) / time;
console.log(Math.abs(speed));
}, false);
I have done this with only the touchX in this example. The idea is to take a snapshot of the x position every 200 milliseconds, and then take that from the current position then divide by the 200 (speed = distance / time). This would keep a fresh update on the speed. The time is milliseconds and the output would be the number of pixels traveled per 200 milliseconds.
I also had a requirement to find the acceleration, speed, movement of the mouse. Below is the code which is implemented for the react application. Through this we were able to find the movement, speed, max speed, acceleration, maximum acceleration of the mouse.
let previousEvent, currentEvent;
let maxSpeed = 0, previousSpeed = 0, speed = 0, maxPositiveAcc = 0, maxNegativeAcc = 0;
componentDidMount() {
document.addEventListener('mousemove', (event) => {
currentEvent = event
});
setInterval(function () {
if (currentEvent && previousEvent) {
let movementX = Math.abs(currentEvent.pageX - previousEvent.pageX);
let movementY = Math.abs(currentEvent.pageY - previousEvent.pageY);
let movement = Math.sqrt(movementX * movementX + movementY * movementY);
//Dividing by 100 since the setInterval function is called every 100ms
speed = 10 * movement;
maxSpeed = Math.round(speed > maxSpeed ? (maxSpeed = speed) : maxSpeed);
let acceleration = 10 * (speed - previousSpeed);
if (acceleration > 0) {
maxPositiveAcceleration = Math.round(acceleration > maxPositiveAcc ? (maxPositiveAcc = acceleration) : maxPositiveAcc);
} else {
maxNegativeAcceleration = Math.round(acceleration < maxNegativeAcc ? (maxNegativeAcc = acceleration) : maxNegativeAcc);
}
}
previousEvent = currentEvent
previousSpeed = speed;
}, 100);
}
I'm looking for a way to track mouse speed as well. I found this video on Youtube https://www.youtube.com/watch?v=Lrfmu9V_foE. You can see how to track mouse speed with mousemove event once has defined previous mouse event and current mouse event.
Anyways, I want to store the speed as a value to use elsewhere but don't know how to.

Categories

Resources