Making element rotate to mouse position, after another rotation - javascript

I'm trying to make a tank-simulator of sorts. It's strictly on divs, not on canvas etc. It's already rotating and moving forward/backward how I want it to, but rotating the turret is a bit problematic. I got to the point where the turret rotates to the mouse pointer, however when the tank body rotates, the turret rotation doesn't take it into account (it rotates with the tanks body). Does anyone have any idea how to make it work so that the turret ALWAYS points to the mouse cursor?
Preferably in pure JS.
It's a bit too big to paste, so I'll just link to fiddle:
https://jsfiddle.net/9pom714a/
Sample of mousemove handler:
window.addEventListener('mousemove', (e) => {
mouseX = e.clientX - field.offsetLeft;
mouseY = e.clientY - field.offsetTop;
turretAngle = Math.atan2(mouseX - turretBaseX, -(mouseY - turretBaseY)) * (180/Math.PI);
turretBase.style.transform = 'rotate(' + turretAngle + 'deg)';
})
I know the code is a mess, I'll refactor it after I get everything to work ;)

substract the tankAngle to the turretAngle rotation like this:
window.addEventListener('mousemove', (e) => {
mouseX = e.clientX - field.offsetLeft;
mouseY = e.clientY - field.offsetTop;
turretAngle = Math.atan2(mouseX - turretBaseX, -(mouseY - turretBaseY)) * (180/Math.PI);
turretAngle -= tankAngle;
turretBase.style.transform = 'rotate(' + turretAngle + 'deg)';
})
Updated fiddle: https://jsfiddle.net/9pom714a/1/

Related

Particles follow cursor on mousemove (Javascript - Canvas)

I'm trying to create a simple animation where some particles animation follow the cursor, but i'm having trouble with it.
I've created a fiddle to replicate the issue : Example on JSFiddle
Right now my particles appear, but when you move the cursor over the section, they suddenly disappear. I know my error comes from my mousemove() function, but i can't figure out what is wrong with it..
here is my mousemove function :
function mouseMove(e) {
var posx = posy = 0;
if (e.pageX || e.pageY) {
posx = e.pageX;
posy = e.pageY;
}
else if (e.clientX || e.clientY) {
posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
target.x = posx;
target.y = posy;
}
Your mouse coordinate X, Y is relative to the top/left corner of the web page, probably mousemove event is attached to document, not to the canvas. Attach the mosemove event to the canvas
document.getElementById('services-canvas').addEventListener('mousemove', mouseMove);
And use the elemnt ofset:
target.x = e.offsetX;
target.y = e.offsetY;
If you would like the mouse to be in the centre of figure, then use e.offsetY-something where something is half of height of figure
So your particles do actually follow the mouse from what I have seen in . However, it seems that they are way lower in the y position that you would expect.You need to do this to make it work properly:
target.y = posy -300;
I have tried it and it worked with this little change. Hope this helped :D

Resize multiple objects with JS considering rotate

I'm working on visual editor with objects and user interactions around like move, resize, rotate, etc...
I have resize and rotate functionality in place. Now I have implemented multi-select functionality when user select multiple objects and resize objects keeping the original proportion.
That functionality works very well, however not for rotated objects. I've created a simplified codepen example. Basically the question is - how to adjust resize() function to make sure it works well for rotated objects. To reproduce an issue just click on "Rotate" and then "Increase width & height" once or multiple times.
function resize(incrementX, incrementY, offsetX, offsetY) {
...
}
I'm not sure if this is a valid solution for your problem, but you can undo the rotation before resizing, and reset the rotation afterwards. Like this.
function resize(incrementX, incrementY, offsetX, offsetY) {
var old_r = objmultiple.r
rotate(-objmultiple.r)
var ratioX = (objmultiple.w + incrementX) / objmultiple.w;
var ratioY = (objmultiple.h + incrementY) / objmultiple.h;
objmultiple.x += offsetX;
objmultiple.y += offsetY;
objmultiple.w = objmultiple.w + incrementX;
objmultiple.h = objmultiple.h + incrementY;
[obj1, obj2].forEach(function(obj) {
obj.x = (obj.x - objmultiple.x + offsetX) * ratioX + objmultiple.x;
obj.y = (obj.y - objmultiple.y + offsetY) * ratioY + objmultiple.y;
obj.w *= ratioX;
obj.h *= ratioY;
});
rotate(old_r)
}
Codepen here

Rotating an image to face mouse point using ATAN instead of ATAN2

In order to better understand how trigonometry works in game development, I've been creating little javascript snippets on CodePen.
I managed to create an example that uses Math.atan2() to point a pixel-art shotgun at the mouse cursor.
Now, I am trying to accomplish the same exact thing using the Math.atan() function but it isn't functioning properly.
Here is the logic code I am using:
canvas.onmousemove = function(event) {
Mouse = {
x: event.pageX,
y: event.pageY
}
// These length variables use the distance formula
var opposite_length = Math.sqrt((Mouse.x - Mouse.x) * (Mouse.x - Mouse.x) + (Mouse.y - y) * (Mouse.y - y));
var adj_length = Math.sqrt((Mouse.x - x) * (Mouse.x - x) + (y - y) * (y - y));
var angle_in_radians = Math.atan(opposite_length / adj_length);
//var angle_in_radians = Math.atan2(Mouse.y - y, Mouse.x - x);
angle = angle_in_radians * (180 / Math.PI);
}
The in my draw() function, I rotate the gun to the angle var using:
cxt.rotate(angle*(Math.PI/180));
If you uncomment the line that starts as // var angle_in_radians, everything will suddenly work.
So, atan2 is working, but atan is producing the result I want.
I know that opposite_length and adj_length are accurate, because when i console.log() them, they are the correct values.
You can check out the code being used on CodePen for a live example.
There's a lot of initialization stuff but you only really need to focus on the canvas.onmousemove = function(event) section, starting on line 50. You can also check out my draw function on line 68.
Note that your atan computation is equivalent to
atan2( abs(mouse.y-y), abs(mouse.x-x) )
The screen coordinates have the opposite orientation to the cartesian coordinates. To get a cartesian angle from screen coordinates, use
atan2( y-mouse.y, mouse.x-x )

drag div on exact mouse point

I'm trying to drag an element with just vanilla JavaScript.
When I drag the element I want it to move in sync with where the mouse pointer clicked, I can move it via the elements top left corner which is simple enough but I'm having issues moving it from the exact point of click.
Javascript
function mouseMove(e){
if(dragging){
boxPos(sq,e);
}
}
function boxPos(el,e){
box = el.getBoundingClientRect();
mouse_top = e.clientY;
mouse_left = e.clientX;
diff_x = mouse_left - box.left;
diff_y = mouse_top - box.top;
el.style.top = (mouse_top + diff_y) +"px";
el.style.left = (mouse_left + diff_x) +"px";
}
sq is a div, e is the event.
What im trying to do is calculate the position of the mouse, work out the difference from the top/left and add it to the top left but I'm getting undesired results. See fiddle
I know it has been passed some years but, testing the Fiddle, I figured out that (at least to me) the answer isn't exactly what the OP was looking for. That's because in the response's Fiddle the div is moved perfectly but it's moved always on its origin (0,0) and not where it was exactly clicked.
The solution is actually quite simple.
You are calculating the diff_x and diff_y on every mousemove event. But the mouse pointer position while moving is always fixed in relation to the div at the very start of the mousedown event until firing the mouseup event, right? So, just declare the diffs' variables globally and calculate them on the mousedown event.
Then, change the following code:
function boxPos(el,e){
box = el.getBoundingClientRect();
mouse_top = e.clientY;
mouse_left = e.clientX;
diff_x = mouse_left - box.left;
diff_y = mouse_top - box.top;
el.style.top = (mouse_top + diff_y) +"px";
el.style.left = (mouse_left + diff_x) +"px";
}
To:
function boxPos(el,e){
box = el.getBoundingClientRect();
mouse_top = e.clientY;
mouse_left = e.clientX;
el.style.top = (mouse_top - diff_y) +"px";
el.style.left = (mouse_left - diff_x) +"px";
}
The diffs' math is done on the mousedown event, so we don't need it anymore. Thus, this will stop the flickering.
Besides that, as the diffs will always be positive, we just subtract the diffs from the corresponding mouse coordinates to place the div taking into account the difference calculated (because the mouse pointer must be on top of the div to trigger the mousedown event, then e.clientY >= sq.style.top and e.clientX >= sq.style.left will be always true). This last step is the one that moves the element on exact mouse point.
This logic may be used on other "dragging events", like the dragstart and drag events, touchstart and touchmove events and pointerdown and pointermove events as well (with some adaptation, of course). So, since this kind of behaviour may be relevant, I decided to post this answer. I hope it helps future readers.
Here's the OP's modified Fiddle.
A quick hotfix would be:
change
el.style.top = (mouse_top + diff_y) +"px";
el.style.left = (mouse_left + diff_x) +"px";
to
el.style.top = (Number(el.style.top.split("px")[0]) + diff_y) +"px";
el.style.left = (Number(el.style.left.split("px")[0]) + diff_x) +"px";
or
el.style.top = (Number(el.style.top.replace("px", "")) + diff_y) +"px";
el.style.left = (Number(el.style.left.replace("px", "")) + diff_x) +"px";
You need to move that div from its old position to the new one using the old coordinates and the calculated difference. To get the old position I used the current top and left setting, removed the "px" from it and converted it to a number. This is necessary to avoid string concatenation (top and left are string values).
Your program has another bug ... you currently can't stop the dragging mode.
Edit
To remove the drag "stop" bug you could subtract 1 pixel from the new value, so the mouse pointer would still be inside the div and the mouse up event will be triggered correctly.
You can do that like this:
el.style.top = ((Number(el.style.top.replace("px", "")) - 1) + diff_y) +"px";
el.style.left = ((Number(el.style.left.replace("px", "")) - 1) + diff_x) +"px";
Here is the new Fiddle.

Zoom image to cursor breaks when mouse is moved

This is a followup question to How to zoom to mouse pointer while using my own mousewheel smoothscroll?
I am using css transforms to zoom an image to the mouse pointer. I am also using my own smooth scroll algorithm to interpolate and provide momentum to the mousewheel.
With Bali Balo's help in my previous question I have managed to get 90% of the way there.
You can now zoom the image all the way in to the mouse pointer while still having smooth scrolling as the following JSFiddle illustrates:
http://jsfiddle.net/qGGwx/7/
However, the functionality is broken when the mouse pointer is moved.
To further clarify, If I zoom in one notch on the mousewheel the image is zoomed around the correct position. This behavior continues for every notch I zoom in on the mousewheel, completely as intended. If however, after zooming part way in, I move the mouse to a different position, the functionality breaks and I have to zoom out completely in order to change the zoom position.
The intended behavior is for any changes in mouse position during the zooming process to be correctly reflected in the zoomed image.
The two main functions that control the current behavior are as follows:
self.container.on('mousewheel', function (e, delta) {
var offset = self.image.offset();
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
if (!self.running) {
self.running = true;
self.animateLoop();
}
self.delta = delta
self.smoothWheel(delta);
return false;
});
This function collects the current position of the mouse at the current scale of the zoomed image.
It then starts my smooth scroll algorithm which results in the next function being called for every interpolation:
zoom: function (scale) {
var self = this;
self.currentLocation.x += ((self.mouseLocation.x - self.currentLocation.x) / self.currentscale);
self.currentLocation.y += ((self.mouseLocation.y - self.currentLocation.y) / self.currentscale);
var compat = ['-moz-', '-webkit-', '-o-', '-ms-', ''];
var newCss = {};
for (var i = compat.length - 1; i; i--) {
newCss[compat[i] + 'transform'] = 'scale(' + scale + ')';
newCss[compat[i] + 'transform-origin'] = self.currentLocation.x + 'px ' + self.currentLocation.y + 'px';
}
self.image.css(newCss);
self.currentscale = scale;
},
This function takes the scale amount (1-10) and applies the css transforms, repositioning the image using transform-origin.
Although this works perfectly for a stationary mouse position chosen when the image is completely zoomed out; as stated above it breaks when the mouse cursor is moved after a partial zoom.
Huge thanks in advance to anyone who can help.
Actually, not too complicated. You just need to separate the mouse location updating logic from the zoom updating logic. Check out my fiddle:
http://jsfiddle.net/qGGwx/41/
All I have done here is add a 'mousemove' listener on the container, and put the self.mouseLocation updating logic in there. Since it is no longer required, I also took out the mouseLocation updating logic from the 'mousewheel' handler. The animation code stays the same, as does the decision of when to start/stop the animation loop.
here's the code:
self.container.on('mousewheel', function (e, delta) {
if (!self.running) {
self.running = true;
self.animateLoop();
}
self.delta = delta
self.smoothWheel(delta);
return false;
});
self.container.on('mousemove', function (e) {
var offset = self.image.offset();
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
});
Before you check this fiddle out; I should mention:
First of all, within your .zoom() method; you shouldn't divide by currentscale:
self.currentLocation.x += ((self.mouseLocation.x - self.currentLocation.x) / self.currentscale);
self.currentLocation.y += ((self.mouseLocation.y - self.currentLocation.y) / self.currentscale);
because; you already use that factor when calculating the mouseLocation inside the initmousewheel() method like this:
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
So instead; (in the .zoom() method), you should:
self.currentLocation.x += (self.mouseLocation.x - self.currentLocation.x);
self.currentLocation.y += (self.mouseLocation.y - self.currentLocation.y);
But (for example) a += b - a will always produce b so the code above equals to:
self.currentLocation.x = self.mouseLocation.x;
self.currentLocation.y = self.mouseLocation.y;
in short:
self.currentLocation = self.mouseLocation;
Then, it seems you don't even need self.currentLocation. (2 variables for the same value). So why not use mouseLocation variable in the line where you set the transform-origin instead and get rid of currentLocation variable?
newCss[compat[i] + 'transform-origin'] = self.mouseLocation.x + 'px ' + self.mouseLocation.y + 'px';
Secondly, you should include a mousemove event listener within the initmousewheel() method (just like other devs here suggest) but it should update the transform continuously, not just when the user wheels. Otherwise the tip of the pointer will never catch up while you're zooming out on "any" random point.
self.container.on('mousemove', function (e) {
var offset = self.image.offset();
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
self.zoom(self.currentscale);
});
So; you wouldn't need to calculate this anymore within the mousewheel event handler so, your initmousewheel() method would look like this:
initmousewheel: function () {
var self = this;
self.container.on('mousewheel', function (e, delta) {
if (!self.running) {
self.running = true;
self.animateLoop();
}
self.delta = delta;
self.smoothWheel(delta);
return false;
});
self.container.on('mousemove', function (e) {
var offset = self.image.offset();
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
self.zoom(self.currentscale); // <--- update transform origin dynamically
});
}
One Issue:
This solution works as expected but with a small issue. When the user moves the mouse in regular or fast speed; the mousemove event seems to miss the final position (tested in Chrome). So the zooming will be a little off the pointer location. Otherwise, when you move the mouse slowly, it gets the exact point. It should be easy to workaround this though.
Other Notes and Suggestions:
You have a duplicate property (prevscale).
I suggest you always use JSLint or JSHint (which is available on
jsFiddle too) to validate your code.
I highly suggest you to use closures (often refered to as Immediately Invoked Function Expression (IIFE)) to avoid the global scope when possible; and hide your internal/private properties and methods.
Add a mousemover method and call it in the init method:
mousemover: function() {
var self = this;
self.container.on('mousemove', function (e) {
var offset = self.image.offset();
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
self.zoom(self.currentscale);
});
},
Fiddle: http://jsfiddle.net/powtac/qGGwx/34/
Zoom point is not exactly right because of scaling of an image (0.9 in ratio). In fact mouse are pointing in particular point in container but we scale image. See this fiddle http://jsfiddle.net/qGGwx/99/ I add marker with position equal to transform-origin. As you can see if image size is equal to container size there is no issue. You need this scaling? Maybe you can add second container? In fiddle I also added condition in mousemove
if(self.running && self.currentscale>1 && self.currentscale != self.lastscale) return;
That is preventing from moving image during zooming but also create an issue. You can't change zooming point if zoom is still running.
Extending #jordancpaul's answer I have added a constant mouse_coord_weight which gets multiplied to delta of the mouse coordinates. This is aimed at making the zoom transition less responsive to the change in mouse coordinates. Check it out http://jsfiddle.net/7dWrw/
I have rewritten the onmousemove event hander as:
self.container.on('mousemove', function (e) {
var offset = self.image.offset();
console.log(offset);
var x = (e.pageX - offset.left) / self.currentscale,
y = (e.pageY - offset.top) / self.currentscale;
if(self.running) {
self.mouseLocation.x += (x - self.mouseLocation.x) * self.mouse_coord_weight;
self.mouseLocation.y += (y - self.mouseLocation.y) * self.mouse_coord_weight;
} else {
self.mouseLocation.x = x;
self.mouseLocation.y = y;
}
});

Categories