I have html, css and js codes for a simple cake on which I want to place some candles. But i want the candles to be placed only on the icing layer of the cake marked with the css property .icing. An algorithm to restrict the addition of candles to other layers of the cake is used but still candles can be added outside the icing layer of the cake specially the drips. How can i make the candles to be able to place on the icing layer of the cake only (which is the circular disk on top of the cake)
const cake = document.querySelector(".cake");
let candles = [];
function addCandle(left, top) {
const icing = document.querySelector(".icing");
const icingRect = icing.getBoundingClientRect();
const icingCenterX = icingRect.left + icingRect.width / 2;
const icingCenterY = icingRect.top + icingRect.height / 2;
const icingRadius = icingRect.width / 2;
const cakeRect = cake.getBoundingClientRect();
const clickX = event.clientX - cakeRect.left;
const clickY = event.clientY - cakeRect.top;
const distance = Math.sqrt(
Math.pow(clickX - (icingCenterX - cakeRect.left), 2) +
Math.pow(clickY - (icingCenterY - cakeRect.top), 2)
);
if (distance <= icingRadius && clickY >= icingRect.top - cakeRect.top && clickY <= icingRect.bottom - cakeRect.top) {
const candle = document.createElement("div");
candle.className = "candle";
candle.style.left = left + "px";
candle.style.top = top + "px";
const flame = document.createElement("div");
flame.className = "flame";
candle.appendChild(flame);
cake.appendChild(candle);
candles.push(candle);
} else {
console.log("Clicked outside of icing or in an invalid area, no candle added.");
}
}
cake.addEventListener("click", function (event) {
const activeCandles = candles.filter(
(candle) => !candle.classList.contains("out")
).length;
const rect = cake.getBoundingClientRect();
const left = event.clientX - rect.left;
const top = event.clientY - rect.top;
addCandle(left, top);
});
.cake {
position: absolute;
width: 250px;
height: 200px;
top: 50%;
left: 50%;
margin-top: -70px;
margin-left: -125px;
}
.plate {
width: 270px;
height: 110px;
position: absolute;
bottom: -10px;
left: -10px;
background-color: #ccc;
border-radius: 50%;
box-shadow: 0 2px 0 #b3b3b3, 0 4px 0 #b3b3b3, 0 5px 40px rgba(0, 0, 0, 0.5);
}
.cake > * {
position: absolute;
}
.layer {
position: absolute;
display: block;
width: 250px;
height: 100px;
border-radius: 50%;
background-color: #553c13;
box-shadow: 0 2px 0px #6a4b18, 0 4px 0px #33240b, 0 6px 0px #32230b, 0 8px 0px #31230b, 0 10px 0px #30220b, 0 12px 0px #2f220b, 0 14px 0px #2f210a, 0 16px 0px #2e200a, 0 18px 0px #2d200a, 0 20px 0px #2c1f0a, 0 22px 0px #2b1f0a, 0 24px 0px #2a1e09, 0 26px 0px #2a1d09, 0 28px 0px #291d09, 0 30px 0px #281c09;
}
.layer-top {
top: 0px;
}
.layer-middle {
top: 33px;
}
.layer-bottom {
top: 66px;
}
.icing {
top: 2px;
left: 5px;
background-color: #f0e4d0;
width: 240px;
height: 90px;
border-radius: 50%;
}
.icing:before {
content: "";
position: absolute;
top: 4px;
right: 5px;
bottom: 6px;
left: 5px;
background-color: #f4ebdc;
box-shadow: 0 0 4px #f6efe3, 0 0 4px #f6efe3, 0 0 4px #f6efe3;
border-radius: 50%;
z-index: 1;
}
.drip {
display: block;
width: 50px;
height: 60px;
border-bottom-left-radius: 25px;
border-bottom-right-radius: 25px;
background-color: #f0e4d0;
}
.drip1 {
top: 53px;
left: 5px;
transform: skewY(15deg);
height: 48px;
width: 40px;
}
.drip2 {
top: 69px;
left: 181px;
transform: skewY(-15deg);
}
.drip3 {
top: 54px;
left: 90px;
width: 80px;
border-bottom-left-radius: 40px;
border-bottom-right-radius: 40px;
}
.candle {
background-color: #7B020B;
width: 12px;
height: 35px;
border-radius: 6px/3px;
top: -20px;
left: 50%;
margin-left: -8px;
z-index: 10;
}
.candle:before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 12px;
height: 6px;
border-radius: 50%;
background-color: #ad030f;
}
.candle.out .flame {
display: none;
}
.flame {
position: absolute;
background-color: orange;
width: 10px;
height: 25px;
border-radius: 8px 8px 8px 8px/20px 20px 8px 8px;
top: -34px;
left: 50%;
margin-left: -7.5px;
z-index: 10;
box-shadow: 0 0 10px rgba(255, 165, 0, 0.5), 0 0 20px rgba(255, 165, 0, 0.5), 0 0 60px rgba(255, 165, 0, 0.5), 0 0 80px rgba(255, 165, 0, 0.5);
transform-origin: 50% 90%;
animation: flicker 1s ease-in-out alternate infinite;
}
<div class="cake">
<div class="plate"></div>
<div class="layer layer-bottom"></div>
<div class="layer layer-middle"></div>
<div class="layer layer-top"></div>
<div class="icing"></div>
<div class="drip drip1"></div>
<div class="drip drip2"></div>
<div class="drip drip3"></div>
</div>
I expected the candles to be able to place on the icing layer of the cake only but still they can be placed outside
>Solution :
I did the minimum amount of changes to your code here:
- Listening to the click event handler, specifically over the
.icing
element instead of the whole.cake; - Changing the
topcss property of the newly added candle, just
after it was pushed into the dom, substracting its computed height
so that its bottom side will match where you clicked; - Added a styling
border: solid 3px red;for the.icingto
highlight its perimeter;
This way it gets very close to the intended result. I actually also tried to transform the left coord so that the bottom-left corner of the candle matched the clicked point. But it make things worst. To make it even more accurate you should prevent the event to occur in case the clicked coords is too close to the margin of the target element (in terms of half the width of the candle or considering a bottom margin also).
const cake = document.querySelector(".cake");
let candles = [];
function addCandle(left, top) {
const icing = document.querySelector(".icing");
const icingRect = icing.getBoundingClientRect();
const icingCenterX = icingRect.left + icingRect.width / 2;
const icingCenterY = icingRect.top + icingRect.height / 2;
const icingRadius = icingRect.width / 2;
const cakeRect = cake.getBoundingClientRect();
const clickX = event.clientX - cakeRect.left;
const clickY = event.clientY - cakeRect.top;
const distance = Math.sqrt(
Math.pow(clickX - (icingCenterX - cakeRect.left), 2) +
Math.pow(clickY - (icingCenterY - cakeRect.top), 2)
);
if (distance <= icingRadius && clickY >= icingRect.top - cakeRect.top && clickY <= icingRect.bottom - cakeRect.top) {
const candle = document.createElement("div");
candle.className = "candle";
candle.style.left = left + "px";
candle.style.top = top + "px";
const flame = document.createElement("div");
flame.className = "flame";
candle.appendChild(flame);
cake.appendChild(candle);
//changing the candle's top coord substracting its computed height after it was pushed into the dom
candle.style.top = (parseInt(candle.style.top) - parseInt(window.getComputedStyle(candle).height)) + 'px';
//candle.style.left = (parseInt(candle.style.left) + parseInt(window.getComputedStyle(candle).width)) + 'px';
candles.push(candle);
} else {
console.log("Clicked outside of icing or in an invalid area, no candle added.");
}
}
//listening to the click event on the .icing element only
const icing = document.querySelector('.icing');
icing.addEventListener("click", function(event) {
const activeCandles = candles.filter(
(candle) => !candle.classList.contains("out")
).length;
const rect = cake.getBoundingClientRect();
const left = event.clientX - rect.left;
const top = event.clientY - rect.top;
addCandle(left, top);
});
.cake {
position: absolute;
width: 250px;
height: 200px;
top: 50%;
left: 50%;
margin-top: -70px;
margin-left: -125px;
}
.plate {
width: 270px;
height: 110px;
position: absolute;
bottom: -10px;
left: -10px;
background-color: #ccc;
border-radius: 50%;
box-shadow: 0 2px 0 #b3b3b3, 0 4px 0 #b3b3b3, 0 5px 40px rgba(0, 0, 0, 0.5);
}
.cake>* {
position: absolute;
}
.layer {
position: absolute;
display: block;
width: 250px;
height: 100px;
border-radius: 50%;
background-color: #553c13;
box-shadow: 0 2px 0px #6a4b18, 0 4px 0px #33240b, 0 6px 0px #32230b, 0 8px 0px #31230b, 0 10px 0px #30220b, 0 12px 0px #2f220b, 0 14px 0px #2f210a, 0 16px 0px #2e200a, 0 18px 0px #2d200a, 0 20px 0px #2c1f0a, 0 22px 0px #2b1f0a, 0 24px 0px #2a1e09, 0 26px 0px #2a1d09, 0 28px 0px #291d09, 0 30px 0px #281c09;
}
.layer-top {
top: 0px;
}
.layer-middle {
top: 33px;
}
.layer-bottom {
top: 66px;
}
.icing {
top: 2px;
left: 5px;
background-color: #f0e4d0;
width: 240px;
height: 90px;
border-radius: 50%;
}
.icing:before {
content: "";
position: absolute;
top: 4px;
right: 5px;
bottom: 6px;
left: 5px;
background-color: #f4ebdc;
box-shadow: 0 0 4px #f6efe3, 0 0 4px #f6efe3, 0 0 4px #f6efe3;
border-radius: 50%;
z-index: 1;
}
.drip {
display: block;
width: 50px;
height: 60px;
border-bottom-left-radius: 25px;
border-bottom-right-radius: 25px;
background-color: #f0e4d0;
}
.drip1 {
top: 53px;
left: 5px;
transform: skewY(15deg);
height: 48px;
width: 40px;
}
.drip2 {
top: 69px;
left: 181px;
transform: skewY(-15deg);
}
.drip3 {
top: 54px;
left: 90px;
width: 80px;
border-bottom-left-radius: 40px;
border-bottom-right-radius: 40px;
}
.candle {
background-color: #7B020B;
width: 12px;
height: 35px;
border-radius: 6px/3px;
top: -20px;
left: 50%;
margin-left: -8px;
z-index: 10;
}
.candle:before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 12px;
height: 6px;
border-radius: 50%;
background-color: #ad030f;
}
.candle.out .flame {
display: none;
}
.flame {
position: absolute;
background-color: orange;
width: 10px;
height: 25px;
border-radius: 8px 8px 8px 8px/20px 20px 8px 8px;
top: -34px;
left: 50%;
margin-left: -7.5px;
z-index: 10;
box-shadow: 0 0 10px rgba(255, 165, 0, 0.5), 0 0 20px rgba(255, 165, 0, 0.5), 0 0 60px rgba(255, 165, 0, 0.5), 0 0 80px rgba(255, 165, 0, 0.5);
transform-origin: 50% 90%;
animation: flicker 1s ease-in-out alternate infinite;
}
/*highlighting the .icing border*/
.icing{
border: solid 3px red;
}
.candle{
border: solid 3px green;
}
<div class="cake">
<div class="plate"></div>
<div class="layer layer-bottom"></div>
<div class="layer layer-middle"></div>
<div class="layer layer-top"></div>
<div class="icing"></div>
<div class="drip drip1"></div>
<div class="drip drip2"></div>
<div class="drip drip3"></div>
</div>