Hướng dẫn render trong javascript

Trình duyệt web render nội dung như thế nào?

Intro

Có lẽ trình duyệt web (TDW) là phần mềm được sử dụng nhiều nhất ngày nay. Trong bài viết này, tôi sẽ chỉ ra cách TDW hoạt động và đi sâu hơn là cách TDW render nội dung như thế nào. Hiện nay có 5 loại TDW chính trên desktop: Chrome, Internet Explorer, Firefox, Safari và Opera. Trên di động là Android Browser, Safari trên iPhone, Opera Mini và Opera Mobile, UC Browser, Chrome.

Nhưng trước tiên chúng ta cần có kiến thức cơ bản về TDW.

Giới thiệu cơ bản về TDW

Chức năng của TDW

Chức năng chính của TDW là trình bày nội dung trang web bạn chọn bằng cách gửi yêu cầu đến server và thể hiện chúng trên của sổ trình duyệt. Nội dung thường là tài liệu HTML nhưng cũng có thể là PDF, ảnh, XML ... Cách trình bày 1 file HTML sẽ dựa vào các thẻ HTML và nội dung CSS cũng như JS kèm theo.

Cấu trúc của TDW

Thành phần chính của TDW gồm:

  • User Interface (giao diện người dùng): bao gồm thanh địa chỉ, các nút back/forward, bookmark, menu …
  • Engine TDW: liên kết giữa UI và engine render.
  • Engine render: trình bày nội dung được trả về. Ví dụ như khi ta gửi request HTML thì engine này sẽ phân tích HTML, CSS và thể hiện nội dung được phân tích lên màn hình.
  • Networking: thực hiện các request HTTP.
  • UI: trình bày các widget, cửa sổ cho TDW.
  • JavaScript interpreter (thông dịch JavaScript): thông dịch và thực thi code JS
  • Data storage (lưu trữ dữ liệu): lưu trữ các loại data cục bộ như cookies. Ngoài ra TDW còn hỗ trợ một số phương thức lưu trữ khác như localStorage, IndexedDB, WebSQL and FileSystem.

Hướng dẫn render trong javascript

Engine render

Như đã nhắc đến ở trên, chức năng của engine render tất nhiên là render các nội dung nhận về từ request và trình bày chúng trên màn hình TDW.

Mặc định engine render có thể trình bày HTML, XML và ảnh. Nó cũng có thể trình bày các loại dữ liệu khác thông qua các plug-ins và phần mở rộng như sử dụng plug-in PDF viewer để xem PDF. Tuy nhiên trong phần này tôi chỉ tập trung đến việc trình bày các file HTML, ảnh được định dạng bởi CSS.

Các TDW khác nhau sử dụng các engine render khác nhau: Internet Explorer dùng Trident, Firefox dùng Gecko, Safari dùng WebKit. Chrome and Opera (từ version 15) dùng Blink (1 fork của WebKit).

TDW render nội dung như thế nào?

Trình tự engine render thế hiện nội dung được thể hiện trong biểu đồ dưới đây

Hướng dẫn render trong javascript

  • Phân tích HTML và tạo cây DOM - HTML là cấu trúc phân cấp được bắt đầi bởi thẻ , thường sẽ bao gồm thẻ , và các thành phần khác lồng bên trong. Những thẻ này được phân tích và tổng hợp thành cây DOM.

Hướng dẫn render trong javascript

  • Render cấu trúc cây – Thuộc tính CSS (thuộc tính style) cũng được phân tích và tích hợp với cây DOM để tạo ra "cây render". Đây là cây của các yếu tố trực quan như chiều cao, chiều dài và màu sắc được sắp xếp phân cấp theo cách chúng sẽ được trình bày trên TDW.

Hướng dẫn render trong javascript

  • Quy trình giao diện (Layout Process) – Khi cây render được cấu trúc xong, engine render sẽ duyệt toàn bộ các thuộc tính HTML trong cây và chỉ ra nơi mà chúng được thể hiện trên màn hình. Điều này được thực hiện tại vị trí trái trên (top left- 0,0) và các phần và thuộc tính được ánh xạ đến tọa độ trên màn hình.
  • Vẽ (Painting) – từng node (nhánh) của cây render được vẽ ra trên màn hình bằng việc giao tiếp với giao diện của hệ điều hành nhằm chỉ ra cách thể hiện các yếu tố UI.

Các engine search (Google, Bing, Yahoo ...) không thể "nhìn" các trang web như cách chúng ta nhìn thấy mà thay vào đó sử dụng tập hợp các luật để xây dựng ra cây DOM và xem các yếu tố là một phần của cây. Ví dụ như Google sẽ hiểu rằng các từ trong trang web sẽ nằm trong thẻ .

JavaScript

Tuy nhiên sẽ thật là thiếu sót khi không nhắc đến JavaScript. JavaScript là một ngôn ngữ lập trình khiến mọi thứ có thể thực hiện bên trong TDW và khiến trang web có thể tương tác. Ví dụ điển hình là Popup - sẽ hiện lên khi bấm vào một nút nào đó và một cửa sổ sẽ bật lên, hiển thị trên màn hình. Điều này có nghĩa là JS có thể thực thi sau khi trang web được render trên màn hình, và khi code JS được thực thi thì nó sẽ kích hoạt re-render lại trang web.

Ví dụ như nút Like trên Facebook. Ban đầu sau khi load trang bạn thấy nó là nút Like với ngón tay đưa lên. Khi bạn nhấn vào nó, ngay lập tức nó chuyển thành "Unlike" và thay đổi thành ngón tay đưa xuống.

Nội dung của trang có thể được điều khiển bởi JS để thể hiện các yếu tố mà trong source code không hề có. Điều này cũng có nghĩa là search engine (không render trang) sẽ không thể biết được những gì JS đã thực hiện trên trang.

CSS

Nào chúng ta cùng đào sâu hơn 1 chút về các thuộc tính CSS sẽ được render ra sao trên TDW.

Repaint

Khi thay đổi một yếu tố style mà không ảnh hưởng đến vị trí trên trang (như background-color, border-color, visibility), TDW sẽ thực hiện "repaint" yếu tố đó lần nữa với "style" mới.

Reflow

Khi thay đổi có ảnh hưởng đến nội dung, cấu trúc hoặc vị trí các thành phần thì "reflow" sẽ được thực hiện. Các thay đổi này thường được thực thi bởi:

  • DOM manipulation - Thêm, sửa, xoá, thay thế các thành phần
  • Nội dung thay đổi, bao gồm chữ thay đổi trong các trường của form
  • Tính toán và thay đổi tính chất CSS
  • Thêm và xoá style
  • Thay đổi thuộc tính class
  • Thay đổi liên quan đến TDW - co giãn, scroll
  • Thực thi các Pseudo-class - :hover

TDW sẽ tinh chỉnh việc render như thế nào?

TDW sẽ hạn chế tối đa các "repaint" và "reflow" chỉ thực hiện ở vùng có sự thay đổi. Ví dụ như việc thay đổi kích thước của một thành phần absolute hoặc fixed chỉ ảnh hưởng đến chính nó và thành phần lân cận, trong khi thay đổi các thành phần relative sẽ kích hoạt reflow lại toàn bộ.

Một tinh chỉnh nữa là khi chạy các đoạn code JS, TDW sẽ cache lại thay đổi và chỉ thực hiện thay đổi trong 1 lần chạy duy nhất. Ví dụ, đoạn code này chỉ thực hiện một reflow và repaint:

var $body = $('body');
$body.css('padding', '1px'); // reflow, repaint
$body.css('color', 'red'); // repaint
$body.css('margin', '2px'); // reflow, repaint
// only 1 reflow and repaint will actually happen

Tuy nhiên, như đã nhắc đến ở trên khi truy xuất một thuộc tính của một thành phần thì sẽ kích hoạt reflow. Điều này sẽ xảy ra khi cho thêm một dòng code nữa:

var $body = $('body');
$body.css('padding', '1px');
$body.css('padding'); // reading a property, a forced reflow
$body.css('color', 'red');
$body.css('margin', '2px');

Vì vậy, chúng ta nên nhóm các lần đọc các thuộc tính vào với nhau để đạt hiệu năng tốt nhất.

Tuy nhiên đôi khi chúng ta muốn reflow, như khi cần thực hiện 1 animation nào nó. Ví dụ như ta gán giá trị vào cùng một thuộc tính (như "margin-left") 2 lần.

.has-transition {
   -webkit-transition: margin-left 1s ease-out;
      -moz-transition: margin-left 1s ease-out;
        -o-transition: margin-left 1s ease-out;
           transition: margin-left 1s ease-out;
}
// our element that has a "has-transition" class by default
var $targetElem = $('#targetElemId');

// remove the transition class
$targetElem.removeClass('has-transition');

// change the property expecting the transition to be off, as the class is not there
// anymore
$targetElem.css('margin-left', 100);

// put the transition class back
$targetElem.addClass('has-transition');

// change the property
$targetElem.css('margin-left', 50);

Nhìn thì có vẻ đúng nhưng khi thực hiện thì không như mong đợi. Thay đổi bị cache và chỉ thực hiện 1 lần duy nhất. Vì vậy chúng ta cần "ép" reflow bằng cách:

// remove the transition class
$(this).removeClass('has-transition');

// change the property
$(this).css('margin-left', 100);

// trigger a forced reflow, so that changes in a class/property get applied immediately
$(this)[0].offsetHeight; // an example, other properties would work, too

// put the transition class back
$(this).addClass('has-transition');

// change the property
$(this).css('margin-left', 50);

Và bây giờ, mọi thứ đã chạy như ý muốn.

Tại sao phải quan tâm đến vấn đề này?

Để tổng hợp lại, chúng ta rút ra được các kinh nghiệm như sau:

  • Khi tạo HTML và CSS, đừng quên encoding, nên có các thẻ , script nên được đặt dưới thẻ
  • Hãy tạo CSS selectors đơn giản nhưng chi tiết.
  • Khi thực hiện với JavaScript nên tối giản các tác động đến DOM, nên cache mọi thứ. Tạo các thành phần “offline” và chèn vào DOM sau cùng.
  • Cách tốt nhất để thay đổi “style” là thay đổi đặc tính class

Cảm ơn các bạn đã theo dõi (bow)