Facebook Facebook Facebook Facebook

Shadow DOM là gì?

Giới thiệu Shadow DOM - Tương lai của Front End

Tất cả các website hiện tại được lập trình với HTML, bất bạn có đang viết back-end bằng ngôn ngữ gì thì phía front-end hay các trình duyệt web chỉ hiểu HTML mà thôi. Đây là một ngôn ngữ dể hiểu và thân thiện với con người, ai cũng có thể lập trình được với vài thẻ (tag) là chúng ta đã làm chủ một trang web với với mơ ước được hàng triệu lượt truy cập mỗi tháng.
Thế nhưng với máy là một câu chuyện khác, chúng chỉ hiểu DOM - Document Object Mode và thường được biết đến dưới dạng các nút (node) theo mô hình cha con như một cái cây mọc ngược (rễ ở trên, lá ở dưới) - DOM Tree.
Với sự phát triển còn mạnh hơn cả siêu bão Irma của front-end, website ngày nay đã có thể làm được nhiều thứ hơn rất nhiều, thậm chí gánh việc cho back-end. Điều này đòi hỏi công việc ở front-end cũng nặng nề hơn theo thời gian. Các trang web lớn như Facebook, Twitter, GMail, etc thì có cả một rừng cây Amazon trong source code HTML của họ. Và đương nhiên chỉ với DOM thôi thì các nhà phát triển web khó lòng xử lý hàng núi việc như thế. Từ đó ra đời khái niệm Virtual DOMvà Shadow DOM.
Virual DOM là các component mà chúng ta thường thấy trong các library/framework như React, Vue, Ember. Tuy nhiên bài viết này mình chỉ nói về Shadow DOM (Polymer là JS librabry đang sử dụng).

Shadow DOM: hộp đen giữa rừng cây

Shadow DOM thật ra vẫn là DOM. Chỉ có điều chúng có đặc tính là Isolated DOM, nghĩa là cô lập với thế giới bên ngoài. Các style sheet hay javascript từ bên ngoài không thể tác động vào trong Shadow DOM được và ngược lại. Scoped CSS bị giới hạn trong Shadow DOM nên vì thế chúng trở nên simple đi nhiều vì phần seleclor chúng ta không cần phải viết đầy đủ id rồi tới class gì để đảm bảo chúng không tác động vào các DOM khác.
Chính điều này Shadow DOM rất hữu ích để để xây dựng các custom elements và phối hợp chúng lại để tạo thành những element lớn hơn thay vì viết 1 lần cho tất cả.

Shadow DOM luôn tồn tại quanh ta

Thật vậy, không cần phải đi tìm kiếm đâu xa xôi, các thẻ Audio, Video, Input Range trong HTML5 chính là hiện thân của Shadow DOM.

Ví dụ với thẻ <Video>, để soi được vào bộ đồ lòng của nó, ta cần bật tính năng "Show user agent shadow DOM" của DevTool trên Chrome.

Bây giờ chúng ta có thể inspect một Video Player trên HTML5. Các bạn có thể để ý thành phần #shadow-roottrong hình bên dưới.
Như vậy bản chất của video player render từ thẻ <Video> cũng chính là 1 custom element được xây dựng từ rất nhiều tag HTML, có điều chúng đặt trong 1 Shadow DOM nên bình thường ta không nhìn thấy. Chúng ta không bao giờ có thể thay đổi các thành phần này, cũng như style của chúng cũng không chịu bất kỳ ảnh hưởng nào từ bên ngoài. Đó là minh chứng cho khả năng Isolated (hay có thể được gọi là Encapsulation) của Shadow DOM.

Cấu trúc của Shadow DOM

Với Shadow DOM chúng ta sẽ có thêm khái niệm Shadow Root. Nội dung Shadow Root sẽ không được trình duyệt render mà thay vào đó là Shadow HostShadow Host được xem như một element bình thường nhưng nội dung bên trong nó (cả style sheet) sẽ được scoped, độc lập với thế giới bên ngoài.

Cách xây dựng một Shadow DOM cơ bản

Chúng ta ví dụ với một Shadow DOM chỉ là 1 element p như sau:
<style> p { color: green } </style>
<p id="sd-root">I am Shadow Root</p>

<script>
  const host = document.querySelector('#sd-root');
  const root = host.createShadowRoot();
  root.innerHTML = '<style> p { color: gray }</style><p>Hello <strong>Shadow DOM</strong></p>';
</script>
Ở đây khi trình duyệt render xong chúng ta sẽ nhận được: Hello Shadow DOM
Thay vì là:
Hello Shadow DOM
Và khi inspect ta có được kết quả sau:

bên trong Shadow DOM dù conflict selector với bên ngoài (cùng tác động vào element p) nhưng cũng không sao vì đặc tính của Shadow DOM (scoped style sheet)
Thêm nữa là khi chúng ta truy xuất innerHTML của element #sd-root thì ta chỉ lấy được đoạn text khởi tạo: “I am Shadow Root” và cũng không thể tác động được gì vào bên trong Shadow Root, cụ thể chính là Shadow Host.

Template và Content

Bây giờ ta đã biết cách tạo nội dung bên trong một Shadow DOM chính là dùng code JS để thêm elements vào Shadow Root. Điều này dẫn đến các Shadow Root có nội dung phức tạp thì phần code JS này rất khó bảo trì. Vì thế chúng ta có thể tách rời nội dung (có thể hiểu là data) và code thể hiện của nó (presentation).
Bây giờ mình sẽ "làm màu" thêm cho ví dụ trên:
Hello
Shadow DOM
Theo cách viết thông thường sẽ như sau:
<style> 
  .container {
    background-color: #90CAF9;
    padding: 10px;
    width: 300px;
    margin: 5px auto;
    text-align: center;
    border-radius: 5px;
    font-family: BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
  }

  .title {
    padding-bottom: 5px;
    color: #4a4a4a;
    font-weight: 400;
    line-height: 1.25;
  }

  .content {
    font-size: 25px;
    color: #363636;
    font-weight: 600;
    line-height: 1.125;
  }
</style>

<div class="container">
  <div class="title">
    <span>Hello</span>
  </div>
  <div class="content">
    <span>Shadow DOM</span>
  </div>
</div>
Đương nhiên là chúng ta có thể ném hết đoạn code trên vào thuộc tính innerHTML của Shadow Root để tạo 1 Shadow DOM. Tuy nhiên làm vậy thì code sẽ rất khó xem. Ở đây chúng ta thấy rằng nội dung chính là chuỗi Shadow DOM, phần còn lại chỉ là "thể hiện" của nó. Với thẻ <template> và <content> ta có thể xây dựng Shadow DOM như sau:
<div id="helloName">Shadow DOM</div>

<template id="helloNameTemplate">
  <style>
    /* ...  giống như trên */
  </style>

  <div class="container">
    <div class="title">
      <span>Hello</span>
    </div>
    <div class="content">
      <span>
        <content></content>
      </span>
    </div>
  </div>
</template>

<script>
  const shadow = document.querySelector('#helloName').createShadowRoot();
  const template = document.querySelector('#helloNameTemplate');
  const clone = document.importNode(template.content, true);
  shadow.appendChild(clone);

  document.querySelector('#helloName').textContent = 'Shadow DOM';
</script>
Ở đây chỉ có 2 điểm đơn giản cần lưu ý là tất cả element và style của Shadow DOM ta sẽ đặt vào thẻ <template>. Khi run time, nội dung thật sự sẽ được inject vào thẻ <content> thông qua câu lệnh
document.querySelector('#helloName').textContent = 'Shadow DOM';
Ta có thể thử lại một content khác:
document.querySelector('#helloName').textContent = 'Template and Content';

0 nhận xét:

Đăng nhận xét