07/09/2018, 14:09

Cách tối ưu mã CSS Selectors sao cho hiệu quả

Tối ưu CSS Selector để đạt hiệu quả cao không phải là một chủ đề mới và thực sự thì tôi cũng không cần trình bày tất tần tật về nó, nhưng đây là một trong những thứ tôi rất thích và vẫn liên tục nghiên cứu từ lúc làm việc ở Sky. Có nên tối ưu CSS Selector? Rất nhiều người quên rằng, hoặc đơn ...

Tối ưu CSS Selector để đạt hiệu quả cao không phải là một chủ đề mới và thực sự thì tôi cũng không cần trình bày tất tần tật về nó, nhưng đây là một trong những thứ tôi rất thích và vẫn liên tục nghiên cứu từ lúc làm việc ở Sky.

Có nên tối ưu CSS Selector?

Rất nhiều người quên rằng, hoặc đơn giản là họ không nhận ra rằng CSS có thể được tối ưu hoặc không. Điều này dễ bị bỏ quên bởi vì chỉ một chút thay đổi, người ta có thể tạo ra mã CSS không được tối ưu.

Các quy tắc tối ưu CSS chỉ thực sự có tác dụng với những website cần tốc độ rất cao, những website mà tốc độ là một điểm nổi bật của nó. Tất nhiên, không phải vì bạn đang làm một trang tầm cỡ như facebook thì mới quan tâm đến tối ưu, kể cả bạn đang xây dựng một trang web nhỏ thôi, thì việc biết cách viết CSS tối ưu lúc nào cũng có lợi cả.

Tối ưu với những loại selector nào?

CSS Selectors không phải mới với tất cả chúng ta, cơ bản nhất của nó là các loại selector như: Type (ví dụ: div), ID (ví dụ: #header), Class (ví dụ: .tweet).

Thêm các loại không được phổ biến như: pseudo-classes (ví dụ: :hover) và thêm các loại phức tạp có trong CSS3 như regex, ví dụ: :first-child hoặc [class=^"grid-"].

Tốc độ của các selectors khi truy vấn được xếp từ cao tới thấp như sau:

  • ID, ví dụ: #header
  • Class, ví dụ: .promo
  • Type, ví dụ: div
  • Adjacent sibling, ví dụ: h2 + p
  • Child, ví dụ: li > ul
  • Descendant, ví dụ: ul a
  • Universal, ví dụ: *
  • Attribute, ví dụ: [type="text"]
  • Pseudo-classes/-elements, ví dụ: a:hover

Bạn có thể tìm hiểu các loại selector ở trên trong phần Selector ở Khoá học CSS trên CiOne

Có một điều quan trọng cần để ý là mặc dù một ID thì rất nhanh và hiệu quả khi truy vấn về mặt kỹ thuật, nhưng chưa hẳn là thế. Sử dụng công cụ CSS Test Creator từ Steve Souders, chúng ta có thể thấy rằng một id selector và một class selector có rất ít sự khác biệt về tốc độ.

Khi thử nghiệm bằng công cụ của Steve trên trình duyệt Firefox 6 với hệ điều hành Window, ghi nhận được tốc độ hiển thị là 10.9ms khi sử dụng một class selector đơn giản, còn với id selector là 12.5ms, rõ ràng là chậm hơn cả class selector.

Sự khác biệt về tốc độ giữa id và class liên quan đến những yếu tố khác nữa.

Quá trình thử nghiệm cho thấy, sử dụng selector theo kiểu type selector thì chậm khá nhiều so với class hoặc id.

Thử nghiệm với số lượng lớn descendant selector cho một kết quả khoảng 440ms!.

Từ những kết quả trên, chúng ta có thể thấy sự khác biệt giữa id/classtype/descendants là khá lớn… còn bản thân id,class thì không có quá nhiều sự khác biệt.

Tìm hiểu thêm về Descendants selector

Lưu ý, sự khác biệt này còn phụ thuộc vào trình duyệt và hệ điều hành.

Tối ưu css selector có thể làm bạn ngạc nhiên đấy

Selectors kết hợp

Sử dụng selectors riêng lẻ như #nav, sẽ chọn bất kỳ thẻ nào có ID là nav, hoặc có thể kết hợp các selector lại với nhau như: #nav a, sẽ chọn được bất kỳ thẻ a nào với ID là nav.

Bây giờ, chúng ta đọc các selector theo thứ tự từ trái qua phải. Chúng ta sẽ duyệt từ #nav và sau đó là các thẻ a bên trong nó. Trình duyệt lại làm ngược lại, tức là từ phải sang trái.

Với chúng ta, #nav a có nghĩa là tìm thẻ có id là nav có chứa thẻ a trong nó, còn với trình duyệt, nó sẽ thấy một thẻ a bên trong thẻ có id là nav. Đây chính là sự khác biệt rất lớn ảnh hưởng đến việc tối ưu các selector, và rất đáng để chúng ta bỏ thời gian tìm hiểu nó.

Sẽ rất hiệu quả cho trình duyệt để xử lý từ các thành tố tận cùng bên phải trước và truy ngược lên cây DOM thay vì đi từ điểm bắt đầu của cây DOM và đi xuống từng thẻ để kiểm tra, mà chưa chắc là có được selector cần tìm. Selector tận cùng bên phải còn được gọi là key selector.

Tìm hiểu key selector

Key selector, như đã nói ở trên, là thành phần tận cùng bên phải của một selector kết hợp. Đây là selector mà trình duyệt đi tìm đầu tiên.

Bạn còn nhớ phần thứ tự tốc độ của các loại selector ở phần trên không? Dựa vào bảng đó, nếu key selector là loại có tốc độ cao nhất thì nó sẽ quyết định đến hiệu năng của selector.

Một key selector giống như thế này:

#content .intro {}

Thì khá tối ưu bởi vì hiệu năng phụ thuộc vào class selector. Trình duyệt sẽ tìm tất cả các thẻ có class là intro và sau đó tra ngược lại các thẻ cha nó để tìm xem key selector có cha là #content hay không.

Tuy nhiên selector dưới đây lại không phải là tối ưu nhất:

#content * {}

Khi thực thi, trình duyệt sẽ tìm tất cả các thẻ trên trang web và sau đó tìm xem nó có cha là #content hay không. Thử tượng tượng nếu trang web có 2000 thẻ thì nó sẽ đi qua từng thẻ và lại tra ngược lên.

Đây là một ví dụ minh hoạ cho kiểu selector rất tệ về hiệu năng, đặt selector như vậy làm key selector là rất nguy hiểm.

Như vậy, sử dụng những kiến thức ở trên chúng ta có thể đưa ra quyết định về việc lựa chọn loại selector nào cho phù hợp.

Thử tưởng tượng, chúng ta có một trang web rất lớn với hàng ngàn thẻ a. Ở trang này cũng có một danh sách (ul) liên kết đến các mạng xã hội như Twitter, Dribble, Facebook, Google+. Chúng ta có 4 mạng xã hội trên trang và hàng ngàn liên kết khác.

Một selector như thế này sẽ không hợp lý:

#social a {}

Điều gì sẽ xảy ra khi trình duyệt truy cập cả ngàn liên kết trên trang trước khi kiểm tra các liên kết có nằm trong thẻ có id là social hay không? Key selector trong trường hợp này sẽ thoả rất nhiều các thẻ liên kết mà chúng ta không cần.

Và để xử lý vấn đề này, chúng ta có thể chỉ định rõ hơn các selector của .social-link đến mỗi thẻ a trong phần liên kết mạng xã hội. Tuy nhiên làm như vầy sẽ mâu thuẫn với cái chúng ta đã biết, đó là không nên đặt những class trên các element khi chúng ta có thể sử dụng chính các thẻ HTML.

Đây chính là lý do tại sao tôi thích thú với việc tối ưu hiệu năng, chúng ta cần phải tìm ra điểm cân bằng giữa các kỹ thuật này sao cho phù hợp nhất.

Thực tế là chúng ta hay đặt như thế này:

<ul id="social">
    <li><a href="#" class="twitter">Twitter</a></li>
    <li><a href="#" class="facebook">Facebook</a></li>
    <li><a href="#" class="dribble">Dribbble</a></li>
    <li><a href="#" class="gplus">Google+</a></li>
</ul>

Với mã CSS:

#social a {}

Chúng ta sẽ có:

<ul id="social">
    <li><a href="#" class="social-link twitter">Twitter</a></li>
    <li><a href="#" class="social-link facebook">Facebook</a></li>
    <li><a href="#" class="social-link dribble">Dribbble</a></li>
    <li><a href="#" class="social-link gplus">Google+</a></li>
</ul>

Với mã CSS:

#social .social-link {}

key selector này sẽ chỉ thoả với một vài thẻ, có nghĩa là trình duyệt có thể tìm chúng và trang trí chúng nhanh hơn.

Chúng ta có thể làm cho selector này trở thành:

.social-link {}

mà không làm cho selector quá chi tiết (overqualifying). Sẽ được để cập ở phần dưới đây.

Tóm lại, key selector là một nhân tố quyết định khối lượng công việc mà trình duyệt phải làm, bạn phải để ý điều này nhé.

Overqualifying selector

Chúng ta đã biết về key selector là gì, và nó là một nhân tố chính ảnh hưởng đến khối lượng công việc mà trình duyệt cần xử lý, hãy tối ưu thêm một chút nữa trong phần này.

Lợi điểm nhất của việc sử dụng key selectors là chúng ta có thể tránh được Overqualifying selector.

Ví dụ về một overqualifying selector:

html body .wrapper #content a {}

Có quá nhiều thứ ở đây, và ít nhất là có 3 selector hoàn toàn không cần thiết ở đây,
Cái không cần thiết nhất là:

#content a {}

Cái này thì sao nhỉ?

Đầu tiên là trình duyệt phải tìm kiếm tất cả thẻ a, sau đó kiểm tra xem chúng có nằm trong thẻ cha có ID là content hay không, và cứ tương tự như vậy cho mỗi thẻ a. Điều này làm cho trình duyệt phải xử lý quá nhiều, mà lại không cần thiết nữa chứ. Hiểu được điều này, chúng ta có thể chuyển selector phù hợp hơn như thế này:

#nav li a {}

trở thành:

#nav a {}

Chúng ta biết rằng nếu a nằm bên trong một li thì nó phải là con của #nav do đó chúng ta có thể loại bỏ li từ selector. Sau đó, với thẻ có ID là nav thì chỉ tồn tại duy nhất một thẻ trong trang nên thẻ này sẽ được lựa chọn, chúng ta cũng có thể bỏ thẻ ul.

Overqualified selectors chỉ làm cho trình duyệt vất vả hơn mà không cần thiết. Loại bỏ bớt các selector không cần thiết giúp mã CSS gọn gàng hơn và hiệu năng tốt hơn.

Phải chăng ta nên sử dụng tất cả những nguyên tắc này?

Câu trả lời ngắn gọn là không!

Dài dòng hơn thì là phụ thuộc vào trang web bạn đang xây dựng. Nếu bạn đang làm một trang web nhỏ thì chỉ cần làm cho mã CSS gọn gàng và dễ hiểu thay vì tối ưu hiệu năng của CSS, hơn nữa bạn cũng không mấy cần chúng cho một trang web nhỏ.

Tuy nhiên, nếu bạn đang xây dựng trang web cỡ như Amazon, Facebook thì chênh lệch vài mili giây về tốc độ cũng có thể tạo sự khác biệt.

Các trình duyệt ngày càng biên dịch CSS nhanh hơn, thậm chí trên cả mobile. Bạn cũng không nhất thiết phải để ý tới các CSS selector chậm trên website, Nhưng…

Nhưng

Các CSS selector đó vẫn làm chậm trình duyệt của bạn và bạn biết điều đó. Thậm chí, nếu bạn không áp dụng các nguyên tắc đã nói ở bài này, thì nó vẫn rất có lợi cho bạn khi biết về nó. Ít nhất là chúng ta cũng hiểu là các selector có thể ảnh hưởng tới hiệu năng của trang web và do đó chúng ta cần tối ưu bất kể lúc nào có thể. Nếu mà bạn viết một selector như thế này:

div:nth-of-type(3) ul:last-child li:nth-of-type(odd) * { 
    font-weight:bold 
}

Thì hầu như là bạn đang làm sai.

0