Restrict creation of new divs to a specific area

Viewed 123

I have a small page. Divas in the form of circles are created here every certain time. They spawn in random places.

As can be seen even on the buttons and slightly outside the page.

The question is. Is it possible to make a box that does not touch the buttons, and that the circles are created within this box?

This should be done as a border with a certain extension, but specifying everything in pixels is not an option, it will be bad for different screens.

I created such a frame, replaced document.body.appendChild(div); on the document.getElementById("spawnRadius").appendChild(div);

It seems that they should appear within this frame, but no, all the same throughout the page.

I also tried instead of whole page height and width document.documentElement.clientWidth use the width and height of the desired border spawnRadius.width

But now all my circles do not appear randomly, but at the beginning of this block in one place.

I tried to see these values ​​through console.log

console.log(documentHeight);
console.log(documentWidth);

But getting there undefined

PS. Demo watch in full page

//timer

var minutesLabel = document.getElementById("minutes");
var secondsLabel = document.getElementById("seconds");
var totalSeconds = 0;
setInterval(setTime, 1000);

function setTime() {
  ++totalSeconds;
  secondsLabel.innerHTML = pad(totalSeconds % 60);
  minutesLabel.innerHTML = pad(parseInt(totalSeconds / 60));
}

function pad(val) {
  var valString = val + "";
  if (valString.length < 2) {
    return "0" + valString;
  } else {
    return valString;
  }
}

//create circle

var widthHeight = 65;
var margin = 25;
var delta = widthHeight + margin;
var spawnRadius = document.getElementById("spawnRadius");

let clicks = 0;

function createDiv(id, color) {
  let div = document.createElement('div');
  var currentTop = 0;
  var documentHeight = spawnRadius.height;
  var documentWidth = spawnRadius.width;
  div.setAttribute('class', id);
  if (color === undefined) {
    let colors = ['#35def2', '#35f242', '#b2f235', '#f2ad35', '#f24735', '#3554f2', '#8535f2', '#eb35f2', '#f2359b', '#f23547'];
    div.style.borderColor = colors[Math.floor(Math.random() * colors.length)];
  }
  else {
   div.style.borderColor = color; 
  }
  div.classList.add("circle");
  div.classList.add("animation");
  
  currentTop = Math.floor(Math.random() * documentHeight) - delta;
  currentLeft = Math.floor(Math.random() * documentWidth) - delta;
  
  var limitedTop = Math.max(margin * -1, currentTop);
  var limitedLeft = Math.max(margin * -1, currentLeft);

  div.style.top = limitedTop + "px";
  div.style.left = limitedLeft + "px";
  
  const nodes = document.querySelectorAll('.animation');
  for(let i = 0; i < nodes.length; i++) {
  nodes[i].addEventListener('click', (event) => {
    event.target.style.animation = 'Animation 200ms linear';
    setTimeout(() => {
      event.target.style.animation = '';
    }, 220);  });
  }
  
  $(div).click(function() {
    $('#clicks').text(++clicks);            
    $(this).fadeOut();
  });
  
  document.getElementById("spawnRadius").appendChild(div);
}
    
let i = 0;

const oneSecond = 600;

setInterval(() => {
  i += 1;
  createDiv(`circle${i}`);
}, oneSecond);
html, body {
  width: 100%;
  height: 100%;
  margin: 0;
  background: #0f0f0f;
}

.back {
  font-family: "Comic Sans MS", cursive, sans-serif;
  font-size: 25px;
  letter-spacing: 2px;
  word-spacing: 2px;
  color: #ffffff;
  text-shadow: 0 0 5px #ffffff, 0 0 10px #ffffff, 0 0 20px #ffffff, 0 0 40px #ff00de, 0 0 80px #ff00de, 0 0 90px #ff00de, 0 0 100px #ff00de, 0 0 150px #ff00de;
  font-weight: 700;
  text-decoration: none;
  font-style: italic;
  font-variant: normal;
  text-transform: lowercase;
  position: absolute;
  top: 25%;
  left: 2%;
  user-select: none;
  z-index: 999;
}

.panel {
  color: #0f0f0f;
  font-size: 40px;
  z-index: 999;
  position: absolute;
  cursor: default;
  user-select: none;
  color: #0f0f0f;
}

.score {
  border: 1px solid #ffffff;
  padding: 5px;
  background-color: #ffffff;
  border-radius: 40px 10px;
}

.time {
  border: 1px solid #ffffff;
  padding: 5px;
  background-color: #ffffff;
  border-radius: 40px 10px;
}

.circle {
  width: 60px;
  height: 60px;
  border-radius: 60px;
  background-color: #0f0f0f;
  border: 3px solid #000;
  margin: 20px;
  position: absolute;
}

@keyframes Animation {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(.8);
  }
  100% {
    transform: scale(1);
  }
}

#spawnRadius {
  top: 55%;
  height: 650px;
  width: 1000px;
  left: 50%;
  white-space: nowrap;
  position: absolute;
  transform: translate(-50%, -50%);
  background: #0f0f0f;
  border: 2px solid #ebc6df;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span class="panel">
<span class="score">Score: <a id="clicks">0</a></span>
<span class="time">Time: <label id="minutes">00</label>:<label id="seconds">00</label></span>
</span>
<a href="" class="back">back</a>

<div id="spawnRadius"></div>

1 Answers

To answer your main question, the getBoundingClientRect method can be used to retrieve the current bounding rectangle of an element, using which you can determine where the valid spawn areas are.

When choosing a valid placement, only consider the width and height of the container element, since the coordinates of child elements are relative to its parent. You also need to take into account the size of the elements being spawned, so the valid range of the x position for example is 0 to containerWidth - circleWidth.

The circles also had a CSS margin associated with them, which would offset them past their absolute coordinates.

There are a few other issues with the code though which you may run into later on:

There was an odd mix of jQuery and standard JavaScript calls, so if you're familiar with native JavaScript methods then it's likely simpler to stick with those and remove the dependency on jQuery.

For example, there were two click event handlers on each circle, one to add the CSS animation and another to increment the score. These can be combined into a single function.

The bounce out animation and the jQuery fade out can also be combined by adding opacity values into the animation start and end keyframes.

There was a loop in the createDiv function which added another click event handler to every circle element rather than just to the newly created element. This may have originally necessitated the jQuery click handler outside of that loop, since otherwise the score counter would have been incremented multiple times.

It was also possible to click the circles multiple times before the animation was complete (hence adding multiple points), which was likely not intended. Adding a simple Boolean clicked flag can avoid this.

Once the fade animation completed, the circle element itself was still on the page, it just had a display of none so wouldn't be visible. Over time, this would cause slowdowns on lower end hardware since there would be many DOM elements still sitting in memory that were no longer required. As such, it's best to remove elements from the DOM once they're no longer needed using removeChild. You had the right idea by removing the animation after the animation completed.

Here's the amended code:

var minutesLabel = document.getElementById("minutes");
var secondsLabel = document.getElementById("seconds");
var clickEl = document.getElementById("clicks");
var totalSeconds = 0;
let clicks = 0;
setInterval(setTime, 1000);

function setTime() {
    ++totalSeconds;
    secondsLabel.innerText = pad(totalSeconds % 60);
    minutesLabel.innerText = pad(parseInt(totalSeconds / 60));
}

function pad(val) {
    var valString = val + "";
    if (valString.length < 2) {
        return "0" + valString;
    } else {
        return valString;
    }
}


var spawnRadius = document.getElementById("spawnRadius");
var spawnArea = spawnRadius.getBoundingClientRect();
const circleSize = 66; // Including borders

//create circle
function createDiv(id, color) {
    let div = document.createElement('div');
    div.setAttribute('class', id);
    if (color === undefined) {
        let colors = ['#35def2', '#35f242', '#b2f235', '#f2ad35', '#f24735', '#3554f2', '#8535f2', '#eb35f2', '#f2359b', '#f23547'];
        div.style.borderColor = colors[Math.floor(Math.random() * colors.length)];
    }
    else {
        div.style.borderColor = color; 
    }
    
    // Randomly position circle within spawn area
    div.style.top = `${Math.floor(Math.random() * (spawnArea.height - circleSize))}px`;
    div.style.left = `${Math.floor(Math.random() * (spawnArea.width - circleSize))}px`;
    div.classList.add("circle", "animation");
    
    // Add click handler
    let clicked = false;
    div.addEventListener('click', (event) => {
        if (clicked) { return; } // Only allow one click per circle
        clicked = true;
        
        div.style.animation = 'Animation 200ms linear forwards';
        setTimeout(() => { spawnRadius.removeChild(div); }, 220);
        clickEl.innerText = ++clicks
    });
    
    spawnRadius.appendChild(div);
}

let i = 0;

const oneSecond = 1000;

setInterval(() => {
    i += 1;
    createDiv(`circle${i}`);
}, oneSecond);
html, body {
    width: 100%;
    height: 100%;
    margin: 0;
    background: #0f0f0f;
}

.back {
    font-family: "Comic Sans MS", cursive, sans-serif;
    font-size: 25px;
    letter-spacing: 2px;
    word-spacing: 2px;
    color: #ffffff;
    text-shadow: 0 0 5px #ffffff, 0 0 10px #ffffff, 0 0 20px #ffffff, 0 0 40px #ff00de, 0 0 80px #ff00de, 0 0 90px #ff00de, 0 0 100px #ff00de, 0 0 150px #ff00de;
    font-weight: 700;
    text-decoration: none;
    font-style: italic;
    font-variant: normal;
    text-transform: lowercase;
    position: absolute;
    top: 25%;
    left: 2%;
    user-select: none;
    z-index: 999;
}

.panel {
    color: #0f0f0f;
    font-size: 40px;
    z-index: 999;
    position: absolute;
    cursor: default;
    user-select: none;
    color: #0f0f0f;
}

.score {
    border: 1px solid #ffffff;
    padding: 5px;
    background-color: #ffffff;
    border-radius: 40px 10px;
}

.time {
    border: 1px solid #ffffff;
    padding: 5px;
    background-color: #ffffff;
    border-radius: 40px 10px;
}

.circle {
    width: 60px;
    height: 60px;
    border-radius: 60px;
    background-color: #0f0f0f;
    border: 3px solid #000;
    position: absolute;
}

@keyframes Animation {
    0% {
        transform: scale(1);
        opacity: 1;
    }
    50% {
        transform: scale(.8);
    }
    100% {
        transform: scale(1);
        opacity: 0;
    }
}

#spawnRadius {
    top: 55%;
    height: 650px;
    width: 1000px;
    left: 50%;
    white-space: nowrap;
    position: absolute;
    transform: translate(-50%, -50%);
    background: #0f0f0f;
    border: 2px solid #ebc6df;
}
<span class="panel">
    <span class="score">Score: <a id="clicks">0</a></span>
    <span class="time">Time: <label id="minutes">00</label>:<label id="seconds">00</label></span>
</span>
<a href="" class="back">back</a>
<div id="spawnRadius"></div>

Related