Calculating point on Circle - javascript

I am aware of plenty of articles on this topic on the Internet, but I have not managed to solve my problem. I think me trigonometry calculations are correct, but perhaps I am messing something up within JavaScript.
My goals is to calculate the point on circle (x,y) so that I can move my sliderHead to that position. For this I need to calculate the angle between the pressed / clicked points on my circle - I use atan2 for that. Next I use simple trigonometry to calculate the actually X and Y:
let y = -Math.ceil(radius * Math.cos(deg));
let x = Math.ceil(radius * Math.sin(deg));
I understand that I need to invert the cosine and sine, because my starting position is actually at 90 degrees and not 0 and I turn CLOCKWISE and not COUNTERCLOCKWISE, which maths suggests.
The starting values are 0, becuse the initial value of the sliderHead is at center of the Circle. The starting value is not actual 0, 0px, but center of the parent div.
As you will see on below picture, I am not doing it correct, the sliderHead is behind the bar.
Here is the code where I calculate the point x,y:
testCalc(event) {
let deg = Math.atan2(event.pageY - this.centerY, event.pageX - this.centerX);
console.log(deg);
let radius = this.options.radius;
let y = -Math.ceil(radius * Math.cos(deg));
let x = Math.ceil(radius * Math.sin(deg));
return {
"x": x + "px",
"y": y + "px"
};
}
And here is the positioning of the sliderHead element within my HTML:
Here is how I translate the sliderHead:
moveSliderHeadPosition(point) {
this.sliderHead.style.transform = "translate(" + point["x"] + "," + point["y"] + ")";
}
As said, I exhausted all of my options with trying to solve this.

Nice on the use of atan2! Looking at your code it looks right to me, though maybe it's as simple as needing to subtract half the size of the sliderHead?
Here's an example that I put together to test it:
function showCoords(event) {
var x = event.clientX;
var y = event.clientY;
var xn = x / window.innerWidth;
var yn = y / window.innerHeight;
var result = Math.atan2(x, y);
var coor = `X: ${x}, Y: ${y} > X1: ${xn.toFixed(2)}, Y1: ${yn.toFixed(
2
)} ~ ${result.toFixed(
2)}`;
document.getElementById("demo").innerHTML = coor;
}
function moveGrabber(event) {
// get our grabber and track as well as their bounding rectangles
var grabber = document.querySelector(".grabber");
var grabberRect = grabber.getBoundingClientRect();
var track = document.querySelector(".track");
var trackRect = track.getBoundingClientRect();
var trackRadius = trackRect.width / 2;
// get our window center
var windowCenterX = window.innerWidth / 2;
var windowCenterY = window.innerHeight / 2;
// get the angle around the window center
var x = event.clientX;
var y = event.clientY;
var angle = Math.atan2(x - windowCenterX, y - windowCenterY);
// get the point on the circle
var circleX = Math.sin(angle) * trackRadius + windowCenterX;
var circleY = Math.cos(angle) * trackRadius + windowCenterY;
// subtract half the size of the grabber so that it is centered on the track
var posX = circleX - grabberRect.width / 2;
var posY = circleY - grabberRect.height / 2;
grabber.style.left = `${posX}px`;
grabber.style.top = `${posY}px`;
}
function moveGrabberWithX(event) {
// get our grabber and track as well as their bounding rectangles
var grabber = document.querySelector(".grabber");
var grabberRect = grabber.getBoundingClientRect();
var track = document.querySelector(".track");
var trackRect = track.getBoundingClientRect();
var trackRadius = trackRect.width / 2;
// get the mouse position and the normalized version in the range of [0,1]
var x = event.clientX;
var xn = x / window.innerWidth;
// get the center of the window
var windowCenterX = window.innerWidth / 2;
var windowCenterY = window.innerHeight / 2;
// get the point on the circle
// this uses the normalized x position of the mouse [0,1]
// and multiplies it by 2PI which gives you a circle
var circleX = Math.sin(xn * Math.PI * 2) * trackRadius + windowCenterX;
var circleY = Math.cos(xn * Math.PI * 2) * trackRadius + windowCenterY;
// subtract half the size of the grabber so that it is centered on the track
var posX = circleX - grabberRect.width / 2;
var posY = circleY - grabberRect.height / 2;
grabber.style.left = `${posX}px`;
grabber.style.top = `${posY}px`;
}
html,
body {
margin: 0px;
padding: 0px;
}
.center {
position: absolute;
top: 0px;
left: 0px;
width: 100vw;
height: 100vh;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
background: gray;
}
.track {
width: 300px;
height: 300px;
background: rgb(13, 88, 134);
border-radius: 50%;
}
.grabber {
position: absolute;
top: 0px;
left: 0px;
width: 25px;
height: 25px;
background: coral;
border-radius: 50%;
}
.label {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
/* display: flex; */
}
p {
background: darkgray;
font-family: "Courier New", Courier, monospace;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Static Template</title>
<link rel="stylesheet" href="styles.css" />
<script src="script.js"></script>
</head>
<body>
<div class="center" onmousemove="moveGrabber(event); showCoords(event);">
<div class="track" />
<div class="grabber" />
</div>
<div class="label">
<p id="demo" />
</div>
</body>
</html>

Try multiplying or dividing the calculated angle by 57.2957795 (a conversion between degrees and radians).

Related

How can I generate a random position for an element so that it does not overlap with the position of a different element in javascript?

I am trying to generate random player locations that do not overlap. My initial assumption is that maybe I should (1) generate random positions, (2) check if random positions are overlapping, (3) regenerate positions that are overlapping.
Edit: Simplified the code to more directly point out the problem.
function randomPosition() {
let random = parseInt( (50 + Math.random()*100));
return random
}
let div01 = document.createElement('div');
div01.setAttribute('class', 'div01');
let div02 = document.createElement('div');
div02.setAttribute('class', 'div02');
document.body.append(div01, div02);
div01.style.top = randomPosition() + 'px';
div01.style.left = randomPosition()+ 'px';
div02.style.top = randomPosition() + 'px';
div02.style.left = randomPosition() + 'px';
.div01 {
position: absolute;
background-color: red;
width: 50px;
height: 50px;
}
.div02 {
position: absolute;
background-color: blue;
width: 50px;
height: 50px;
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
<title>random position</title>
</head>
<body>
<script src="script.js" type="text/javascript"></script>
</body>
</html>
You need to go with your idea 1. For most games, this is something you handle at the beginning when the character is being drawn. Given a player already at (x1,y1) and a random position (x2,y2) at which you'd like to place a new player, and a desired minDistance between characters, you need a function of the sort (Java).
boolean isPlayerPositionValid(int x1, int y1, int x2, int y2, int minDistance) {
double clearance = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
return clearance >= minDistance;
}
When characters are drawn on the game frame, minDistance must of course be chosen according to the dimension (size in pixels) of your characters.
My current solution:
(1) Generate random coordinates
(2) Use the checkPosition() function to compare if the players overlap
(3) Added a minimum distance variable to increase the allowable space between each player when they are placed.
**The if statement that checks for overlap would normally be written as:
if((x2 > x1 - x2.width) && (x2 <= x1 + x1.width) && (y2 > y1 - y2.height) && (y2 <= y1 + y1.height)) { console.log("is overlapping") }
function randomPosition() {
let random = parseInt( (50 + Math.random()*100));
return random
}
let playerWidth = 50;
let playerHeight = 50;
let minDist = 20;
let div01 = document.createElement('div');
div01.setAttribute('class', 'div01');
let div02 = document.createElement('div');
div02.setAttribute('class', 'div02');
function checkPosition() {
let x1 = randomPosition();
let y1 = randomPosition();
let x2 = randomPosition();
let y2 = randomPosition();
if((x2 > x1 - playerWidth - minDist) &&
(x2 <= x1 + playerWidth + minDist) &&
(y2 > y1 - playerHeight - minDist) &&
(y2 <= y1 + playerHeight + minDist)) {
console.log("player is less than minimum distance");
checkPosition();
} else {
div01.style.top = x1 + 'px';
div01.style.left = y1 + 'px';
div02.style.top = x2 + 'px';
div02.style.left = y2 + 'px';
document.body.append(div01, div02);
}
}
checkPosition();
.div01 {
position: absolute;
background-color: red;
width: 50px;
height: 50px;
}
.div02 {
position: absolute;
background-color: blue;
width: 50px;
height: 50px;
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
<title>random position</title>
</head>
<body>
<script src="script.js" type="text/javascript"></script>
</body>
</html>

How to make the point to be spawned have the same coordinates to the "left" or "top" as the previous point

I need to make it so that it is possible to connect the dice only with straight lines. But to do this, I need each subsequent dice to have a common "top" or "left" coordinate with the previous dice and at the same time a partial random is preserved.
Here is an example:
Here is my code:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Игральные кости</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<link rel="stylesheet" type="text/css" href="./style.css">
<link rel="icon" type="image/png" href="./dice.png">
</head>
<body>
<div id="main-container">
<div id="square-container">
</div>
<canvas id="overlay"></canvas>
</div>
</body>
<script src="./script.js"></script>
</html>
html,
body {
margin: 0;
padding: 0;
}
#main-container {
width: 100%;
margin: 10px
}
#square-container {
width: 700px;
height: 600px;
position: absolute;
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
background: #dddddd;
border: 5px solid #6b6b6b;
border-radius: 5px;
z-index: 1;
}
#overlay {
width: 100%;
height: 100%;
pointer-events: none;
z-index: 9999;
position: absolute;
top: 0;
left: 0;
}
.js-is-target {
position: absolute;
width: 56px;
height: 56px;
/*background: blue;*/
border-radius: 30px;
}
.square {
top: 13px;
left: 13px;
position: relative;
width: 30px;
height: 30px;
background: red;
background-size: contain;
border-radius: 5px;
z-index: 10;
}
.active-target {
box-shadow: 0 0 0 2px rgba(255, 0, 0, 0.5);
}
//links to images of dice
var imgArr = [
'./dotsOnDice/dice1.jpg',
'./dotsOnDice/dice2.jpg',
'./dotsOnDice/dice3.jpg',
'./dotsOnDice/dice4.jpg',
'./dotsOnDice/dice5.jpg',
'./dotsOnDice/dice6.jpg'
]
//random number from 3 to 6
var randomAmount = Math.floor(Math.random() * (6 - 3 + 1)) + 3;
//limit for recursion
var limitDice = 0;
function addDice() {
// if the limit is equal to a random number, the recursion function is interrupted
if (limitDice === randomAmount) {
return
}
//random number from 2 to 5
let randomAmountImg = Math.floor(Math.random() * (5 - 2 + 1)) + 2;
//random numbers for dice coordinates
let position = {
top: Math.random() * 520,
left: Math.random() * 600
}
//saved previous coordinates for comparison
let positionAfter = {
top: 0,
left: 0
}
//creating an id that we press later
let idSquare = 'idSquare-' + limitDice;
//subtract the previous coordinates from the current coordinates
let top = position.top - positionAfter.top;
let left = position.left - positionAfter.left;
//we compare the positions of the previous coordinates to avoid a large number of overlaps
if (top > 50 || left > 50) {
positionAfter.top = position.top;
positionAfter.left = position.left;
//adding dice to the playing field
let square = $('<div class="js-is-target"><div id="' + idSquare + '" class="square"></div></div>');
square.appendTo('#square-container').css({ left: position.left + 'px', top: position.top + 'px' });
if (limitDice === 0) {
$('#idSquare-0').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
} else if (limitDice === 1) {
$('#idSquare-1').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
} else if (limitDice === 2) {
$('#idSquare-2').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
} else if (limitDice === 3) {
$('#idSquare-3').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
} else if (limitDice === 4) {
$('#idSquare-4').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
} else if (limitDice === 5) {
$('#idSquare-5').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
}
limitDice += 1;
}
addDice();
}
addDice();
//here we store the canvas field
const canvas = document.getElementById('overlay');
//here we set the format in 2D for canvas
const ctx = canvas.getContext('2d');
//here we store the width and height of the canvas field. We set the height and width in HTML
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
//in the variable we store the element that we clicked on
let targetEl = null;
//in the variable we store the coordinates of the center of the element we clicked on
let targetCenter = null;
//we check whether you clicked on the mouse or not
var down = false;
//coordinates of the pressed cursor mouseCoords
var mouseCoords = {
x: 0,
y: 0
}
//coordinates of the starting point
var centerCoords = {
x: 760,
y: 280
}
//в этой функции мы вычисляем где центр у прямоугольника
function getCenter(el) {
//if not true, the function terminates and returns null
if (!el) return null;
//we save the coordinates in the rect constant using the getBoundingClientRect method
const rect = el.getBoundingClientRect();
//returning the x and y coordinates. Here we calculate the center of the rectangle
return {
x: rect.left + rect.width / 2 + window.scrollX,
y: rect.top + rect.height / 2 + window.scrollY,
}
}
//connection points
function joinPoints(ctx, from, to) {
//dotted line size
const dashSize = 10;
//draw a dotted line and write the size in the arguments
ctx.setLineDash([dashSize]);
//setting the gradient
var stroke = ctx.createLinearGradient(from.x, from.y, to.x, to.y);
//setting the color and position
stroke.addColorStop(0, 'blue');
stroke.addColorStop(1, 'red');
//passing the colors to the strokeStyle property to set the color
ctx.strokeStyle = stroke;
//line thickness
ctx.lineWidth = 5;
//draw a contour (line)
ctx.beginPath();
//moves the contour point to the specified coordinates without drawing a line. Starting point
ctx.moveTo(from.x, from.y);
//adds a new contour point and creates a line to this point from the last specified point. End point
ctx.lineTo(to.x, to.y);
//draw a line
ctx.stroke();
}
//when you click the mouse
$(document).mousedown(function (e) {
down = true;
mouseCoords.x = e.pageX;
mouseCoords.y = e.pageY;
});
//when moving the mouse
$(document).mousemove(function (event) {
if (down) {
mouseCoords.x = event.pageX;
mouseCoords.y = event.pageY;
console.log(mouseCoords.x + ' ' + mouseCoords.y);
}
});
//when we release the mouse button
$(document).mouseup(function (e) {
down = false;
});
$(".js-is-target").hover(function (e) {
if (down) {
down = false;
//we transmit data about which js-is-target we clicked on
targetEl = e.target;
//we write down where the center of the object we clicked on is. We calculate using the getCenter() function
targetCenter = getCenter(targetEl);
//changing the coordinates of the starting point
centerCoords.x = targetCenter.x;
centerCoords.y = targetCenter.y;
}
});
//first we count, then we draw a line from ctx.lineTo to ctx.moveTo
function drwaDot() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = 'red';
ctx.moveTo(mouseCoords.x, mouseCoords.y);
ctx.lineTo(centerCoords.x, centerCoords.y);
ctx.stroke();
};
//the drawing interval of our line
var intervalId = setInterval(function () {
if (down) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
joinPoints(ctx, { x: centerCoords.x, y: centerCoords.y }, mouseCoords);
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}, 10);
jsfiddle link
under the comment "//we compare the positions of the previous coordinates to avoid a large number of overlaps" changed the condition:
if(top < 10 || left < 10){
dice spawn code
}
That's what came out of it:

Tilt element pane on mousemove

I'm trying to redo the animation that I saw on a site where an image changes it's x and y values with the movement of the mouse. The problem is that the origin of the mouse is in the top left corner and I'd want it to be in the middle.
To understand better, here's how the mouse axis values work :
Now here's how I'd want it to be:
sorry for the bad quality of my drawings, hope you understand my point from those ^^
PS: I'm having a problem while trying to transform the x y values at the same time and I don't know why.
Here's what I wrote in JavaScript :
document.onmousemove = function(e){
var x = e.clientX;
var y = e.clientY;
document.getElementById("img").style.transform = "rotateX("+x*0.005+"deg)";
document.getElementById("img").style.transform = "rotateY("+y*0.005+"deg)";
}
The exact 3D effect you're up to is called "tilting".
Long story short, it uses CSS transform's rotateX() and rotateY() on a child element inside a perspective: 1000px parent. The values passed for the rotation are calculated from the mouse/pointer coordinates inside the parent Element and transformed to a respective degree value.
Here's a quick simplified remake example of the original script:
const el = (sel, par) => (par || document).querySelector(sel);
const elWrap = el("#wrap");
const elTilt = el("#tilt");
const settings = {
reverse: 0, // Reverse tilt: 1, 0
max: 35, // Max tilt: 35
perspective: 1000, // Parent perspective px: 1000
scale: 1, // Tilt element scale factor: 1.0
axis: "", // Limit axis. "y", "x"
};
elWrap.style.perspective = `${settings.perspective}px`;
const tilt = (evt) => {
const bcr = elWrap.getBoundingClientRect();
const x = Math.min(1, Math.max(0, (evt.clientX - bcr.left) / bcr.width));
const y = Math.min(1, Math.max(0, (evt.clientY - bcr.top) / bcr.height));
const reverse = settings.reverse ? -1 : 1;
const tiltX = reverse * (settings.max / 2 - x * settings.max);
const tiltY = reverse * (y * settings.max - settings.max / 2);
elTilt.style.transform = `
rotateX(${settings.axis === "x" ? 0 : tiltY}deg)
rotateY(${settings.axis === "y" ? 0 : tiltX}deg)
scale(${settings.scale})
`;
}
elWrap.addEventListener("pointermove", tilt);
/*QuickReset*/ * {margin:0; box-sizing: border-box;}
html, body { min-height: 100vh; }
#wrap {
height: 100vh;
display: flex;
background: no-repeat url("https://i.stack.imgur.com/AuRxH.jpg") 50% 50% / cover;
}
#tilt {
outline: 1px solid red;
height: 80vh;
width: 80vw;
margin: auto;
background: no-repeat url("https://i.stack.imgur.com/wda9r.png") 50% 50% / contain;
}
<div id="wrap"><div id="tilt"></div></div>
Regarding your code:
Avoid using on* event handlers (like onmousemove). Use EventTarget.addEventListener() instead — unless you're creating brand new Elements from in-memory. Any additionally added on* listener will override the previous one. Bad programming habit and error prone.
You cannot use style.transform twice (or more) on an element, since the latter one will override any previous - and the transforms will not interpolate. Instead, use all the desired transforms in one go, using Transform Matrix or by concatenating the desired transform property functions like : .style.transform = "rotateX() rotateY() scale()" etc.
Disclaimer: The images used in the above example from the original problem's reference website https://cosmicpvp.com might be subject to copyright. Here are used for illustrative and educative purpose only.
You can find out how wide / tall the screen is:
const width = window.innerWidth;
const height = window.innerHeight;
So you can find the centre of the screen:
const windowCenterX = width / 2;
const windowCenterY = height / 2;
And transform your mouse coordinates appropriately:
const transformedX = x - windowCenterX;
const transformedY = y - windowCenterY;
Small demo:
const coords = document.querySelector("#coords");
document.querySelector("#area").addEventListener("mousemove", (event)=>{
const x = event.clientX;
const y = event.clientY;
const width = window.innerWidth;
const height = window.innerHeight;
const windowCenterX = width / 2;
const windowCenterY = height / 2;
const transformedX = x - windowCenterX;
const transformedY = y - windowCenterY;
coords.textContent = `x: ${transformedX}, y: ${transformedY}`;
});
body, html, #area {
margin: 0;
width: 100%;
height: 100%;
}
#area {
background-color: #eee;
}
#coords {
position: absolute;
left: 10px;
top: 10px;
}
<div id="area"></div>
<div id="coords"></div>
I think I would use the bounding rect of the image to determine the center based on the image itself rather than the screen... something like this (using CSSVars to handle the transform)
const img = document.getElementById('fakeimg')
addEventListener('pointermove', handler)
function handler(e) {
const rect = img.getBoundingClientRect()
const x1 = (rect.x + rect.width / 2)
const y1 = (rect.y + rect.height / 2)
const x2 = e.clientX
const y2 = e.clientY
let angle = Math.atan2(y2 - y1, x2 - x1) * (180 / Math.PI) + 90
angle = angle < 0 ?
360 + angle :
angle
img.style.setProperty('--rotate', angle);
}
*,
*::before,
*::after {
box-sizeing: border-box;
}
html,
body {
height: 100%;
margin: 0
}
body {
display: grid;
place-items: center;
}
[id=fakeimg] {
width: 80vmin;
background: red;
aspect-ratio: 16 / 9;
--rotation: calc(var(--rotate) * 1deg);
transform: rotate(var(--rotation));
}
<div id="fakeimg"></div>

2D Infinitely looping Array of elements

The Goal :
The idea is to create an element grid (image gallery for exemple) that would infinitely loop on itself scrolling on two axes.
There should be no holes nor too much randomness (avoid having the same element randomly falling aside from itself). And this no matter how many element there is in the first place (it seems easy to infinite loop through a grid of 16 (4*4) elements, not that much over 17 (17*1). (My guess is that any prime number of elements is by definition a pain to make a grid of).
So I actually found a wonderful working exemple :
http://www.benstockley.com/
It's actually really close (probably better) than what I was imagining. Now it's using canvas and i tried looking at the javascript and it's a 30000 minified lines long script so I really can't read any core logic behind it.
Math side / Problem solving :
This is the logic and theory behind the problem, the math involved and the mindset.
How the program should process the list of elements so we have no holes, infinite grid, best repartion of the elements over all the axes.
My guess is that it somehow has to be procedural. I'm not sure if we should create grids or loop through the list on every axes (kind of like sudoku ? i don't know);
Pratical side / UI / UX :
Any advice on the technologies involved, pieces of code. I'm guessing it classic DOM is out of the way and that somehow canvas or 2D webgl will be mandatory. But I would love to hear any advice on this side.
Besides all the elements grid processing. The UI and UX involved in exploring a 2D infinite or vast layout in DOM or renderer is somehow not classical. The best technologies or advice on doing this are welcome.
Exemples :
I would welcome any working exemple that somewhat share an aspect of this problem.
I've got a fiddle that's set up to arrange your 2d grid.
It functions by using horizontal and vertical "step sizes". So, moving one step right in the grid advances the horizontal step size in the list. Moving one step down advances the vertical step size in the list (and they accumulate).
We allow the advances in the list to loop back to zero when the end is reached.
It likely makes sense to use a horizontal step size of 1 (so a row of your grid will maintain your list order). For the vertical step size, you want an integer that shares no common divisors with the list length. Though it's no guarantee, I used the (rounded) square root of the list length as something that will work in lots of cases.
I'll reproduce the fiddle here:
var list = ['red','green','blue','cyan','orange','yellow','pink'];
var hstep = 1;
var vstep = Math.ceil(Math.sqrt(list.length));
function getListItem(x,y) {
var index = x * hstep + y * vstep;
return list[index % list.length];
}
var elementSize = 30;
var gutterSize = 10;
function getOffset(x,y) {
return [10 + (elementSize + gutterSize) * x, 10 + (elementSize + gutterSize) * y];
}
var frame = $('.frame');
function drawElement(x,y) {
var listItem = getListItem(x,y);
var offsets = getOffset(x,y);
var element = $('<div></div>').addClass('element').css({
left: offsets[0] + 'px',
top: offsets[1] + 'px',
'background-color': listItem
});
frame.append(element);
}
function drawElements() {
var x = 0, y = 0;
while (10 + (elementSize + gutterSize) * x < frame.width()) {
while (10 + (elementSize + gutterSize) * y < frame.height()) {
drawElement(x,y);
y++;
}
y = 0;
x++;
}
}
drawElements();
.frame {
border: 2px solid black;
margin: 40px auto;
height: 300px;
width: 300px;
position: relative;
overflow: hidden;
}
.frame .element {
position: absolute;
width: 30px;
height: 30px;
}
.buttons {
position: absolute;
top: 0px;
width: 100%;
}
.buttons button {
position: absolute;
width: 30px;
height: 30px;
padding: 5px;
}
button.up {top: 0px; left: 46%;}
button.down {top: 355px; left: 46%;}
button.left {top: 160px; left: 15px;}
button.right {top: 160px; right: 15px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="frame">
</div>
<div class="buttons">
<button class="up">↑</button>
<button class="down">↓</button>
<button class="left">←</button>
<button class="right">→</button>
</div>
You can see I've left some simple buttons to implement movement, but they are not functional yet. If you wanted to continue implementation along the lines of what I've done here, you could render your elements to a certain range beyond the visible frame, then implement some sort of animated repositioning. The renderElements function here only renders what is visible, so you can use something like that and not get stuck in rendering infinite elements, even though there's no theoretical limit to how far you can "scroll".
#arbuthnott I edited your code to implement the exploration via decrementing relativeX and relativeY variables. Also I inserted an "origin" div (1x1 px, overflow visible). This DOM element will represent the X and Y origin. I'm not sure it's essential but it's really convenient.
Now my function currently remove all elements and reinsert all elements on each update (every 500ms for now).
The idear would be to find a way to compare which elements I need versus which one already exists.
Maybe storing existing elements into an array, and compare the array with the "query" array. Than see just the elements that are missing.
This is the idear, not sure about the implementation (I suck at handling arrays).
https://jsfiddle.net/bnv6mumd/64/
var sources = ['red','green','blue','cyan','orange','yellow','pink','purple'];
var frame = $('.frame'),
origin = $('.origin');
var fWidth = 600,
fHeight = 300,
srcTotal = sources.length,
srcSquare = Math.ceil(Math.sqrt(srcTotal)),
rX = 0,
rY = 0;
var gridSize = 30,
gutterSize = 5,
elementSize = gridSize - gutterSize;
function getSourceItem(x,y) {
var index = x + y * srcSquare;
return sources[Math.abs(index) % srcTotal];
}
function getOffset(x,y) {
return [gridSize * x,gridSize * y];
}
function drawElement(x,y) {
var sourceItem = getSourceItem(x,y);
var offsets = getOffset(x,y);
var element = $('<div></div>').addClass('element').css({
left: offsets[0] + 'px',
top: offsets[1] + 'px',
'background-color': sourceItem,
});
origin.append(element);
}
function init() {
var x = 0, y = 0;
while ( gridSize * x < fWidth) {
while ( gridSize * y < fHeight) {
drawElement(x,y);
y++;
}
y = 0;
x++;
}
}
function updateElements() {
origin.empty();
var x = -Math.trunc(rX / gridSize) -1, y = - Math.trunc(rY / gridSize) -1;
while ( gridSize * x + rX < fWidth) {
while ( gridSize * y + rY < fHeight) {
drawElement(x,y);
y++;
}
y = -Math.ceil(rY / gridSize);
x++;
}
}
function animate() {
rX -= 5;
rY -= 5;
origin.css({left: rX, top: rY})
updateElements();
console.log("relative X : " + rX + " | relative Y : " + rY);
}
setInterval(animate, 500)
init();
.frame {
border: 2px solid black;
margin: 40px auto;
height: 300px;
width: 600px;
position: relative;
overflow: hidden;
}
.origin {
height: 1px;
width: 1px;
position: absolute;
overflow: visible;
}
.frame .element {
position: absolute;
width: 25px;
height: 25px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="frame">
<div class="origin" style="top:0;left:0;"></div>
</div>
This is my final snippet version (i will start to work on real implementation specific to my case now).
I think I optimized in a decent way DOM operations, code structure etc (I am very well open to suggestions though).
I now only update the elements that needs to be updated (click near the frame to show overflow)
https://jsfiddle.net/bnv6mumd/81/
var sources = ['red', 'green', 'blue', 'cyan', 'orange', 'yellow', 'pink', 'purple'];
var frame = $('.frame'),
origin = $('.origin');
var srcTotal = sources.length,
srcSquare = Math.round(Math.sqrt(srcTotal)),
fWidth = 200,
fHeight = 200,
cellSize = 50,
gutterSize = 20,
gridSize = [Math.floor(fWidth / cellSize) + 1, Math.floor(fHeight / cellSize) + 1],
aX = 0, // Absolute/Applied Coordinates
aY = 0,
rX = 0, // Relative/frame Coordinates
rY = 0;
function getSrcItem(x, y) {
var index = x + y * srcSquare;
return sources[Math.abs(index) % srcTotal];
}
function getOffset(x, y) {
return [cellSize * x, cellSize * y];
}
function getY() {
return Math.floor(-rY / cellSize);
}
function getX() {
return Math.floor(-rX / cellSize);
}
function drawElement(x, y) {
var srcItem = getSrcItem(x, y),
offsets = getOffset(x, y),
element = $('<div></div>').addClass('element').css({
left: offsets[0] + 'px',
top: offsets[1] + 'px',
'background-color': srcItem,
}).attr({
"X": x,
"Y": y
});
origin.append(element);
}
function drawCol(x, y) {
var maxY = y + gridSize[1];
while (y <= maxY + 1) {
drawElement(x - 1, y - 1);
y++;
}
}
function drawLign(x, y) {
var maxX = x + gridSize[0];
while (x <= maxX + 1) {
drawElement(x - 1, y - 1);
x++;
}
}
function drawGrid() {
origin.empty();
var x = getX(),
y = getY(),
maxX = x + gridSize[0],
maxY = y + gridSize[1];
while (y <= maxY + 1) {
drawLign(x, y);
x = getX();
y++;
}
}
function updateX(x, y, diffX, diffY) {
if (Math.sign(diffX) == -1) {
drawCol(aX - 1, y);
$('[x=' + (aX + gridSize[0]) + ']').remove();
aX--;
} else if (Math.sign(diffY) == 1) {
drawCol(aX + gridSize[0] + 2, y);
$('[x=' + (aX - 1) + ']').remove();
aX++;
}
}
function updateY(x, y, diffX, diffY) {
if (Math.sign(diffY) == -1) {
drawLign(x, aY - 1);
$('[y=' + (aY + gridSize[0]) + ']').remove();
aY--;
} else if (Math.sign(diffY) == 1) {
drawLign(x, aY + gridSize[0] + 2);
$('[y=' + (aY - 1) + ']').remove();
aY++;
}
}
function animate() {
rX += 1;
rY += 1;
origin.css({
left: rX,
top: rY
});
var x = getX(),
y = getY(),
diffX = x - aX,
diffY = y - aY;
if (diffX) {
updateX(x, y, diffX, diffY)
};
if (diffY) {
updateY(x, y, diffX, diffY)
};
requestAnimationFrame(animate);
}
$('body').click(function() {
$(frame).toggleClass("overflow");
})
drawGrid();
animate();
.frame {
border: 2px solid black;
margin: 100px auto;
height: 200px;
width: 200px;
position: relative;
}
.overflow{
overflow:hidden;
}
.origin {
height: 1px;
width: 1px;
position: absolute;
overflow: visible;
}
.frame .element {
position: absolute;
width: 30px;
height: 30px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="frame overflow">
<div class="origin" style="top:0;left:0;"></div>
</div>

Make several objects rotate on same orbit but with different positions using jQuery

Hello I was trying to find a solution for a small script that can rotate objects around 1 center but it seems a bit too tricky for me.
I've found almost perfect solution and tried to modify it to suit my needs but there's a problem.
I'm trying to make 3 objects with text to rotate with same speed, same orbit but different start positions as if they were apexes(vertices) of equilateral triangle.
Here's my fiddle so far:
( function ( $ ) {
jQuery.fn.orbit = function(s, options){
var settings = {
orbits: 1 // Number of times to go round the orbit e.g. 0.5 = half an orbit
,period: 3000 // Number of milliseconds to complete one orbit.
,maxfps: 25 // Maximum number of frames per second. Too small gives "flicker", too large uses lots of CPU power
,clockwise: false // Direction of rotation.
};
$.extend(settings, options); // Merge the supplied options with the default settings.
return(this.each(function(){
var p = $(this);
/* First obtain the respective positions */
var p_top = p.css('top' ),
p_left = p.css('left'),
s_top = s.css('top' ),
s_left = s.css('left');
/* Then get the positions of the centres of the objects */
var p_x = parseInt(p_top ) + p.height()/2,
p_y = parseInt(p_left) + p.width ()/2,
s_x = parseInt(s_top ) + s.height()/2,
s_y = parseInt(s_left) + s.width ()/2;
/* Find the Adjacent and Opposite sides of the right-angled triangle */
var a = s_x - p_x,
o = s_y - p_y;
/* Calculate the hypotenuse (radius) and the angle separating the objects */
var r = Math.sqrt(a*a + o*o);
var theta = Math.acos(a / r);
/* Calculate the number of iterations to call setTimeout(), the delay and the "delta" angle to add/subtract */
var niters = Math.ceil(Math.min(4 * r, settings.period, 0.001 * settings.period * settings.maxfps));
var delta = 2*Math.PI / niters;
var delay = settings.period / niters;
if (! settings.clockwise) {delta = -delta;}
niters *= settings.orbits;
/* create the "timeout_loop function to do the work */
var timeout_loop = function(s, r, theta, delta, iter, niters, delay, settings){
setTimeout(function(){
/* Calculate the new position for the orbiting element */
var w = theta + iter * delta;
var a = r * Math.cos(w);
var o = r * Math.sin(w);
var x = parseInt(s.css('left')) + (s.height()/2) - a;
var y = parseInt(s.css('top' )) + (s.width ()/2) - o;
/* Set the CSS properties "top" and "left" to move the object to its new position */
p.css({top: (y - p.height()/2),
left: (x - p.width ()/2)});
/* Call the timeout_loop function if we have not yet done all the iterations */
if (iter < (niters - 1)) timeout_loop(s, r, theta, delta, iter+1, niters, delay, settings);
}, delay);
};
/* Call the timeout_loop function */
timeout_loop(s, r, theta, delta, 0, niters, delay, settings);
}));
}
}) (jQuery);
$('#object1' ).orbit($('#center' ), {orbits: 2, period: 8000});
$('#object2' ).orbit($('#center' ), {orbits: 4, period: 4000});
$('#object3' ).orbit($('#center' ), {orbits: 8, period: 2000});
HTML:
<h1> Example</h1>
<div id='rotation'>
<div id='center' >C</div>
<div id='object1' >Text1</div>
<div id='object2' >Text2</div>
<div id='object3' >Text3</div>
</div>
CSS:
#rotation {position: relative; width: 600px; height: 600px; background-color: #898989}
#center {position: absolute; width: 20px; height: 20px;
top: 300px; left: 300px; background-color: #ffffff;
-moz-border-radius: 40px; border-radius: 40px;
text-align: center; line-height: 15px;
}
#object1 {position: absolute; width: 36px; height: 36px;
top: 300px; left: 200px; background-color: #ff8f23;
-moz-border-radius: 18px; border-radius: 18px;
text-align: center; line-height: 30px;
}
#object2 {position: absolute; width: 36px; height: 36px;
top: 300px; left: 200px; background-color: #ff8f23;
-moz-border-radius: 18px; border-radius: 18px;
text-align: center; line-height: 30px;
}
#object3 {position: absolute; width: 36px; height: 36px;
top: 300px; left: 200px; background-color: #ff8f23;
-moz-border-radius: 18px; border-radius: 18px;
text-align: center; line-height: 30px;
}
I used different speed and revolutions for each object because I can't figure out how to set different start positions without messing up. If I touch x,y coordinates of any object in CSS then the orbiting messes up. It seems that object positions calculations are connected. And if I change coordinates then the calculation also changes. But I can't figure out how to fix this.
If you change the starting position of each element and call the .orbit function on each of them passing the same arguments it should work.
Check this out: fiddle
This is a slightly modified version of your fiddle. (Changed the starting positions and the arguments when calling the .orbit function.
Correct me if I'm wrong or if I didn't answer your question.
Got it working as intended
Fiddle
Jquery code:
var txt = $('#text .txt'), txtLen = txt.size();
var deg2rad = function(a) { return a*Math.PI/180.0; }
var angle = 0, speed=0.1, delay = 0, r = 100;
(function rotate() {
for (var i=0; i<txtLen; i++) {
var a = angle - (i * 360 / txtLen);
$(txt[i]).css({top: r+(Math.sin(deg2rad(a))*r), left: r+(Math.cos(deg2rad(a))*r)});
}
angle = (angle - speed) % 360;
setTimeout(rotate, delay);
})();
var rotation = function (){
$("#image").rotate({
duration: 6000,
angle:360,
animateTo:0,
callback: rotation,
easing: function (x,t,b,c,d){
var d = 6000
return c*(t/d)+b;
}
});
}
rotation();
var txt2 = $('#text2 .txt2'), txt2Len = txt2.size();
var deg2rad = function(a) { return a*Math.PI/180.0; }
var angle = 13, speed=0.2, delay = 0, r = 100;
(function rotate() {
for (var i=0; i<txt2Len; i++) {
var a = angle - (i * 360 / txt2Len);
$(txt2[i]).css({top: r+(Math.sin(deg2rad(a))*r), left:
r+(Math.cos(deg2rad(a))*r)});
}
// increment our angle and use a modulo so we are always in the range [0..360] degrees
angle = (angle - speed) % 360;
// after a slight delay, call the exact same function again
setTimeout(rotate, delay);
})(); // invoke this boxed function to initiate the rotation
var rotation = function (){
$("#image").rotate({
duration: 6000,
angle:360,
animateTo:0,
callback: rotation,
easing: function (x,t,b,c,d){
var d = 6000
return c*(t/d)+b;
}
});
}
rotation();

Categories