In my code, I used Jquery UI to make the elements draggable for easy debugging. The code I wrote with the help of someone tells me if the rounded divs are overlapping each other but I want to check if a rounded div is overlapping a square div. How can I do that?
BTW If you remove the round class the div will become a square.
let $label = $('.overlap-label span');
const hasOverlap = (x0, y0, r0, x1, y1, r1) => {
return Math.hypot(x0 - x1, y0 - y1) <= r0 + r1;
}
const coordinates = (className) => {
const val = document.querySelector(className);
const rect = val.getBoundingClientRect();
return {
y: rect.top + val.offsetHeight / 2,
x: rect.left + val.offsetHeight / 2,
rad: val.offsetHeight / 2
}
}
const checkForOverlap = () => {
const cm = coordinates(".circle.small");
const cl = coordinates(".circle.large");
$label.text(hasOverlap(cm.x, cm.y, cm.rad, cl.x, cl.y, document.querySelector(".circle.large").offsetHeight / 2));
}
$('.parent').draggable().on('drag', checkForOverlap);
.circle {
width: var(--square);
height: var(--square);
background: var(--bg);
display: inline-block;
}
.round {
border-radius: 50%;
}
.parent {
display: inline-block;
}
.small {
--square: 50px;
--bg: red;
}
.large {
--square: 100px;
--bg: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/themes/base/jquery-ui.min.css" />
<div class="overlap-label">Circles are overlapping? <span>false</span></div>
<div class="parent">
<div class="circle small round"></div>
</div>
<div class="parent">
<div class="circle large round"></div>
</div>
Related
I made a basic javascript code so you can poke divs with you mouse.
Unfortunately I have to add them manualy but i wanna add so much of them with a pattern.
First i decided to use grid but i guessed it wont work because divs (which i call them squares from now on :D) can change their position.
So I was about to ask, how can I create a javascript code that i can spawn them until they fill the screen.
Also i have another question which is realted to this project, How can i make these squares just decors. I mean by decors i dont want them to effect the webside at all, when the blocks goes out of the screen the body starts to expend, is there any way to avoid that?
(Also it will be better if you make the snippet full screen!)
EDIT: I put the refresh-button on the top left on the main div so you can draw squares by clicking it!
let mouse = {
speedX: 0,
speedY: 0,
posX: 0,
posY: 0,
movement: 0,
speed: 0
}
//on mousemove update the moouse object
document.onmousemove = function(e) {
mouse.speedX = e.movementX;
mouse.speedY = e.movementY
mouse.posX = e.pageX;
mouse.posY = e.pageY;
}
//refresh the mouse movement and speed every 100ms
setInterval(() => {
mouse.movement =
Math.sqrt(Math.pow(mouse.speedX, 2) + Math.pow(mouse.speedY, 2));
mouse.speed = mouse.movement * 10;
}, 100);
//add a square div in parent element
function addSquare(parent) {
const newDiv = document.createElement("div");
newDiv.classList.add("square")
parent.appendChild(newDiv)
return newDiv;
}
//add squares in the parent element filling the available size
//gap is the space between squares, size is the edge of the square
//if skipbefore is false it will begin to draw the next square also if it won't fit entirely
function addSquares(parent, gap, size, skipbefore = true) {
const squares = [];
let rect = parent.getBoundingClientRect();
const availableWidth = rect.width;
const availableHeight = rect.height;
let top = 100;
while (top < availableHeight) {
let left = 0;
if (skipbefore && top + size > availableHeight)
break;
while (left < availableWidth) {
if (skipbefore && left + size > availableWidth)
break;
const square = addSquare(parent);
square.style.left = `${left}px`;
square.style.top = `${top}px`;
squares.push(square);
left += gap + size;
}
top += gap + size;
}
return squares;
}
//onmoveover event handler
const squareOnMouseOver = (event) => {
const element = event.target;
const y = mouse.speedY;
const x = mouse.speedX;
const rad = Math.atan2(y, x);
yAxis = mouse.movement * Math.sin(rad);
xAxis = mouse.movement * Math.cos(rad);
const rect = element.getBoundingClientRect();
const left = Math.round(rect.x + xAxis * 3);
const top = Math.round(rect.y + yAxis * 3);
element.style.left = `${left}px`;
element.style.top = `${top}px`;
const o = rad * (180 / Math.PI);
element.style.transform = `rotate(${o}deg)`;
}
//resets the .target parent and redraw the squares inside it
function drawSquares() {
const parent = document.querySelector('.target');
parent.innerHTML = '';
const squares = addSquares(parent, 25, 75);
const colors = [
'lightcoral',
'bisque',
'aquamarine',
'cadetblue',
'greenyellow',
'yellowgreen'
];
squares.forEach(square => {
const iColor = Math.floor(Math.random() * (colors.length - 1));
const color = colors[iColor];
square.style.background = color;
square.addEventListener('mouseover', squareOnMouseOver);
});
}
body{
margin: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-color: rgb(242, 239, 231);
color: rgb(10, 10, 9);
}
.square{
background-color: lightcoral;
width: 75px;
height: 75px;
position: absolute;
transform: rotate(0deg);
transition: all ease-out 0.5s;
}
.background {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.container .row .col > * {
display: inline-block;
}
.target {
display: block;
width: 100%;
height: 100%;
}
#draw {
font-size: 20px;
padding: .2em 1em;
cursor: pointer;
}
.name{
font-size: 40px;
}
#main-container{
position: absolute;
padding: 35px 45px;
width: 950px;
height: 285px;
box-shadow: 0px 2px 5px 2px rgb(191, 188, 182);
}
.links{
display: flex;
justify-content: center;
align-items: center;
}
.icons{
width: 55px;
height: auto;
margin: 0px 25px;
padding: 10px;
border-radius: 5px;
transition: all ease-in 0.2s;
}
.icons:hover{
background-color: rgb(144, 144, 144);
}
.refresh{
position: absolute;
}
.refresh-button{
width: 25px;
height: auto;
}
.btn:hover{
background-color: rgb(144, 144, 144);
}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<link rel="stylesheet" href="css.css">
<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>Document</title>
</head>
<body>
<div class="background">
<div class="target"></div>
<div class="container text-center" id="main-container">
<div class="">
<div class="refresh">
<button class="btn" id="draw" onclick="drawSquares()"><img class="refresh-button" src="SVG/arrow-clockwise.svg"></button>
</div>
<div class="name">Berk Efe Keskin</div>
<br>
<i>This website is working in progress right now...</i>
<br>
<i>Here is some useful links.</i>
<br>
<br>
<div class="links">
<img class="icons" src="SVG/github.svg">
<img class="icons" src="SVG/linkedin.svg">
<img class="icons" src="SVG/stack-overflow.svg">
</div>
</div>
</div>
</div>
<script type="text/javascript" src = "javascript.js"></script>
</body>
</html>
You may have a function that given a container will be filled with how many squares can fit inside as long as there is still avaiable width and available height in the target.
Here in this demo I better factored your code and added a main function called drawSquares that gets called when the button reDRAW is clicked. Each time the squares are redrawn, the target content is emptied.
I'm using a button to trigger the box drawing because the available space depends on the size of the area when the action is fired. For example you can expand the snippet and decide to redraw the squares to have the whole new area filled again.
You may decide to call the action on document ready or when the window gets resized.
let mouse = {
speedX: 0,
speedY: 0,
posX: 0,
posY: 0,
movement: 0,
speed: 0
}
//on mousemove update the moouse object
document.onmousemove = function(e) {
mouse.speedX = e.movementX;
mouse.speedY = e.movementY
mouse.posX = e.pageX;
mouse.posY = e.pageY;
}
//refresh the mouse movement and speed every 100ms
setInterval(() => {
mouse.movement =
Math.sqrt(Math.pow(mouse.speedX, 2) + Math.pow(mouse.speedY, 2));
mouse.speed = mouse.movement * 10;
}, 100);
//add a square div in parent element
function addSquare(parent) {
const newDiv = document.createElement("div");
newDiv.classList.add("square")
parent.appendChild(newDiv)
return newDiv;
}
//add squares in the parent element filling the available size
//gap is the space between squares, size is the edge of the square
//if skipbefore is false it will begin to draw the next square also if it won't fit entirely
function addSquares(parent, gap, size, skipbefore = true) {
const squares = [];
let rect = parent.getBoundingClientRect();
const availableWidth = rect.width;
const availableHeight = rect.height;
let top = 100;
while (top < availableHeight) {
let left = 0;
if (skipbefore && top + size > availableHeight)
break;
while (left < availableWidth) {
if (skipbefore && left + size > availableWidth)
break;
const square = addSquare(parent);
square.style.left = `${left}px`;
square.style.top = `${top}px`;
squares.push(square);
left += gap + size;
}
top += gap + size;
}
return squares;
}
//onmoveover event handler
const squareOnMouseOver = (event) => {
const element = event.target;
const y = mouse.speedY;
const x = mouse.speedX;
const rad = Math.atan2(y, x);
yAxis = mouse.movement * Math.sin(rad);
xAxis = mouse.movement * Math.cos(rad);
const rect = element.getBoundingClientRect();
const left = Math.round(rect.x + xAxis * 3);
const top = Math.round(rect.y + yAxis * 3);
element.style.left = `${left}px`;
element.style.top = `${top}px`;
const o = rad * (180 / Math.PI);
element.style.transform = `rotate(${o}deg)`;
}
//resets the .target parent and redraw the squares inside it
function drawSquares() {
const parent = document.querySelector('.target');
parent.innerHTML = '';
const squares = addSquares(parent, 25, 75);
const colors = [
'lightcoral',
'bisque',
'aquamarine',
'cadetblue',
'greenyellow',
'yellowgreen'
];
squares.forEach(square => {
const iColor = Math.floor(Math.random() * (colors.length - 1));
const color = colors[iColor];
square.style.background = color;
square.addEventListener('mouseover', squareOnMouseOver);
});
}
body {
margin: 0;
width: 100vw;
height: 100vh;
display: flex;
}
.square {
background-color: lightcoral;
width: 75px;
height: 75px;
position: absolute;
transform: rotate(0deg);
transition: all ease-out 0.5s;
}
#Header {
font-size: italic;
}
.background {
width: 100%;
height: 100%;
}
.target {
position: relative;
display: block;
width: 100%;
height: 100%;
z-index: -1;
}
#draw {
font-size: 20px;
padding: .2em 1em;
cursor: pointer;
}
.container .row .col > * {
display: inline-block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<link el="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<body>
<div class="background">
<span>Work in progress...</span>
<div class="container text-center">
<div class="row">
<div class="col">
<h1 id="Header">Work in progress...</h1>
<button id="draw" onclick="drawSquares()">reDRAW</button>
</div>
</div>
</div>
<div class="target"></div>
</div>
</body>
I am attempting to create a drawing app in JS, however, whenever anything is drawn, it is positioned away from my cursor depending on where it is on the canvas, when I am on the furthest left/bottom side of the canvas, you can draw where your cursor is, but the further right/up I move, the more the brush begins to "drift" and go further than where my cursor is.
const canvas = document.getElementById("canvas");
const increaseBtn = document.getElementById("increase");
const decreaseBtn = document.getElementById("decrease");
const sizeEl = document.getElementById("size");
const colorEl = document.getElementById("color");
const clearEl = document.getElementById("clear");
//Core Drawing Functionality (with some research)
const ctx = canvas.getContext("2d");
let size = 5;
let isPressed = false;
let color = "black";
let x;
let y;
let fakeSize = 1;
canvas.addEventListener("mousedown", (e) => {
isPressed = true;
x = e.offsetX;
y = e.offsetY;
});
canvas.addEventListener("mouseup", (e) => {
isPressed = false;
x = undefined;
y = undefined;
});
canvas.addEventListener("mousemove", (e) => {
if (isPressed) {
const x2 = e.offsetX;
const y2 = e.offsetY;
drawCircle(x2, y2);
drawLine(x, y, x2, y2);
x = x2;
y = y2;
}
});
function drawCircle(x, y) {
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
}
function drawLine(x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.strokeStyle = color;
ctx.lineWidth = size * 2;
ctx.stroke();
}
function updateSizeOnScreen() {
sizeEl.innerHTML = fakeSize;
}
increaseBtn.addEventListener("click", () => {
size += 5;
fakeSize++;
if (fakeSize > 10) {
fakeSize = 10;
}
if (size > 50) {
size = 50;
}
updateSizeOnScreen();
});
decreaseBtn.addEventListener("click", () => {
size -= 5;
fakeSize--;
if (fakeSize < 1) {
fakeSize = 1;
}
if (size < 5) {
size = 5;
}
updateSizeOnScreen();
});
colorEl.addEventListener("change", (e) => {
color = e.target.value;
});
clearEl.addEventListener("click", () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
});
//Eraser and Pencil Actions (my own algorithm)
const eraser = document.getElementById("eraser");
const pencil = document.getElementById("pencil");
eraser.addEventListener("click", () => {
localStorage.setItem("colorEl", JSON.stringify(color));
color = "#fff";
colorEl.disabled = true;
canvas.classList.add("eraseractive");
eraser.classList.add("eraseractive");
colorEl.classList.add("eraseractive");
canvas.classList.remove("pencilactive");
eraser.classList.remove("pencilactive");
colorEl.classList.remove("pencilactive");
});
pencil.addEventListener("click", () => {
JSON.parse(localStorage.getItem("colorEl"));
color = colorEl.value;
colorEl.disabled = false;
canvas.classList.remove("eraseractive");
eraser.classList.remove("eraseractive");
colorEl.classList.remove("eraseractive");
canvas.classList.add("pencilactive");
eraser.classList.add("pencilactive");
colorEl.classList.add("pencilactive");
});
// Dark/Light Mode
const darkMode = document.getElementById("darkMode");
const lightMode = document.getElementById("lightMode");
const toolbox = document.getElementById("toolbox");
darkMode.addEventListener("click", () => {
darkMode.classList.add("mode-active");
lightMode.classList.remove("mode-active");
lightMode.classList.add("rotate");
darkMode.classList.remove("rotate");
toolbox.style.backgroundColor = "#293462";
document.body.style.backgroundImage =
"url('/assets/images/darkModeBackground.svg')";
document.body.style.backgroundSize = "1920px 1080px";
canvas.style.borderColor = "#293462";
toolbox.style.borderColor = "#293462";
});
lightMode.addEventListener("click", () => {
lightMode.classList.add("mode-active");
darkMode.classList.remove("mode-active");
darkMode.classList.add("rotate");
lightMode.classList.remove("rotate");
toolbox.style.backgroundColor = "#293462";
document.body.style.backgroundImage =
"url('/assets/images/lightModeBackground.svg')";
document.body.style.backgroundSize = "1920px 1080px";
canvas.style.borderColor = "#293462";
toolbox.style.borderColor = "#293462";
});
* {
box-sizing: border-box;
font-size: 20px !important;
}
body {
background: url("https://drawing-app-green.vercel.app/assets/images/lightModeBackground.svg");
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
margin: 0;
position: relative;
max-height: 100vh;
overflow: hidden;
}
::selection {
background: transparent;
}
::-moz-selection {
background: transparent;
}
.mode {
display: flex;
position: absolute;
top: 10px;
right: 25px;
cursor: pointer;
}
.light-mode {
color: yellow;
}
.dark-mode {
color: #16213e;
}
.container {
display: flex;
flex-direction: column;
max-width: 1200px;
width: 100%;
max-height: 600px;
height: 100%;
}
canvas {
display: flex;
border: 2px solid #293462;
cursor: url("https://drawing-app-green.vercel.app/assets/images/pencilCursor.png") 2 48, pointer;
background-color: #fff;
margin-top: 3rem;
width: 100%;
height: 600px;
}
.toolbox {
background-color: #293462;
border: 1px solid #293462;
display: flex;
width: 100%;
align-items: center;
justify-content: center;
padding: 0.2rem;
}
.toolbox > * {
background-color: #fff;
border: none;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 2rem;
height: 30px;
width: 30px;
margin: 0.25rem;
padding: 0.25rem;
cursor: pointer;
}
.toolbox > *:last-child {
margin-left: auto;
}
canvas.eraseractive {
cursor: url("https://drawing-app-green.vercel.app/assets/images/eraserCursor.png") 2 48, pointer;
}
#color.eraseractive {
cursor: not-allowed;
}
canvas.pencilactive {
cursor: url("https://drawing-app-green.vercel.app/assets/images/pencilCursor.png") 2 48, pointer;
}
.mode-active {
visibility: hidden;
}
.rotate {
transform: rotate(360deg);
transition: transform 1s linear;
}
<!DOCTYPE html>
<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>Drawing App</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"
integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
</head>
<body>
<i class="fa-solid fa-moon dark-mode fa-2x mode" id="darkMode"></i>
<i
class="fa-solid fa-sun light-mode fa-2x mode mode-active"
id="lightMode"
></i>
<div class="container">
<canvas id="canvas" width="1024" height="600"></canvas>
<div class="toolbox" id="toolbox">
<button id="decrease">-</button>
<span id="size">1</span>
<button id="increase">+</button>
<input type="color" id="color" />
<button id="pencil">
<img src="assets/images/pencilCursor.png" alt="" />
</button>
<button id="eraser">
<img src="assets/images/eraserCursor.png" alt="" />
</button>
<button id="clear">X</button>
</div>
</div>
<script src="assets/js/script.js"></script>
</body>
</html>
Your problem is that your canvas dimentions don't match with the dimentions of the HTML element that contains it. You see: your canvas has a fixed width="" and height="" attributes set. But in your HTML your canvas element has a width of 100%. So that means that the container vairies in dimentions but the canvas inside it not. This result in the canvas trying to resize to show inside the container thus giving you issues with calculating exacly what pixel you are clicking.
You have two options:
Option 1: calculate your click position taking into account canvas deformation
If you want your canvas to resize, then calculate the real position using a simple ratio formula. If for example your canvas has a width of 100 but right now its container is 10px wide, then if you click on pixel 5 you expect a dot to be drawn at pixel 50. In other words if your canvas is smaller by a factor of 10 then you need to multiply your position by a factor of 10.
In your code it would look something like this:
// this is your same code in lines 33 ana34 but see that I added a multiplication by the ratio between the canvas size and the canvas container
const x2 = e.offsetX * (canvas.width / ctx.canvas.getBoundingClientRect().width);
const y2 = e.offsetY * (canvas.height / ctx.canvas.getBoundingClientRect().height);
Option #2: Dont allow your canvas to deform
Remove the container class, and remove the width:100% from your canvas css. Your canvas will overflow and cause a scrollbar but the positions will be calculated properly with your code.
I have some Javascript drawing random square elements in the DOM. I have a gif (Image) I want these elements to appear over but they keep appearing underneath the gif. I tried defining z-depth and layout parameters to move these elements on top of the image here, but this produced no difference.
Any assistance in achieving the result (drawing elements onclick, on top of this gif) would be much appreciated.
I ultimately want to draw various other images over this image onclick, restricted to this particular area on top of the gif. If someone can suggest a solution to this as well I would be very much grateful!
(Code features some unused elements from my past attempts)
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="div.css" />
</head>
<body>
<div style="cursor: pointer;" id="boxy" >
<img src="bg.gif" alt="unfinished bingo card" onclick="create()" />
</div>
</div>
<script>
var body = document.getElementsByTagName("body")[0];
var canvas = document.createElement("canvas");
canvas.height = 1300;
canvas.width = 1300;
var context = canvas.getContext("2d");
body.appendChild(canvas);
var rects = [];
function create() {
// Opacity
context.globalAlpha = 0.7;
var color = '#' + Math.round(0xffffff * Math.random()).toString(16);
context.fillStyle = color;
//Each rectangle's size is (20 ~ 100, 20 ~ 100)
var coordx = Math.random() * canvas.width;
var coordy = Math.random() * canvas.width;
var width = Math.random() * 80 + 20;
var height = Math.random() * 80 + 20;
var rect = {
x: coordx,
y: coordy,
w: width,
h: height
}
var ok = true;
rects.forEach(function (item) {
if (isCollide(rect, item)) {
console.log("collide");
ok = false;
} else {
console.log("no collision");
}
})
if (ok) {
context.fillRect(coordx, coordy, width, height);
rects.push(rect);
} else {
console.log('rect dropped');
}
console.log(rects);
}
function isCollide(a, b) {
return !(
((a.y + a.h) < (b.y)) ||
(a.y > (b.y + b.h)) ||
((a.x + a.w) < b.x) ||
(a.x > (b.x + b.w))
);
}
document.getElementById('boxy').addEventListener('click', create);
document.getElementById('canvas').style.position = "relative";
document.getElementById('canvas').style.zIndex = "10";
</script>
</body>
</html>
#my-div {
width: 1300x;
height: 1300px;
z-index: -1;
}
a.fill-div {
display: block;
height: 100%;
width: 100%;
text-decoration: none;
}
#boxy {
display: inline-block;
height: 100%;
width: 100%;
text-decoration: none;
z-index: -1;
}
.canvas {
display: inline-block;
height: 100%;
width: 100%;
text-decoration: none;
z-index: 10;
}
You have to use position:absolute; to take it out of the html flow.
Now anything added after the image will be placed like the image was never there.
img {
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
left: 0;
z-index: -10;
}
div {
font-size: 2rem;
color: white;
}
<img src="https://images.unsplash.com/photo-1664273107076-b6d1fbfb973b?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1171&q=80">
<div>Hello i am on top of the image
</div>
This is my codepen. I want to check if the divs are overlapping each other with jQuery. I wrote a code for that but it doesn't work with round boxes. it only works with squares and rectangles. how can I make it work with round divs?
const coordinates = (className) => {
const val = document.querySelector(className);
return {
y: val.offsetTop,
x: val.offsetLeft,
yh: val.offsetTop + val.offsetHeight,
xw: val.offsetLeft + val.offsetWidth,
}
}
const cm = coordinates(".circle.small");
const cl = coordinates(".circle.large");
const offset_x = cm.x < cl.x && cm.xw > cl.x;
const offset_xw = cm.x < cl.xw && cm.xw > cl.xw;
const offset_cx = cm.x < cl.xw && cm.xw < cl.xw;
const offset_cy = cm.y < cl.yh && cm.yh < cl.yh;
const offset_y = cm.y < cl.y && cm.yh > cl.y;
const offset_yh = cm.y < cl.yh && cm.yh > cl.yh;
const is_x = offset_x || offset_xw || offset_cx;
const is_y = offset_y || offset_yh || offset_cy;
console.log(is_x, is_y);
.circle {
width: var(--square);
height: var(--square);
background: var(--bg);
border-radius: 50%;
}
.parent {
margin-left: 5px;
}
.parent2 {
margin-left: 15px;
}
.small {
--square: 50px;
--bg: red;
margin-bottom: -5px;
}
.large {
--square: 100px;
--bg: green;
}
<div class="parent">
<div class="circle small"></div>
</div>
<div class="parent2">
<div class="circle large"></div>
</div>
Your logic to calculate the delta between circle positions isn't quite right. You need to get the X and Y from the centre of each circle, then work out if the hypotenuse calculated from those two points is less than half of the combined radii.
Here's a working example. Note that I only added jQuery/jQueryUI to make dragging the circles around easier for testing - neither of these libraries are required for production use.
let $label = $('.overlap-label span');
const hasOverlap = (x0, y0, r0, x1, y1, r1) => Math.hypot(x0 - x1, y0 - y1) <= r0 + r1;
const coordinates = (className) => {
const el = document.querySelector(className);
const rect = el.getBoundingClientRect();
const radius = el.offsetHeight / 2;
return {
y: rect.top + radius,
x: rect.left + radius,
r: radius
}
}
const checkForOverlap = () => {
const cm = coordinates(".circle.small");
const cl = coordinates(".circle.large");
$label.text(hasOverlap(cm.x, cm.y, cm.r, cl.x, cl.y, cl.r));
}
$('.parent').draggable().on('drag', checkForOverlap);
.circle {
width: var(--square);
height: var(--square);
background: var(--bg);
border-radius: 50%;
display: inline-block;
}
.parent {
display: inline-block;
}
.small {
--square: 50px;
--bg: red;
}
.large {
--square: 100px;
--bg: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/themes/base/jquery-ui.min.css" />
<div class="overlap-label">Circles are overlapping? <span>false</span></div>
<div class="parent">
<div class="circle small"></div>
</div>
<div class="parent">
<div class="circle large"></div>
</div>
Well, I admit that I am really really bad with trigonometry. Just for the sake of context, I will add things from the question I am referring to here.
Reference question: https://stackoverflow.com/a/39429290/168492.
I am trying to build circular rotating navigation but I have landed in a pickle.
What I want to achieve:
.
I want elements to rotate when you click on any one element. For e.g., Menu Items Are Rotated If The User Selected Item 3:
.
What I have been able to do so far. I have been able to render the items on a circular plot using sin and cos function. I have also been able to achieve circular motion when I click on any of the elements (using the code in the reference question - https://stackoverflow.com/a/39429290/168492). The problem happens when I click on the last element. It marked the whole nav move in another direction and do a complete rotation in opposite direction.
Below is the code using which I was able to position elements on the circle:
const count = this.slides.length;
const increase = (Math.PI * 2) / this.slides.length;
const radius = 100;
let angle = 0;
var that = this;
this.slides = this.slides.map(function (item, i) {
item.id = i;
item.top = Math.sin(-Math.PI / 4 + i * increase) * radius + "px";
item.left = Math.cos(-Math.PI / 4 + i * increase) * radius + "px";
/* I tried adding an angle component (couldn't get it to work) */
item.angle = Math.atan2(
Math.sin(-Math.PI / 4 + i * increase) * radius,
Math.cos(-Math.PI / 4 + i * increase) * radius
);
console.log((item.angle * 180) / Math.PI);
return item;
});
What I want is to make the nav rotate only in one direction. How do I do it?
Below is the code which triggers the rotation on click:
function move(e) {
const n = buttons.indexOf(e.target);
var item = that.slides.find((slide) => slide.id == n);
const endAngle = (n % count) * increase;
turn();
function turn() {
if (Math.abs(endAngle - angle) > 1 / 12) {
const sign = endAngle > angle ? 1 : -1;
angle = angle + sign / 12;
setTimeout(turn, 20);
} else {
angle = endAngle;
}
buttons.forEach((button, i) => {
var item = that.slides.find((slide) => slide.id == n);
button.style.top =
Math.sin(-Math.PI / 4 + i * increase - angle) * radius + "px";
button.style.left =
Math.cos(-Math.PI / 4 + i * increase - angle) * radius + "px";
});
}
}
I have tried changing the sign value to only 1. But it makes the menu rotate into an infinite loop.
Full snippet:
const buttons = Array.from(document.querySelectorAll('.button'))
const count = buttons.length
const increase = Math.PI * 2 / buttons.length
const radius = 150
let angle = 0
buttons.forEach((button, i) => {
button.style.top = Math.sin(-Math.PI / 2 + i * increase) * radius + 'px'
button.style.left = Math.cos(-Math.PI / 2 + i * increase) * radius + 'px'
button.addEventListener('click', move)
})
function move(e) {
const n = buttons.indexOf(e.target)
const endAngle = (n % count) * increase
turn()
function turn() {
if (Math.abs(endAngle - angle) > 1 / 8) {
const sign = endAngle > angle ? 1 : -1
angle = angle + sign / 8
setTimeout(turn, 20)
} else {
angle = endAngle
}
buttons.forEach((button, i) => {
button.style.top = Math.sin(-Math.PI / 2 + i * increase - angle) * radius + 'px'
button.style.left = Math.cos(-Math.PI / 2 + i * increase - angle) * radius + 'px'
})
}
}
html,
body {
height: 100%;
}
.menu {
height: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
background-color: seagreen;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
line-height: 100px;
text-align: center;
}
.center {
width: 100px;
height: 100px;
background-color: goldenrod;
border-radius: 100%;
position: relative;
}
.button {
position: absolute;
width: 100px;
height: 100px;
border-radius: 100%;
background-color: pink;
line-height: 100px;
text-align: center;
cursor: pointer;
}
<div class="menu">
<div class="center">menu
<div class="button">1</div>
<div class="button">2</div>
<div class="button">3</div>
<div class="button">4</div>
<div class="button">5</div>
</div>
</div>
Would be great if someone can help!
You don't need trigonometry to solve this problem. It's only arithmetic. Except for the "there are 360 degrees in a circle" part, which you already covered.
You have a menu which rotates. And some buttons which, when rotated, should remain upright. Place the rotation of the menu in a CSS variable and use it to rotate the entire menu and to rotate the buttons back by the same amount.
For positioning the buttons, use some helper wrappers (some lines from the center, really), rotated around the point in the center, placing buttons at the other end of the line, effectively distancing the buttons from the center of the menu. The rotation of these helpers (axis) only needs to be set at the start. Once set, they no longer need adjusting. Their rotation amount also needs to be placed into a variable, so the buttons use it to counter-rotate by the same amount.
Now, regardless of the rotation of the menu or of any individual axis, the buttons, by being counter-rotated by the values in the same variables, will always stand up straight. By changing the menu rotation variable you rotate the entire thing. Also, by changing the rotation value of each axis you can create fancy opening or closing effects, should you want to. And the buttons will always remain upright.
Obviously, if you want to animate the whole thing, the moving parts should share the same transition properties. If they don't, the buttons won't be standing straight at all times.
See it working:
const axes = [...document.querySelectorAll('.axis')];
axes.forEach((axis, i) => {
const angle = 360 * i / axes.length;
axis.style.setProperty('--axis-rotation', `${angle}deg`);
})
let rotation = 0; // change it to adjust the initial position of buttons
updateMenuRotation(rotation);
function updateMenuRotation(deg) {
document.querySelector('.menu').style
.setProperty('--menu-rotation', `${deg}deg`);
}
function rotateMenu(steps) {
rotation += 360 * steps / axes.length;
updateMenuRotation(rotation);
console.log('rotation:', rotation);
}
.menu {
width: 200px;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
--menu-rotation: 0deg;
transform: rotate(var(--menu-rotation));
}
.menu .center {
transform: rotate(calc(-1 * var(--menu-rotation)));
}
.menu .axis {
position: absolute;
width: 100px;
left: 100px;
height: 0;
display: flex;
align-items: center;
justify-content: flex-end;
transform-origin: 0 0;
transform: rotate(var(--axis-rotation));
}
.axis > * {
width: 70px;
height: 70px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 35px;
border: 1px solid #ccc;
transform: rotate(calc(calc(-1 * var(--axis-rotation)) - var(--menu-rotation)));
}
.menu,
.menu .center,
.menu .axis,
.menu .axis > * {
transition: transform .35s cubic-bezier(.4,0,.2,1);
}
.controls {
z-index: 1;
position: relative;
}
.controls button {
cursor: pointer;
}
<div class="menu">
<div class="center">menu</div>
<div class="axis">
<div>1</div>
</div>
<div class="axis">
<div>2</div>
</div>
<div class="axis">
<div>3</div>
</div>
<div class="axis">
<div>4</div>
</div>
<div class="axis">
<div>5</div>
</div>
</div>
<div class="controls">
<button onclick="rotateMenu(1)">rotate +1</button>
<button onclick="rotateMenu(-1)">rotate -1</button>
<button onclick="rotateMenu(2)">rotate +2</button>
<button onclick="rotateMenu(5)">rotate +5</button>
</div>
Now, as far as I understand your attempt, I believe you want whenever a button is clicked to find the minimal menu rotation for putting that button on top. I purposefully didn't solve this problem above, as I believe it should be solved separately from the problem of rotating the menu a particular number of steps.
I advise you to try and solve it yourself.
If you get stuck, below is how I solved it. I can't shake the feeling I over-complicated it, but I also don't see a way to further simplify it:
const axes = [...document.querySelectorAll('.axis')];
axes.forEach((axis, i) => {
const angle = 360 * i / axes.length;
axis.style.setProperty('--axis-rotation', `${angle}deg`);
axis.querySelector('div').addEventListener('click', rotateToTop);
})
let rotation = -90; // change it to adjust the initial position of buttons
updateMenuRotation(rotation);
function updateMenuRotation(deg) {
document.querySelector('.menu').style
.setProperty('--menu-rotation', `${deg}deg`);
}
function rotateToTop(e) {
const button = e.target;
if (button) {
[...document.querySelectorAll('.axis > div.active')]
.forEach(el => el.classList.remove('active'));
button.classList.add('active');
rotateMenu(
minStepsToTop(
getRotation('axis', button),
getRotation('menu', button),
axes.length
)
);
}
}
function minStepsToTop(aR, mR, aL) {
// aR => axisRotatin
// mR => menuRotation
// aL => axis.length
// angle => 360 / aL
// stepsFromMenu => (((mR + 360) % 360) + 90) / angle;
// stepsFromAxis => Math.round(aR / angle);
// totalSteps => Math.round((((mR + 360) % 360) + 90) + aR) / angle);
const totalSteps = Math.round(((((mR + 360) % 360) + 90) + aR) * aL / 360);
// console.log(totalSteps);
// totalSteps as closest number to 0 (positive or negative)
const maxAbsoluteSteps = Math.floor(aL / 2); // 5 => 2; 6 => 3; 7 => 3, etc...
return -(((totalSteps + maxAbsoluteSteps + aL) % aL) - maxAbsoluteSteps);
}
function getRotation(type, target) {
return +(getComputedStyle(target).getPropertyValue(`--${type}-rotation`).replace('deg', ''));
}
function rotateMenu(steps) {
rotation += 360 * steps / axes.length;
updateMenuRotation(rotation);
}
.menu {
width: 200px;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
--menu-rotation: 0deg;
transform: rotate(var(--menu-rotation));
}
.menu .center {
transform: rotate(calc(-1 * var(--menu-rotation)));
}
.menu .axis {
position: absolute;
width: 100px;
left: 100px;
height: 0;
display: flex;
align-items: center;
justify-content: flex-end;
transform-origin: 0 0;
transform: rotate(var(--axis-rotation));
}
.axis > * {
width: 54px;
height: 54px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 27px;
border: 1px solid #ccc;
transform: rotate(calc(calc(-1 * var(--axis-rotation)) - var(--menu-rotation)));
cursor: pointer;
}
.axis .active {
background-color: #212121;
color: white;
}
.menu,
.menu .center,
.menu .axis,
.menu .axis > * {
transition: transform .35s cubic-bezier(.4,0,.2,1);
}
.controls {
z-index: 1;
position: relative;
}
.controls button {
cursor: pointer;
}
<div class="menu">
<div class="center">menu</div>
<div class="axis">
<div class="active">1</div>
</div>
<div class="axis">
<div>2</div>
</div>
<div class="axis">
<div>3</div>
</div>
<div class="axis">
<div>4</div>
</div>
<div class="axis">
<div>5</div>
</div>
<div class="axis">
<div>6</div>
</div>
<div class="axis">
<div>7</div>
</div>
</div>
Should work with any number of buttons, but I haven't tried it.