Hướng dẫn cách đưa modal box vào html

Trong bài viết lần này, mình sẽ hướng dẫn sơ lượt các bạn về cách xây dựng component modal gần giống với Bootstrap 4. Chủ đích của bài này sẽ giúp bạn hiểu thêm về cách sử dụng javascript tương tác với html. Ngoài ra cũng giúp bạn luyện thêm kỹ năng kết hợp bộ 3 html/css/js.

Lưu ý rằng: Bài viết này chỉ hướng dẫn bạn cấu trúc xây dựng Modal và có tính tái sử dụng nó chứ không có thiên hướng xây dựng CSS đẹp mắt.

Xây dựng cấu trúc HTML

Đầu tiên, chúng ta cần phải xây dựng cấu trúc


0 trước khi triển khai đến CSS / JavaScript.

Chúng ta sẽ bắt đầu từ button bật


1. Hình thức xây dựng theo để tái sử dụng mã JavaScript, chúng ta sẽ không viết một đoạn mã cho duy nhất một Modal, thay vào đó sẽ xây dựng cấu trúc mã để tái sử dụng cho


2 Modal mà không phải lặp lại mã JavaScript.

Kết hợp data-* và aria-* chúng ta có thể truy xuất được modal và trạng thái bật tắt của nó.

  • data-trigger='modal' : Đoạn này có nghĩa giúp bạn định danh được thẻ

    3 nào dùng để truy xuất, tương tác với Modal
  • data-target='id_or_class_selector': Đoạn này sẽ là thuộc tính ID hoặc class cho phép chúng ta truy xuất đến Modal đó.
  • aria-open='false': Đây là trạng thái bật tắt mặc định của Modal.


Xem như đã hoàn thành xong button để bật modal lên, bây giờ chúng ta cần Modal được taget thông qua thuộc tính


4.


Bây giờ, hãy cùng thêm một chút CSS (Mình dùng SCSS, cách viết nested css) cho ra dáng Modal chút.

  • { box-sizing: border-box; padding: 0; margin: 0; } body { line-height: 1.5; } .modal { position: fixed; width: 100%; height: 100%; top: 0; left: 0; z-index: 10; visibility: hidden; opacity: 0; transition: all 300ms linear; .modal__backdrop { position: absolute; top: 0; left: 0; background-color: rgba(0, 0, 0, 0.8); width: 100%; height: 100%; z-index: -1; } .modal__content { transition: all 300ms ease-in-out; width: 30%; margin: auto; z-index: 10; position: absolute; top: -100%; left: 50%; transform: translateX(-50%); background: white; padding: 15px 30px; border-radius: 5px; .modal__header { border-bottom: 1px solid gray; margin-bottom: 15px; } .modal__main { margin-bottom: 15px; } .modal__footer { display: flex; align-items: center; justify-content: flex-end; & > * { margin-left: 15px; cursor: pointer; } } } &--active { opacity: 1; visibility: visible; .modal__content { top: 25%; } } }

  • 5: Modal khi bật lên, sẽ ngăn chặn người dùng tương tác với nội dung bên dưới, vì vậy chúng ta phải cho modal có thuộc tính

    6 để đè lên nội dung bên dưới
  • 7: Làm nhiệm vụ tạo một lớp backdrop mờ đục, che mờ đi phần bên dưới để tạo điểm tập trung cho nội dung hiển thị trên Modal và ngăn chặn việc người dùng tương tác được với nội dung phía dưới
  • 8: Modal thường sẽ nằm giữa trang, vì vậy ta cần thêm

    9 để canh giữa Modal, ngoài ra khi hiển thị cũng cần một chút hiệu ứng nên ta sẽ kết hợp thêm

    `
  • {

    box-sizing: border-box; padding: 0; margin: 0; } body { line-height: 1.5; } .modal { position: fixed; width: 100%; height: 100%; top: 0; left: 0; z-index: 10; visibility: hidden; opacity: 0; transition: all 300ms linear; .modal__backdrop {

    position: absolute;  
    top: 0;  
    left: 0;  
    background-color: rgba(0, 0, 0, 0.8);  
    width: 100%;  
    height: 100%;  
    z-index: -1;  
    
    } .modal__content {
    transition: all 300ms ease-in-out;  
    width: 30%;  
    margin: auto;  
    z-index: 10;  
    position: absolute;  
    top: -100%;  
    left: 50%;  
    transform: translateX(-50%);  
    background: white;  
    padding: 15px 30px;  
    border-radius: 5px;  
    .modal__header {  
      border-bottom: 1px solid gray;  
      margin-bottom: 15px;  
    }  
    .modal__main {  
      margin-bottom: 15px;  
    }  
    .modal__footer {  
      display: flex;  
      align-items: center;  
      justify-content: flex-end;  
      & > * {  
        margin-left: 15px;  
        cursor: pointer;  
      }  
    }  
    
    } &--active {
    opacity: 1;  
    visibility: visible;  
    .modal__content {  
      top: 25%;  
    }  
    
    } }

    ` 0 để tạo cảm giác không bị cứng.
  • `
  • {

    box-sizing: border-box; padding: 0; margin: 0; } body { line-height: 1.5; } .modal { position: fixed; width: 100%; height: 100%; top: 0; left: 0; z-index: 10; visibility: hidden; opacity: 0; transition: all 300ms linear; .modal__backdrop {

    position: absolute;  
    top: 0;  
    left: 0;  
    background-color: rgba(0, 0, 0, 0.8);  
    width: 100%;  
    height: 100%;  
    z-index: -1;  
    
    } .modal__content {
    transition: all 300ms ease-in-out;  
    width: 30%;  
    margin: auto;  
    z-index: 10;  
    position: absolute;  
    top: -100%;  
    left: 50%;  
    transform: translateX(-50%);  
    background: white;  
    padding: 15px 30px;  
    border-radius: 5px;  
    .modal__header {  
      border-bottom: 1px solid gray;  
      margin-bottom: 15px;  
    }  
    .modal__main {  
      margin-bottom: 15px;  
    }  
    .modal__footer {  
      display: flex;  
      align-items: center;  
      justify-content: flex-end;  
      & > * {  
        margin-left: 15px;  
        cursor: pointer;  
      }  
    }  
    
    } &--active {
    opacity: 1;  
    visibility: visible;  
    .modal__content {  
      top: 25%;  
    }  
    
    } }

    ` 1: Chúng ta sẽ dùng nó để kích hoạt trạng thái bật / tắt của Modal.
  • Các phần css còn lại bạn có thể tuỳ ý xây dựng để giúp nó đẹp hơn

Thêm JavaScript để xử lý trạng thái Modal

Dựa theo mẫu HTML bên dưới, chúng ta sẽ dùng

  • { box-sizing: border-box; padding: 0; margin: 0; } body { line-height: 1.5; } .modal { position: fixed; width: 100%; height: 100%; top: 0; left: 0; z-index: 10; visibility: hidden; opacity: 0; transition: all 300ms linear; .modal__backdrop { position: absolute; top: 0; left: 0; background-color: rgba(0, 0, 0, 0.8); width: 100%; height: 100%; z-index: -1; } .modal__content { transition: all 300ms ease-in-out; width: 30%; margin: auto; z-index: 10; position: absolute; top: -100%; left: 50%; transform: translateX(-50%); background: white; padding: 15px 30px; border-radius: 5px; .modal__header { border-bottom: 1px solid gray; margin-bottom: 15px; } .modal__main { margin-bottom: 15px; } .modal__footer { display: flex; align-items: center; justify-content: flex-end; & > * { margin-left: 15px; cursor: pointer; } } } &--active { opacity: 1; visibility: visible; .modal__content { top: 25%; } } }

2 để truy vấn những thẻ có thuộc tính data-trigger='modal'


Tương ứng, trong JavaScript, chúng ta có

// data-trigger: "modal" => Dựa vào trigger selector tất cả những thẻ nào có data-trigger='modal'  
// data-target: Mỗi một data-trigger sẽ select đến một bộ modal riêng (Phù hợp cho việc tái sử dụng)  
// aria-open: Khai báo trạng thái modal  
const btnModals = document.querySelectorAll("[data-trigger='modal']");

Sau khi đã lấy được nhóm các nút

  • { box-sizing: border-box; padding: 0; margin: 0; } body { line-height: 1.5; } .modal { position: fixed; width: 100%; height: 100%; top: 0; left: 0; z-index: 10; visibility: hidden; opacity: 0; transition: all 300ms linear; .modal__backdrop { position: absolute; top: 0; left: 0; background-color: rgba(0, 0, 0, 0.8); width: 100%; height: 100%; z-index: -1; } .modal__content { transition: all 300ms ease-in-out; width: 30%; margin: auto; z-index: 10; position: absolute; top: -100%; left: 50%; transform: translateX(-50%); background: white; padding: 15px 30px; border-radius: 5px; .modal__header { border-bottom: 1px solid gray; margin-bottom: 15px; } .modal__main { margin-bottom: 15px; } .modal__footer { display: flex; align-items: center; justify-content: flex-end; & > * { margin-left: 15px; cursor: pointer; } } } &--active { opacity: 1; visibility: visible; .modal__content { top: 25%; } } }

3, tiếp theo chúng ta cần lặp qua mảng NodeList đó

btnModals.forEach(btnModal => {})

Việc cần làm tiếp theo

  • Gán sự kiện `
  • {

    box-sizing: border-box; padding: 0; margin: 0; } body { line-height: 1.5; } .modal { position: fixed; width: 100%; height: 100%; top: 0; left: 0; z-index: 10; visibility: hidden; opacity: 0; transition: all 300ms linear; .modal__backdrop {

    position: absolute;  
    top: 0;  
    left: 0;  
    background-color: rgba(0, 0, 0, 0.8);  
    width: 100%;  
    height: 100%;  
    z-index: -1;  
    
    } .modal__content {
    transition: all 300ms ease-in-out;  
    width: 30%;  
    margin: auto;  
    z-index: 10;  
    position: absolute;  
    top: -100%;  
    left: 50%;  
    transform: translateX(-50%);  
    background: white;  
    padding: 15px 30px;  
    border-radius: 5px;  
    .modal__header {  
      border-bottom: 1px solid gray;  
      margin-bottom: 15px;  
    }  
    .modal__main {  
      margin-bottom: 15px;  
    }  
    .modal__footer {  
      display: flex;  
      align-items: center;  
      justify-content: flex-end;  
      & > * {  
        margin-left: 15px;  
        cursor: pointer;  
      }  
    }  
    
    } &--active {
    opacity: 1;  
    visibility: visible;  
    .modal__content {  
      top: 25%;  
    }  
    
    } }

    4 thông qua thuộc tính

  • {

    box-sizing: border-box; padding: 0; margin: 0; } body { line-height: 1.5; } .modal { position: fixed; width: 100%; height: 100%; top: 0; left: 0; z-index: 10; visibility: hidden; opacity: 0; transition: all 300ms linear; .modal__backdrop {

    position: absolute;  
    top: 0;  
    left: 0;  
    background-color: rgba(0, 0, 0, 0.8);  
    width: 100%;  
    height: 100%;  
    z-index: -1;  
    
    } .modal__content {
    transition: all 300ms ease-in-out;  
    width: 30%;  
    margin: auto;  
    z-index: 10;  
    position: absolute;  
    top: -100%;  
    left: 50%;  
    transform: translateX(-50%);  
    background: white;  
    padding: 15px 30px;  
    border-radius: 5px;  
    .modal__header {  
      border-bottom: 1px solid gray;  
      margin-bottom: 15px;  
    }  
    .modal__main {  
      margin-bottom: 15px;  
    }  
    .modal__footer {  
      display: flex;  
      align-items: center;  
      justify-content: flex-end;  
      & > * {  
        margin-left: 15px;  
        cursor: pointer;  
      }  
    }  
    
    } &--active {
    opacity: 1;  
    visibility: visible;  
    .modal__content {  
      top: 25%;  
    }  
    
    } }

    ` 5 cho các nút.
  • Selector đến Modal qua `
  • {

    box-sizing: border-box; padding: 0; margin: 0; } body { line-height: 1.5; } .modal { position: fixed; width: 100%; height: 100%; top: 0; left: 0; z-index: 10; visibility: hidden; opacity: 0; transition: all 300ms linear; .modal__backdrop {

    position: absolute;  
    top: 0;  
    left: 0;  
    background-color: rgba(0, 0, 0, 0.8);  
    width: 100%;  
    height: 100%;  
    z-index: -1;  
    
    } .modal__content {
    transition: all 300ms ease-in-out;  
    width: 30%;  
    margin: auto;  
    z-index: 10;  
    position: absolute;  
    top: -100%;  
    left: 50%;  
    transform: translateX(-50%);  
    background: white;  
    padding: 15px 30px;  
    border-radius: 5px;  
    .modal__header {  
      border-bottom: 1px solid gray;  
      margin-bottom: 15px;  
    }  
    .modal__main {  
      margin-bottom: 15px;  
    }  
    .modal__footer {  
      display: flex;  
      align-items: center;  
      justify-content: flex-end;  
      & > * {  
        margin-left: 15px;  
        cursor: pointer;  
      }  
    }  
    
    } &--active {
    opacity: 1;  
    visibility: visible;  
    .modal__content {  
      top: 25%;  
    }  
    
    } }

    ` 6
  • Lấy trạng thái bật/ tắt mặc định qua `
  • {

    box-sizing: border-box; padding: 0; margin: 0; } body { line-height: 1.5; } .modal { position: fixed; width: 100%; height: 100%; top: 0; left: 0; z-index: 10; visibility: hidden; opacity: 0; transition: all 300ms linear; .modal__backdrop {

    position: absolute;  
    top: 0;  
    left: 0;  
    background-color: rgba(0, 0, 0, 0.8);  
    width: 100%;  
    height: 100%;  
    z-index: -1;  
    
    } .modal__content {
    transition: all 300ms ease-in-out;  
    width: 30%;  
    margin: auto;  
    z-index: 10;  
    position: absolute;  
    top: -100%;  
    left: 50%;  
    transform: translateX(-50%);  
    background: white;  
    padding: 15px 30px;  
    border-radius: 5px;  
    .modal__header {  
      border-bottom: 1px solid gray;  
      margin-bottom: 15px;  
    }  
    .modal__main {  
      margin-bottom: 15px;  
    }  
    .modal__footer {  
      display: flex;  
      align-items: center;  
      justify-content: flex-end;  
      & > * {  
        margin-left: 15px;  
        cursor: pointer;  
      }  
    }  
    
    } &--active {
    opacity: 1;  
    visibility: visible;  
    .modal__content {  
      top: 25%;  
    }  
    
    } }

    ` 7

// Dựa vào data-trigger='modal' truy xuất toàn bộ nút bật modal  
btnModals.forEach(btnModal => {  
  // lấy thông tin modal  
  const modal = document.querySelector(btnModal.getAttribute("data-target"));  
  // lấy trạng thái modal  
  const open = btnModal.getAttribute("aria-open") === "true" ? true : false;  
  // Khởi động trạng thái mặc định của modal được set từ aria-open của button  
  onActiveOrNot(btnModal, modal, open);  
  // Bật modal thông qua button  
  btnModal.addEventListener("click", () => {  
    onActiveOrNot(btnModal, modal, !open);  
  });
  // Tắt modal thông qua nút close  
  modal.querySelector(".modal__cancel").addEventListener("click", () => {  
    onActiveOrNot(btnModal, modal, !open);  
  })  
})

Ấy mà function

  • { box-sizing: border-box; padding: 0; margin: 0; } body { line-height: 1.5; } .modal { position: fixed; width: 100%; height: 100%; top: 0; left: 0; z-index: 10; visibility: hidden; opacity: 0; transition: all 300ms linear; .modal__backdrop { position: absolute; top: 0; left: 0; background-color: rgba(0, 0, 0, 0.8); width: 100%; height: 100%; z-index: -1; } .modal__content { transition: all 300ms ease-in-out; width: 30%; margin: auto; z-index: 10; position: absolute; top: -100%; left: 50%; transform: translateX(-50%); background: white; padding: 15px 30px; border-radius: 5px; .modal__header { border-bottom: 1px solid gray; margin-bottom: 15px; } .modal__main { margin-bottom: 15px; } .modal__footer { display: flex; align-items: center; justify-content: flex-end; & > * { margin-left: 15px; cursor: pointer; } } } &--active { opacity: 1; visibility: visible; .modal__content { top: 25%; } } }

8 là gì ta. Để không quá rườm rà, đây sẽ là hàm thực hiện thêm / xoá

  • { box-sizing: border-box; padding: 0; margin: 0; } body { line-height: 1.5; } .modal { position: fixed; width: 100%; height: 100%; top: 0; left: 0; z-index: 10; visibility: hidden; opacity: 0; transition: all 300ms linear; .modal__backdrop { position: absolute; top: 0; left: 0; background-color: rgba(0, 0, 0, 0.8); width: 100%; height: 100%; z-index: -1; } .modal__content { transition: all 300ms ease-in-out; width: 30%; margin: auto; z-index: 10; position: absolute; top: -100%; left: 50%; transform: translateX(-50%); background: white; padding: 15px 30px; border-radius: 5px; .modal__header { border-bottom: 1px solid gray; margin-bottom: 15px; } .modal__main { margin-bottom: 15px; } .modal__footer { display: flex; align-items: center; justify-content: flex-end; & > * { margin-left: 15px; cursor: pointer; } } } &--active { opacity: 1; visibility: visible; .modal__content { top: 25%; } } }

1 dựa vào các sự kiện bên trên, nhận vào 3 tham số.

  • 0: Nút để trỏ đến Modal hiện tại
  • 1: Nội dung Modal
  • 2: Trạng thái bật / tắt

// Hàm xử lý bật, tắt modal thông qua class cung cấp  
function onActiveOrNot(buttonTargetModal, modal, open) {  
  if(!modal.classList.contains("modal--active") && open) {  
    modal.classList.add("modal--active")  
  }  
  else {  
    modal.classList.remove("modal--active");  
  }  
  // đặt lại trạng thái cho nút button khi modal active | !active  
  buttonTargetModal.setAttribute("aria-open", open);  
}

Thế là mình đã hoàn thành việc xử lý modal với gần 40 dòng code JavaScript, có thể tái sử dụng cho


3 Modal khác trong quá trình sử dụng sau này.

Nào, chúng ta cùng xem demo, mình có thể thêm


2 modal tuỳ ý tương ứng với


2 nút button để bật nó mà không cần thêm bất kì đoạn JavaScript nào khác.

Chi tiết bản demo: Xem thử kết quả tại đây

Kết luận

Bài viết trên mình viết dựa trên kỹ thuật mình biết, có thể còn khó hiệu và không mạch lạc nhưng có thể sẽ giúp các bạn biết cách xây dựng một


6 cơ bản và ngoài ra, có thể tìm cách để tái sử dụng mã bạn viết để không mất nhiều thời gian xây dựng lại làm phát sinh JavaScript dư thừa.