setTimeout и действия в зависимости от его величины

Кнопки red, blue, green по нажатию меняют цвет соответствующих дивов 01, 02, 03 на указанный. Кнопкой ms (для удобства) выставляю время (задержку) для этих трёх дивов. Кнопка start через интервал (setTimeout) меняет цвет этих дивов с штатного beige на orange.


Хочу, чтобы после нажатия последних двух кнопок, цвет дива можно было изменить кнопками red, blue, green следующим образом:

  • если нажал на кнопку green через 6 секунд, то цвет сменится на зеленый сразу, т.к. задержка в 3 секунды прошла; - если же так нажать на кнопку red, то цвет на красный должен будет смениться через 2 секунды, т.к. у данного дива задержка была выставлена в 8 секунд; - с кнопкой blue аналогично red, но через 6 секунд.

function f01(d,s) {
    document.getElementById(d).style.background = s;
}
function f02() {
    let doc = document.getElementsByClassName('s');
    doc[0].value = 8000;
    doc[1].value = 12000;
    doc[2].value = 3000;
}
function start() {
    setTimeout(f03, document.getElementsByClassName('s')[0].value); // 8000  RED
    setTimeout(f04, document.getElementsByClassName('s')[1].value); // 12000 BLUE
    setTimeout(f05, document.getElementsByClassName('s')[2].value); // 3000  GREEN
    f01('d01','beige');
    f01('d02','beige');
    f01('d03','beige');  
}
function f03() {
    document.getElementById('d01').style.background = 'orange';
}
function f04() {
    document.getElementById('d02').style.background = 'orange';
}
function f05() {
    document.getElementById('d03').style.background = 'orange';
}
#d01, #d02, #d03 {width: 122px; height: 40px; background: beige;}
.s {width: 50px; height: 20px; font-size: 12px;}
.test {width: 60px; height: 26px; background: #CCCCCC;}
<button class="test" onclick="f02()">ms</button>
<br><br>
<div id="d01">01</div>
<input class="s" type="number">
<button class="test" onclick="f01('d01','red')">RED</button>
<br><br>
<div id="d02">02</div>
<input class="s" type="number">
<button class="test" onclick="f01('d02','lightblue')">BLUE</button>
<br><br>
<div id="d03">03</div>
<input class="s" type="number">
<button class="test" onclick="f01('d03','green')">GREEN</button>
<br><br>
<button class="test" onclick="start()">start</button>

Как правильно делать и возможно ли иметь некий общий функционал, чтобы подставлять интервалы из разных мест проекта. Спасибо.


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

Автор решения: OPTIMUS PRIME

Возможно ли иметь некий общий функционал...

Первым делом, выкинуть нумерованные id (и id в целом), а работать с одинаковыми элементами-"карточками", через перебор. Всё наполнение и нужные цвета - собрать в одном массиве.

По таймерам. Кликнули start:

  • Сохранить во внешней переменной время new Date()
  • Для каждого блока запустился свой таймер,
  • Когда дотикает, проверит: На кнопку кликали ? выставит заготовленный цвет, иначе - оранжевый.

С другой стороны, кнопке для краски остается только при клике выставить btn_clicked = true своему объекту. И дополнительно сравнить прошедшее от старта время. Если прошло больше, чем timeout - сразу покрасить блок.

let data = [
  {
    timeout: 2000,   // Через сколько миллисекунд перекрасить блок
    color: "#c00",   // в какой цвет (если на него кликнут)
    btn_name: "RED", // Текст самой кнопки при создании HTML
    btn_clicked: false,
  },
  {
    timeout: 8000,
    color: "#090",
    btn_name: "GREEN",
    btn_clicked: false,
  },
  {
    timeout: 3000,
    color: "#169",
    btn_name: "BLUE",
    btn_clicked: false,
  },
];

insert_html();
set_event_listeners();

/***/
function set_event_listeners() {
  let STARTING_TIME = null;
  
  onclick_start();
  onclick_manage_color();

  function onclick_start() {
    let box = document.querySelectorAll("#main .box"); // закрашиваются
    let input = document.querySelectorAll("#main .inp"); // туда вводят таймаут в секундах

    /***/    
    let start_btn = document.getElementById("start");

    let units_completed = 0;
    // считает количество закрашенных блоков, чтобы по завершению что-то сделать. 

    start_btn.addEventListener("click", function () {
      this.disabled = true;

      update_data();

      STARTING_TIME = new Date();

      for (let i = 0; i < data.length; i++) run_timeout(i);
    });

    /***/
    function update_data() {
      for (let i = 0; i < data.length; i++) {
        data[i].timeout = input[i].value * 1000 || 0;
        data[i].btn_clicked = false;
      }
    }

    function run_timeout(i) {
      let unit = data[i];
      /* unit = { timeout: 8000, color: "#c00", btn_name: "RED", btn_clicked: false } */

      setTimeout(function() {

        box[i].style.backgroundColor = unit.btn_clicked ? unit.color : "orange";

        if (++units_completed >= data.length) {
          start_btn.disabled = false;
        }

      }, unit.timeout);
    }
  }

  function onclick_manage_color() {    
    let btn = document.querySelectorAll("#main .btn");
    let box = document.querySelectorAll("#main .box");    
    let units = document.querySelectorAll("#main .unit");

    for (let i = 0; i < btn.length; i++) {
      btn[i].addEventListener("click", manage_color);
    }

    function manage_color() {
      // this === btn[i]

      let unit_elem = this.closest(".unit"); // элемент, в котором лежит кнопка
      let index = Array.prototype.indexOf.call(units, unit_elem);
      // его номер среди всех остальных блоков (оно же, `i`)
      // под этим же номером хранятся все данные нужного блока в data

      let unit = data[index];

      unit.btn_clicked = true;
      /*** При нажатии start, уже запущенный таймер, если обнаружит btn_clicked == true
       уже сам выставит unit.color вместо orange ***/

      if (unit.timeout < new Date() - STARTING_TIME) {
        // Если уже прошло больше сремени, сразу выставится цвет.
        box[index].style.backgroundColor = unit.color;
      }
    }
  }
}

function insert_html() {
  let html = "";

  for (let i = 0; i < data.length; i++) {
    let unit = data[i];

    html += `
      <div class="unit">
        <div class="box">${ i + 1 }</div>
        <input class="inp" type="number" value="${ unit.timeout / 1000 }">
        <button class="btn">${ unit.btn_name }</button>
      </div>
    `;
  }  
  
  document.getElementById("main").innerHTML = html;
}
.box {
  width: 100px;
  height: 40px;
  background: beige;
}

.inp {
  width: 30px;
  height: 20px;
}

.btn {
  width: 60px;
  height: 26px;
  background: #CCCCCC;
}

.unit {
  display: inline-block;
  margin: 5px;
  padding: 5px;
  box-shadow: 1px 1px 5px #000;
}
<div id="main"></div>

<button id="start">start</button>

→ Ссылка