Подскажите как реализовать функцию для рандомных элементов?

Ребята строго не судите мой код я только учусь! Но конструктивной критике буду рад!

проблема следующая решил для практики создать что то на подобие страницы с вкладками на которых игры простые! вот на втором этапе завис и не могу решить проблема в том что мне нужно что бы объекты #rock+i которые я создаю в js появлялись с разной периодичностью и двигались в низ с разной скоростью пробовал многое получалось сделать только так что бы одновременно и с одной и той же скоростью они двигались. Крч получается все кроме задуманного. Пробовал и через массив и через цикл. как можно реализовать такое? подскажите плз! Да и в принципе если есть возможность было бы не плохо получить мнение профессионалов по моему коду, понятно что он кривой но хотелось бы узнать на чем нужно поработать мне! введите сюда описание изображения

var $btn = document.querySelector('#start');
var $game = document.querySelector('#game');
var $time =document.querySelector('#time') ;
var $app = document.querySelector('.wrapper-marks-js');
var $isGame = false;
var $timeHeader = document.querySelector('#time-header');
var $result = document.querySelector('#result-header');
var $resultText = 0;
var $appContent = document.querySelector('#app');
var $appContentId = document.querySelectorAll('.app');


$btn.addEventListener('click',startGame);

function startGame (){
    $timeHeader.classList.remove('hide');
    $result.classList.add('hide');
    $resultText=0;
    document.querySelector('#result').textContent = $resultText;
    $isGame = true;
    if ($isGame && document.querySelector('#game-time').value>0 ){
        var $valueTime = document.querySelector('#game-time').value;
        $time.textContent = $valueTime;
        var interval = setInterval(function (){
            var time = parseFloat($time.textContent);
            if (time<=0){
                clearInterval(interval);
               endGame();
            }else{
                $time.textContent = (time-0.1).toFixed(1); 
            }
        },100);
    
        $btn.classList.add('hide');
        $game.style.backgroundColor = '#fff';
        renderBox();
    }else{
        
    }
}


function renderBox(){
var box = document.createElement('div');
var size = Math.round(Math.random()*(100-20)+20);
var left = Math.round(Math.random()*(207-5)+5);
var top = Math.round(Math.random()*(207-5)+5);

///случайная генерация цвета
function getRandomColor() {
    var letters = '0123456789ABCDEF';
    var color = '#';
    for (var i = 0; i < 6; i++) {
      color += letters[Math.floor(Math.random() * 16)];
    }
    return color;
  }

box.style.height = box.style.width = size+'px';
box.style.position = 'absolute';
box.style.backgroundColor =  getRandomColor();
box.style.top = top+'px';
box.style.left = left+'px';
box.style.cursor = 'pointer';
box.classList.add('box-last');

$game.insertAdjacentElement("afterbegin",box);

if ($isGame){
    box.addEventListener('click',function(){
        box.remove();
        renderBox();
        $resultText+=1;
        document.querySelector('#result').textContent = $resultText;
        });
}else{
    box.remove();
}
}

function endGame(){
    $isGame = false;
    $btn.classList.remove('hide');
    $game.style.backgroundColor = '#ccc';
    $timeHeader.classList.add('hide');
    $result.classList.remove('hide');
    var $hideBox = document.querySelector('.box-last');
    $hideBox.classList.add('hide');
}
 colors=['red','green','blue'];
 colors[0] = '#ff9393' ;
 colors[30] = 'green' ;
 colors[60] = 'blue' ;
 colors[90] = 'red' ;


// вкладки игровые
for (i=0; i<30*4; i+=30){
    var radio = document.createElement('input');
    radio.setAttribute('id','tab-btn-'+i);
    radio.setAttribute('name','tab-btn-radio');
    radio.setAttribute('type','radio');
    radio.classList.add('radio-tbs');
    var mark = document.createElement('label');
    mark.setAttribute('for','tab-btn-'+i);
    mark.classList.add('mark_game');
    mark.style.left = 40+i+'px';
    $app.insertAdjacentElement("afterbegin",mark);
    $app.insertAdjacentElement("afterbegin",radio);
    mark.style.backgroundColor = colors[i];


    radio.addEventListener('change',function(){
        for (i=0; i<4; i++){
            $appContentId[i].classList.remove('show');
        }
       idNumber= this.getAttribute('id');
       var isNumber;
       var Show;
       if (idNumber.length === 9){
        isNumber= idNumber.slice(-2);
        Show= 'app'+isNumber;
       }else if(idNumber.length === 10){
        isNumber= idNumber.slice(-3);
        Show= 'app'+isNumber;
       }
       Show= 'app'+isNumber;
       idContent = document.querySelector('#'+Show);
       idContent.classList.add('show');
       idContent.style.backgroundColor=colors[isNumber.substr(1)];
    });
}


// ---------Game-Two-----------



var $btnTwo = document.querySelector('#start-two');
var $gameTwo = document.querySelector('#game-two');
var $plane = document.querySelector('.plane');

$btnTwo.addEventListener('click',startGameTwo);

function  startGameTwo (){
    $gameTwo.style.backgroundImage = "url('img/space.jpg')";
    $btnTwo.classList.add('hide');
    $rock= document.querySelector('#rock');
    var go = true;
        document.addEventListener('keydown', function manual(event){
            // console.log('Key: ', event.key);
            // console.log('keyCode: ', event.keyCode);
            // console.log( $plane.style.left);
            if(event.keyCode === 65){
                string=$plane.style.left.substring(0, $plane.style.left.length - 1);
                $plane.style.left = Number(string)-2+'%'; 
                if(Number(string)<=4){
                    $plane.style.left = 4+'%';
                }
               console.log(string);
               console.log(go);
            }else if(event.keyCode === 68){
                string=$plane.style.left.substring(0, $plane.style.left.length - 1);
                $plane.style.left = Number(string)+2+'%'; 
                if(Number(string)>=96){
                    $plane.style.left = 96+'%';
                }
            }else if(event.keyCode === 87){
                string=$plane.style.bottom.substring(0, $plane.style.bottom.length - 1);
                $plane.style.bottom = Number(string)+2+'%'; 
                if(Number(string)>=20){
                    $plane.style.bottom = 20+'%';
                }
            }else if(event.keyCode === 83){
                string=$plane.style.bottom.substring(0, $plane.style.bottom.length - 1);
                $plane.style.bottom = Number(string)-2+'%'; 
                if(Number(string)<=0){
                    $plane.style.bottom = 0+'%';
                }
            }
        });
        
        

        

        function rockdrop(){
            time = Math.round(Math.random()*(50-10)+10);

            for (i=0; i<=5; i++){
                var rockSide = Math.round(Math.random()*(94-6)+6);
                $rockItem = document.createElement('img');
                $gameTwo.insertAdjacentElement("afterbegin",$rockItem);
                $rockItem.setAttribute('id',"rock"+i);
                rockId= document.querySelector('#rock'+i);
                rockId.style.bottom = 575+'px';
                rockId.style.left = rockSide+'%';
                $rockItem.setAttribute('src',"./img/rock (1).svg");
                $rockItem.classList.add('rock');
                
            }
            $rockArray=Array.from(document.querySelectorAll('.rock'));

            function dropDown (Item){
                Item.style.bottom = parseInt(Item.style.bottom)-1+'px';
            }
            
            console.log($rockArray);
            function drop (){
                for (var key in $rockArray){
                    
                    setTimeout(setInterval(dropDown($rockArray[key]),time),time);
                        if ( parseInt($rockArray[key].style.bottom) <= -53){
                                                       
                            $rockArray[key].remove();
                                
                        }
                }
            }
        drop();
        }
        rockdrop();
    }
    
    
    // это мои попытки реализовать функцию.
    
    
//                 if(document.getElementsByClassName('rock').length > 0){
//                     $rockArray=Array.from(document.querySelectorAll('.rock'));
                    
//                         for (var key in $rockArray){
//                             function drop (){
                    
//                                 $rockArray[key].style.bottom = parseInt($rockArray[key].style.bottom)-1+'px';
//                                 if ( parseInt($rockArray[key].style.bottom) <= -53){
                                   
//                                     $rockArray[key].remove();
            
//                                 }else{
//                                     // rockdrop(); 
//                                 }
//                             }
//                             setInterval(drop,time);
                        
//                     }
//                         }
    
    
//                 setTimeout(setInterval(drop(),100),500);
                
//                 if (document.getElementsByClassName('rock').length >= 5){
//                     break;
//                 }else if(document.getElementsByClassName('rock').length < 5){  
//                     // for(s=0; s<=5; s++){
//                     //     time = Math.round(Math.random()*(600-500)+500);
//                     //     setInterval(rockdrop(s),time);
//                     //     setTimeout(setInterval(drop,100),time);
//                     // }
//                 }
               
//               iterator+=1;
    

//             }
            
             
                
        
            

   
// }
// var $rockArray = document.querySelectorAll('.rock');
// rockdrop(); 
 /* Animation */

 @keyframes slide {
  from {top: 0;}
  50% {top: -10;}
  to {top: -20;}
  }



  /* ---------------------------- */



* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    outline: none;
  }
  
  body {
    width: 100%;
    height: 100vh;
    display: flex;
    justify-content: center;
    flex-direction: column;
    align-items: center;
    padding: 20px;
    overflow: hidden;
    background: linear-gradient(#CB356B, #BD3F32);
  }
  
  .app {
    display: none;
    position: relative;
    background: #ff9393;
    padding: 20px;
    width: 600px;
    margin: 0 auto;
    box-shadow: 0 1px 30px 0 rgba(0,0,0,0.14), 0 0px 0px 0 rgba(0,0,0,.12), 0 0px 0px 0px rgba(0,0,0,.3);
    border-radius: 10px;
  }
  
  .app__header {
    padding: 20px;
    text-align: center;
  }
  
  .app__content {
    display: flex;
    position: relative;
    align-items: center;
    justify-content: center;
  }
  
  .app__footer {
    padding: 20px;
    display: flex;
    flex-direction: column;
    align-items: center;
  }
  
  .input {
    display: flex;
    flex-direction: column;
    margin-bottom: 10px;
  }
  
  .input label {
    display: inline-block;
    max-width: 100%;
    margin-bottom: 3px;
    color: #acacac;
    font-family: GothamPro,sans-serif;
    font-size: .8rem;
    font-weight: normal;
    line-height: 1.2rem;
  }
  
  .input input {
    display: block;
    width: 100%;
    max-height: 2.8rem;
    padding: .8rem 1rem;
    font-size: inherit;
    font-family: inherit;
    font-weight: inherit;
    line-height: 1.4;
    color: #000;
    background: #fff none;
    border: 1px solid #999;
    border-radius: 5px;
  }
  
  .game {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid #ccc;
    background: #ccc;
    min-height: 300px;
    min-width: 300px;
    max-width: 300px;
    max-height: 300px;
  }
  .game-two{
    position: relative;
    min-height: 575px;
    min-width: 575px;
    max-width: 575px;
    max-height: 575px;
    background: #ccc;
  }
  
  .hide {
    display: none!important;
  }
  
  .btn {
    position: absolute;
    z-index: 1;
    display: inline-block;
    padding: 1rem 2.3rem;
    margin-bottom: 0;
    font-weight: normal;
    font-family: GothamPro, sans-serif;
    font-size: 1rem;
    line-height: 1.3rem;
    text-align: center;
    white-space: nowrap;
    vertical-align: middle;
    touch-action: manipulation;
    cursor: pointer;
    user-select: none;
    background: #fad64e;
    color: #3f3f3f;
    border: none;
    border-radius: 2.8rem;
    transition: transform .2s ease-in-out,box-shadow .2s ease-in-out,-webkit-transform .2s ease-in-out,-webkit-box-shadow .2s ease-in-out;
    will-change: transform;
  }
  
  .btn:disabled {
    background: #ccc!important;
    border: 1px solid #ccc;
    color: #000;
    cursor: not-allowed;
  }
  
  .btn:active {
    transition: transform .1s ease-in-out,box-shadow .1s ease-in-out!important;
    transform: none!important;
    box-shadow: none!important;
  }
  
  .btn:hover {
    box-shadow: 0 6px 16px 0 rgba(0,0,0,.2);
    transform: translateY(-1px);
  }
  .wrapper-marks{
    margin: 0 auto;
    width: 197px;
    height: 50px;
    background: none;
    position: relative;
    /* top: -15px;
    left: 0; */
  }
  
  .mark_game{
    position: absolute;
    top: 0;
    background-color: #000;
    width: 28px;
    height: 50px;
    /* z-index: -1; */
    border-radius: 10px;
  
  }
  .mark_game:hover{
    cursor: pointer;
    top: -20px;
    animation-duration: 0.8s;
    animation-name: slide;
  }
  .mark_game_active{
    top: -20px;
  }
  .wrapper-marks-js{
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: space-around;
    position: absolute;
    top: 31px;
    left: -230px;

  }
  .radio-tbs{
    display: none;
  }
  
  #tab-btn-0:checked~#app-0,
  #tab-btn-30:checked~#app-30,
  #tab-btn-60:checked~#app-60,
  #tab-btn-90:checked~#app-90 {
      display: block;
    }

  input[type="radio"]:checked+label {
    top: -20px;
  }  
  .show {
 display: block;
  }
  #app-30.app {
    width: 850px

  }
  .plane {
    position:absolute;
    bottom: 1%;
    /* left: 50%; */
    width: 50px;
    height: 55px;
    transform: translate(-50%);
  }
  .rock {
    position:absolute;
    bottom: 100%;
    left: 50%;
    width: 50px;
    height: 55px;
    transform: translate(-50%);
  }
  
<!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">
  <link rel="stylesheet" href="styles.css">
  <title>JS Game</title>
</head>
<body>

  <div class="wrapper-marks">
    <div class="wrapper-marks-js">
      <!-- <div class="mark_game"></div> -->
    </div>
   </div>
  <div class="app show" id="app-0">
   
    <div class="app__header">
      <h1 id="time-header">Время игры: <span id="time"></span></h1>
      <h1 id="result-header" class="hide">Ваш результат: <span id="result"></span></h1>
    </div>
    <div class="app__content">
      <button class="btn" id="start">Начать</button>

      <div class="game" id="game"></div>
    </div>
    <div class="app__footer">
      <div class="input">
        <label for="game-time">Время игры, (сек)</label>
        <input type="number" id="game-time" min="5" step="1" value="5">
      </div>
    </div>
  </div>

  <div class="app" id="app-30">
   
    <div class="app__header">
      
    </div>
    <div class="app__content">
      <button class="btn" id="start-two">Начать</button>

      <div class="game-two" id="game-two">
        <img style="left: 50%;" class="plane" id="plane"  src="./img/plane.svg" alt="">
        <!-- <img  class="rock" id="rock"  src="./img/rock (1).svg" alt=""> -->
      </div>
    </div>
    <div class="app__footer">
      <div class="input">
        
      </div>
    </div>
  </div>

  <div class="app" id="app-60">
   
    <div class="app__header">
      <h1 id="time-header">Вкладка 3 <span id="time"></span></h1>
      <h1 id="result-header" class="hide">Ваш результат: <span id="result"></span></h1>
    </div>
    <div class="app__content">
      <button class="btn" id="start">Начать</button>

      <div class="game" id="game"></div>
    </div>
    <div class="app__footer">
      <div class="input">
        <label for="game-time">Время игры, (сек)</label>
        <input type="number" id="game-time" min="5" step="1" value="5">
      </div>
    </div>
  </div>

  <div class="app" id="app-90">
   
    <div class="app__header">
      <h1 id="time-header">вкладка 4 <span id="time"></span></h1>
      <h1 id="result-header" class="hide">Ваш результат: <span id="result"></span></h1>
    </div>
    <div class="app__content">
      <button class="btn" id="start">Начать</button>

      <div class="game" id="game"></div>
    </div>
    <div class="app__footer">
      <div class="input">
        <label for="game-time">Время игры, (сек)</label>
        <input type="number" id="game-time" min="5" step="1" value="5">
      </div>
    </div>
  </div>
  

  <script src="index.js"></script>
</body>
</html>


Ответы (1 шт):

Автор решения: Leonid

Здесь задача появления объектов в разное время решена с помощью функции createItemsToFall(), которая сначала создает все временные отрезки на которых будут появляться новые объекты (исходя из MAX_TIME - время игры и MAX_INTERVAL/MIN_INTERVAL - диапазон временных интервалов появления новых объектов). Затем в цикле объявляется очередная setTimeout(), которая через текущий временной интервал добавит в itemsToFall новый объект.

Уже в функции drawItems() каждому объекту из itemsToFall назначаются новые координаты по Y и, если объект не достиг конца, отрисовывается. В противном случае, объект удаляется из набора (Set).

Вместо анимации на canvas можно так же добавлять абсолютно позиционированные html-элементы с меняющимся атрибутом top.

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const w = canvas.width = 600;
const h = canvas.height = 180;

const MAX_SPEED = 4;
const MIN_SPEED = 1;
const MAX_SIZE = 40;
const MIN_SIZE = 15;
const MAX_TIME = 8000;
const MAX_INTERVAL = 500;
const MIN_INTERVAL = 100;

const itemsToFall = createItemsToFall();

const start = performance.now();
drawItems();

function createItem(){
    return {
        speed : ((Math.random()*(MAX_SPEED-MIN_SPEED)+MIN_SPEED)*10|0)/10,
        x : (Math.random()*(w - MAX_SIZE))|0 + MAX_SIZE/2,
        color : `rgb(${(Math.random()*255)|0},${(Math.random()*255)|0},${(Math.random()*255)|0})`,
        size : (Math.random()*(MAX_SIZE-MIN_SIZE)+MIN_SIZE)|0,
        y : 0
    }
}

function createItemsToFall(){
    const arr = [];
    const set = new Set();
    let time = 0;
    while(time < MAX_TIME){
        time += (Math.random()*(MAX_INTERVAL - MIN_INTERVAL)+MIN_INTERVAL)|0;
        arr.push(time);
    }
    arr.splice(arr.length-1, 1);
    
    arr.forEach(interval => {
        setTimeout(() => {set.add(createItem())},interval);
    })

    return set;
}

function drawItems(){
    ctx.clearRect(0,0,w,h);
    for (let item of itemsToFall){
        item.y += item.speed;
        if(item.y-item.size/2 > h){
            itemsToFall.delete(item);
            continue;
        }
        ctx.fillStyle = item.color;
        ctx.fillRect(item.x-item.size/2,item.y-item.size/2,item.size,item.size);
    }
    let raf = requestAnimationFrame(drawItems);
    if(performance.now() > start + MAX_TIME){
        cancelAnimationFrame(raf);
        ctx.fillRect(0,0,w,h);
        ctx.font = "48px serif";
        ctx.textAlign = 'center';
        ctx.textBaseLine = 'middle';
        ctx.fillStyle = 'white';
        ctx.fillText("Time is over", w/2, h/2);
    }
}
<canvas id="canvas"></canvas>

→ Ссылка