I have written an Ebook that has 100 JavaScript, 20 React, & 2 Frontend system design questions. Get a free preview or grab your copy for $10 or ₹599.
I have been collecting frontend interview questions since the last 4 years and have been writing about it on my blog learnersbucket.com
Lately I have came across this interesting question that has been asked in an Uber interview and I tried to solve it.
Question
Draw circles on the screen on the click and whenever two circles overlap change the color of the second circle.
- When a user clicks anywhere on the DOM, create a circle around it of a radius of 100px with red background.
- If two or more circles overlap, change the background of the later circle.
Approach
Let us understand the logic of creating the circle first.
As we have to create the circle with a radius of 100px (200px diameter), rather than generating the circle on the click, we will store the coordinates of the position where the circle should be generated when the user clicks and then create circles out of these co-ordinates.
As all the circles will be in absolute position so that they can be freely placed on the screen, we will calculate the top
, bottom
, left
, and right
positions that will help in placement as well as detecting if two circles are colliding.
Get the clientX and clientY coordinates when the user clicks and align the circle around with a simple calculation so that it is placed in the center. Also before updating the state check if the current circle is overlapping with the existing circles then update the background color of the current.
// helper function to gather configuration when user clicks
const draw = (e) => {
// get the co-ordinates where user has clicked
const { clientX, clientY } = e;
// decide the position where circle will be created and placed
// as the circle is of 100 radius (200 diameter), we are subtracting the values
// so that circle is placed in the center
// set the initial background color to red
setElementsCoordinates((prevState) => {
const current = {
top: clientY - 100,
left: clientX - 100,
right: clientX - 100 + 200,
bottom: clientY - 100 + 200,
background: "red",
};
// before making the new entry
// check with the exisitng circles
for (let i = 0; i < prevState.length; i++) {
// if the current circle is colliding with any existing
// update the background color of the current
if (elementsOverlap(current, prevState[i])) {
current.background = getRandomColor();
break;
}
}
return [...prevState, current];
});
};
Assign the event listener and draw the circle on the click.
// assign the click event
useEffect(() => {
document.addEventListener("click", draw);
return () => {
document.removeEventListener("click", draw);
};
}, []);
Helper function to detect collision and generate random colors.
// helper function to generate a random color
const getRandomColor = () => {
const letters = "0123456789ABCDEF";
let color = "#";
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
// helper function to detect if two elements are overlapping
const elementsOverlap = (rect1, rect2) => {
const collide = !(
rect1.top > rect2.bottom ||
rect1.right < rect2.left ||
rect1.bottom < rect2.top ||
rect1.left > rect2.right
);
return collide;
};
Generate the circles from the coordinates which we have stored after the user has clicked. As the detection is done before making entry into the state, the circles are generated with different colors if they will be colliding.
// circle element
const Circle = ({ top, left, background }) => {
return (
<div
style={{
position: "absolute",
width: "200px",
height: "200px",
borderRadius: "50%",
opacity: "0.5",
background,
top,
left,
}}
></div>
);
};
return (
<div>
{/* render each circle */}
{elementsCoordinates.map((e) => (
<Circle {...e} key={e.top + e.left + e.right} />
))}
</div>
);
Putting everything together.
import { useEffect, useState } from "react";
// helper function to generate a random color
const getRandomColor = () => {
const letters = "0123456789ABCDEF";
let color = "#";
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
// helper function to detect if two elements are overlapping
const elementsOverlap = (rect1, rect2) => {
const collide = !(
rect1.top > rect2.bottom ||
rect1.right < rect2.left ||
rect1.bottom < rect2.top ||
rect1.left > rect2.right
);
return collide;
};
const Example = () => {
// store the configuration of each circle
const [elementsCoordinates, setElementsCoordinates] = useState([]);
// helper function to gather configuration when user clicks
const draw = (e) => {
// get the co-ordinates where user has clicked
const { clientX, clientY } = e;
// decide the position where circle will be created and placed
// as the circle is of 100 radius (200 diameter), we are subtracting the values
// so that circle is placed in the center
// set the initial background color to red
setElementsCoordinates((prevState) => {
const current = {
top: clientY - 100,
left: clientX - 100,
right: clientX - 100 + 200,
bottom: clientY - 100 + 200,
background: "red",
};
// before making the new entry
// check with the exisitng circles
for (let i = 0; i < prevState.length; i++) {
// if the current circle is colliding with any existing
// update the background color of the current
if (elementsOverlap(current, prevState[i])) {
current.background = getRandomColor();
break;
}
}
return [...prevState, current];
});
};
// assign the click event
useEffect(() => {
document.addEventListener("click", draw);
return () => {
document.removeEventListener("click", draw);
};
}, []);
// circle element
const Circle = ({ top, left, background }) => {
return (
<div
style={{
position: "absolute",
width: "200px",
height: "200px",
borderRadius: "50%",
opacity: "0.5",
background,
top,
left,
}}
></div>
);
};
return (
<div>
{/* render each circle */}
{elementsCoordinates.map((e) => (
<Circle {...e} key={e.top + e.left + e.right} />
))}
</div>
);
};
export default Example;
Top comments (0)