TABLE OF CONTENTS

Hi 🤓 Cảm ơn bạn đã ghé thăm blog này, nếu những bài viết trên blog giúp ích cho bạn. Bạn có thể giúp blog hiển thị quảng cáo bằng cách tạm ngừng ad blocker 😫 và để giúp blog duy trì hoạt động nếu bạn muốn.
Cảm ơn bạn!

Hi các bạn, trong bài viết này, mình xin chia sẻ cách tạo trafic light - cột đèn giao thông đơn giản, chúng ta sẽ mô phỏng thời gian đếm lùi của đèn xanh, đèn đỏ, đèn vàng như trong thực tế nhé. Project này sẽ giúp các bạn luyện JavaScript, cụ thể là thao tác DOM.

Các bạn có thể xem cách hoạt động của đèn giao thông, mình đã code và đăng lên kênh youtube và làm thử trước. Sau đó, có thể đọc bài viết hướng dẫn của mình nhé! mỗi người có một cách làm khác nhau, cái quan trọng là tự chúng ta code và tìm hướng giải quyết, nếu có cách làm gọn hơn, hãy cùng thảo luận bên dưới nhé! Cùng bắt tay vào làm thôi 😁.

HTML

Đầu tiên chúng ta cần design đèn giao thông - traffic light với HTML, CSS. Bạn có thể xem qua code HTML dưới đây:

<div class="main">
  <div class="traffic-light">
    <div class="signals">
      <!-- đèn đỏ -->
      <div class="red-light"></div>
      <!-- đèn vàng -->
      <div class="yellow-light"></div>
      <!-- đèn xanh -->
      <div class="green-light"></div>
      <!-- thời gian chuyển đèn -->
      <div class="timer">15</div>
    </div>
    <!-- cái cột đèn - tạo thêm cho đỡ trống trải 😁  -->
    <div class="pole"></div>
  </div>
</div>

CSS

Các bạn có thể sử dụng SASS/SCSS để giúp style dễ hơn, dưới đây là một chút CSS được compile từ SASS/SCSS, các bạn có thể tải source về để theo dõi dễ hơn.

.main {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
}

.traffic-light {
  display: flex;
  align-items: center;
  flex-direction: column;
}
.traffic-light .signals {
  width: 180px;
  display: flex;
  align-items: center;
  flex-direction: column;
  background-color: rgb(83, 80, 81);
  padding: 10px;
  border-radius: 20px;
  position: relative;
}
.traffic-light .signals > div {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  margin: 10px 0;
  background-color: rgba(0, 0, 0, 0.3);
}
.traffic-light .signals .red-light.active {
  background-color: rgb(233, 84, 73);
}
.traffic-light .signals .yellow-light.active {
  background-color: rgb(237, 202, 74);
}
.traffic-light .signals .green-light.active {
  background-color: rgb(190, 209, 128);
}
.traffic-light .signals .timer {
  background-color: transparent;
  border-top: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 0;
  padding: 10px 0;
  color: #fff;
  height: auto;
  width: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 28px;
  font-family: Impact, Haettenschweiler, "Arial Narrow Bold", sans-serif;
}
.traffic-light .pole {
  width: 20px;
  height: 180px;
  background-color: rgb(66, 60, 62);
}

Kết quả chúng ta được như hình bên dưới.

traffic Light using HTML, CSS, JAVASCRIPT

JavaScript

Ý tưởng để code đèn giao thông khá đơn giản, đầu tiên sử dụng JavaScript để tạo các element như đèn xanh, đỏ, vàng, và một element là thời gian đếm ngược timer. element timer có nhiệm vụ hiển thị thời gian đếm ngược tương ứng với từng đèn. Sau đó đến element nào thì thêm một class để bật đèn đó lên, nó khá giống với cách làm slide thông thường 💦.

Đoạn code HTML ở trên giúp chúng ta định hình được các element sẽ tạo với JavaScript. Trong bài này, chúng ta sẽ sử dụng JavaScript để render ra HTML. Bước tiếp theo, các bạn có thể comment đoạn HTML lại và tạo element bằng JavaScript sao cho giống với HTML vừa comment.

Để triển khai ý tưởng trên, trước tiên mình sẽ tạo một mảng chứa 3 phần tử đèn xanh, đỏ, vàng cùng thời gian hiển thị cho mỗi đèn.

const trafficLightDefaultValue = [
  { signal: "red-light", timer: 15 },
  { signal: "yellow-light", timer: 3 },
  { signal: "green-light", timer: 15 },
];

Ở trên, signal sẽ là class cho từng element đèn, những class này chúng ta đã style ở CSS. timer là thời gian hiển thị mỗi đèn.

Vì ý tưởng là tạo element với JavaScript nên mình làm một bước nữa là tạo element cho từng phần tử đèn.

const trafficLightValue = trafficLightDefaultValue.map((value) => ({
  ...value,
  element: createElement("div", {
    className: value.signal,
  }),
}));

Ở đoạn code trên, mình đã tạo một hàm utilcreateElement(), hàm này chỉ đơn giản là tạo element bằng một trick sử dụng document.createElement().

// utils
function createElement(tagName, properties) {
  return Object.assign(document.createElement(tagName), properties);
}

Các kiến thức mình áp dụng để tạo trafficLightValue:

  1. Arrow Function JavaScript có gì hay?
  2. Spread Operator Javascript là gì?
  3. Object.assign() trong JavaScript sử dụng với mục đích gì?

Tiếp theo, phần mà chúng ta sẽ tạo bằng HTML là toàn bộ element traffic-light, các bạn có thể empty nội dung của div main bằng cách:

const mainEle = document.querySelector(".main");
mainEle.textContent = null;

Như vậy phần JavaScript của chúng ta lúc này sẽ như sau:

const trafficLightDefaultValue = [
  { signal: "red-light", timer: 15 },
  { signal: "yellow-light", timer: 3 },
  { signal: "green-light", timer: 15 },
];

const trafficLightValue = trafficLightDefaultValue.map((value) => ({
  ...value,
  element: createElement("div", {
    className: value.signal,
  }),
}));

const mainEle = document.querySelector(".main");
mainEle.textContent = null;

// utils
function createElement(tagName, properties) {
  return Object.assign(document.createElement(tagName), properties);
}

Bước kế tiếp, chúng ta tạo các element sao cho giống với cấu trúc đã tạo bằng HTML, cùng theo dõi tiếp nhé:

// tạo khung chứa đèn giao thông
const trafficLightEle = createElement("div", { className: "traffic-light" });
// tạo phần tử chứa các đèn
const signalsEle = createElement("div", { className: "signals" });
// thời gian hiển thị
const timerEle = createElement("div", { className: "timer" });

signalsEle.append(...trafficLightValue.map((value) => value.element));
signalsEle.appendChild(timerEle);

trafficLightEle.appendChild(signalsEle);
trafficLightEle.insertAdjacentHTML("beforeend", `<div class="pole"></div>`);

Các kiến thức ở phần code này:

  1. Khác nhau giữa append() vs appendChild() JavaScript bạn cần biết
  2. Element.insertAdjacentHTML() trong JavaScript bạn đã biết cách dùng?

Sao khi đã tạo các element với JavaScript, lúc này các element vẫn chưa xuất hiện ở DOM, các bạn có thể dùng append(), appendChild(), hoặc mình sử dụng insertAdjacentElement() như bên dưới:

mainEle.insertAdjacentElement("afterbegin", trafficLightEle);

Phần code JavaScript lúc này của chúng ta sẽ như sau:

const trafficLightDefaultValue = [
  { signal: "red-light", timer: 15 },
  { signal: "yellow-light", timer: 3 },
  { signal: "green-light", timer: 15 },
];

const trafficLightValue = trafficLightDefaultValue.map((value) => ({
  ...value,
  element: createElement("div", {
    className: value.signal,
  }),
}));

const mainEle = document.querySelector(".main");
mainEle.textContent = null;

const trafficLightEle = createElement("div", { className: "traffic-light" });
const signalsEle = createElement("div", { className: "signals" });
const timerEle = createElement("div", { className: "timer" });

signalsEle.append(...trafficLightValue.map((value) => value.element));
signalsEle.appendChild(timerEle);

trafficLightEle.appendChild(signalsEle);
trafficLightEle.insertAdjacentHTML("beforeend", `<div class="pole"></div>`);

mainEle.insertAdjacentElement("afterbegin", trafficLightEle);

Sau khi cột đèn 🚦 đã hiện ở DOM, chúng ta sẽ tiến hành code phần thời gian đếm ngược, đây có thể nói là phần tốn thời gian kha khá. Cùng theo dõi lại hình ảnh 🚦 lần nữa.

traffic Light using HTML, CSS, JAVASCRIPT

Chúng ta có thể thấy ở trên, thứ tự đèn là đỏ -> vàng -> xanh. Như vậy nếu code bình thường thì đèn đỏ sẽ nhảy sang đèn vàng, rồi đến đèn xanh. Không đúng thực tế 👮‍♂️.

Cho nên chúng ta sẽ làm cho đèn xanh -> vàng -> đỏ, thứ tự đèn ở trên cũng giống như ở mảng chúng ta đã tạo. Như vậy lúc này chỉ cần lấy phần tử trong mảng theo thứ tự ngược lại, như sau:

  • Đèn xanh chạy trước, nên index sẽ ở cuối mảng.
  • Cho thời gian lùi đến khi hết thì đèn tiếp theo sẽ hiện ra, sử dụng class active đã style ở CSS
if (timerEle.isConnected) {
  let index = trafficLightValue.length - 1;
  let currentTime = trafficLightValue[index].timer;

  // đèn xanh - green light hiện trước vì index khởi tạo là index ở cuối mảng
  trafficLightValue[index].element.classList.add("active");
  // hiện thời gian tương ứng với đèn
  timerEle.innerText = currentTime;

  setInterval(() => {
    if (currentTime > 0) {
      currentTime--;
    } else {
      if (index > 0) index--;
      else index = trafficLightValue.length - 1;

      currentTime = trafficLightValue[index].timer;

      // tắt các đèn đi
      trafficLightValue.forEach((value) => {
        value.element.classList.remove("active");
      });

      // hiển thị đèn phù hợp
      trafficLightValue[index].element.classList.add("active");
    }

    if (currentTime > 0) timerEle.innerText = currentTime;
  }, 1000);
}

Ở đoạn code trên, các bạn có thể thêm một bước kiểm tra xem timerEle đã hiện trên DOM chưa, bằng cách kiểm tra timerEle.isConnected, giá trị của nó là true nếu đã connect với DOM và ngược lại.

Sau đây là toàn bộ phần code JavaScript:

const trafficLightDefaultValue = [
  { signal: "red-light", timer: 15 },
  { signal: "yellow-light", timer: 3 },
  { signal: "green-light", timer: 15 },
];

const trafficLightValue = trafficLightDefaultValue.map((value) => ({
  ...value,
  element: createElement("div", {
    className: value.signal,
  }),
}));

const mainEle = document.querySelector(".main");
mainEle.textContent = null;

const trafficLightEle = createElement("div", { className: "traffic-light" });
const signalsEle = createElement("div", { className: "signals" });
const timerEle = createElement("div", { className: "timer" });

signalsEle.append(...trafficLightValue.map((value) => value.element));
signalsEle.appendChild(timerEle);

trafficLightEle.appendChild(signalsEle);
trafficLightEle.insertAdjacentHTML("beforeend", `<div class="pole"></div>`);

mainEle.insertAdjacentElement("afterbegin", trafficLightEle);

if (timerEle.isConnected) {
  let index = trafficLightValue.length - 1;
  let currentTime = trafficLightValue[index].timer;

  trafficLightValue[index].element.classList.add("active");
  timerEle.innerText = currentTime;

  setInterval(() => {
    if (currentTime > 0) {
      currentTime--;
    } else {
      if (index > 0) index--;
      else index = trafficLightValue.length - 1;

      currentTime = trafficLightValue[index].timer;

      trafficLightValue.forEach((value) => {
        value.element.classList.remove("active");
      });

      trafficLightValue[index].element.classList.add("active");
    }

    if (currentTime > 0) timerEle.innerText = currentTime;
  }, 1000);
}

// utils
function createElement(tagName, properties) {
  return Object.assign(document.createElement(tagName), properties);
}

À 😵, còn tại sao hàm createElement() mình đặt dưới cùng lại có thể sử dụng ở trên như bình thường thì các bạn có thể đọc bài viết này nhé.


Như vậy là mình đã hướng dẫn xong phần code đèn 🚥. Nếu có thắc mắc các bạn có thể để lại bình luận bên dưới nhé.

Chúc các bạn học tốt!

Có thể bạn thích ⚡
homiedev
About Me

Hi, I'm @devnav. Một người thích chia sẻ kiến thức, đặc biệt là về Frontend 🚀. Trang web này được tạo ra nhằm giúp các bạn học Frontend hiệu quả hơn 🎉😄.

Chúc các bạn tìm được kiến thức hữu ích trong blog này 😁😁.