javascript – How to make two opposite traffic lights?

Question:

I have a little problem: I have to make two traffic lights, one for Calle 1 and the other for Calle 2 . Taking into account that while it is green on Calle 1 on Calle 2 it must be red and vice versa.

I have a problem with the time intervals, since they start well but then speed up and almost come together, until the synchronization is lost.

I'll leave the code with HTML and JavaScript :

function start() {
  cambios(1);
  cambios(2);
}
function cambios(id) {

  var cont = 1;
  var bomb1;
  bomb1 = document.getElementById("bombillo"+id);

  function colorRojo() {
    if (cont == 1) {

      bomb1.style.background = "red";
      cont += 2;
    }
  }

  window.setInterval(colorRojo, 5000);
  window.clearInterval(colorRojo);

  function colorAmarillo() {

    if (cont == 2) {
      bomb1.style.background = "yellow";
      cont -= 1;
    }
  }
  window.setInterval(colorAmarillo, 5000);
  window.clearInterval(colorAmarillo);

  function colorVerde() {

    if (cont == 3) {

      bomb1.style.background = "green";
      cont -= 1;
    }
  }

  window.setInterval(colorVerde, 7000);
  window.clearInterval(colorVerde);
}
start()
#bombillo1 {
	height: 150px;
	width: 150px;
	background-color: green;
	border-radius: 50%;
	margin: 25px auto;
	transition: background 300ms;
}
#bombillo2 {
	height: 150px;
	width: 150px;
	background-color: red;
	border-radius: 50%;
	margin: 25px auto;
	transition: background 300ms;
}
<!-- jQuery usado como dependencia de Bootstrap -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<!-- Latest compiled and minified CSS -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" >

<!-- Optional theme -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" >

<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" ></script>

<div class="bombillo" id="bombillo1"></div>
<div class="bombillo" id="bombillo2"></div>

<div class="btn-group"></div>

Answer:

I recommend doing this via requestAnimationFrame as it has many advantages over setInterval/setTimeout . Some advantages are a better FPS, it uses less CPU and battery, among others. Also, if you want synchronization between both semaphores you must implement the Observer pattern (also called pub / sub).

Example

class Observer {

  constructor() {
    this.observers = [];
  }

  subscribe(semaphore) {
    semaphore.observers.push(this);
  }

  publish() {
    this.observers.forEach(ob => ob.semaphoreChange(this.currentLight));
  }
}

class Semaphore extends Observer {

  constructor(id) {
    super();
    this.$el = document.querySelector(id);
    this.count = 0;
    this.rafid = 0;
    this.currentLight = '';
    this.breakpoints = {
      green: 5,
      red: 8,
      ambar: 10
    };
  }

  start() {
    this.rafid = requestAnimationFrame(this.loop.bind(this));
  }

  stop() {
    cancelAnimationFrame(this.rafid);
  }

  loop(currTime) {
    let time = Math.round(currTime / 1000) - this.count;

    if (time < this.breakpoints.green &&
      this.currentLight !== 'green') {
      this.currentLight = 'green';
      this.updateColor();
      this.publish();
    }

    if (time >= this.breakpoints.green &&
      time < this.breakpoints.red &&
      this.currentLight !== 'red') {
      this.currentLight = 'red';
      this.updateColor();
      this.publish();
    }

    if (time >= this.breakpoints.red &&
      time < this.breakpoints.ambar &&
      this.currentLight !== 'ambar') {
      this.currentLight = 'ambar';
      this.updateColor();
      this.publish();
    }

    if (time >= this.breakpoints.ambar) {
      this.count += time;
    }

    this.rafid = requestAnimationFrame(this.loop.bind(this));
  }

  updateColor() {
    this.$el.className = `semaphore ${this.currentLight}`;
  }

  semaphoreChange(color) {
    if (color === 'green') {
      this.$el.className = 'semaphore red';
    } else if (color === 'red') {
      this.$el.className = 'semaphore green';
    } else {
      this.$el.className = `semaphore ${color}`;
    }
  }
}

let semaphore1 = new Semaphore('#s1');
let semaphore2 = new Semaphore('#s2');

semaphore2.subscribe(semaphore1);
semaphore1.start();
@import url('https://fonts.googleapis.com/css?family=Oswald');


.container {
  height: 100vh;
  width: 100%;
}

.street {
  display: inline-block;
  height: 100%;
  margin-right: -4px;
  width: 50%;
}

h1 {
  color: #555;
  font-family: 'Oswald';
  margin: 20px 0;
  text-align: center;
}

.semaphore {
  border-radius: 100%;
  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .23);
  height: 150px;
  margin: 50px auto;
  width: 150px;
}

.semaphore.green {
  background-color: #2ecc71;
}

.semaphore.red {
  background-color: #e74c3c;
}

.semaphore.ambar {
  background-color: #f39c12;
}
<div class="container">
  <section class="street street-1">
    <h1>Street 1</h1>
    <div id="s1" class="semaphore"></div>
  </section>
  <section class="street street-2">
    <h1>Street 2</h1>
    <div id="s2" class="semaphore"></div>
  </section>
</div>

The Observer class only Observer implements the Observer pattern, defining the subscribe methods to subscribe to another semaphore and the publish method to notify the "observer" semaphores when a light change has occurred.

The Semaphore class, as you can see, has nothing out of the ordinary. The property breakpoints are the "breakpoints" at which the semaphore must change. In this example, green will last for 5, red will last for 3, and amber will last for 2.

Scroll to Top