All posts
CSS đang dần dần "cướp việc" của JavaScript
cssjavascriptfrontendweb development

CSS đang dần dần "cướp việc" của JavaScript

Linh Nguyen T. H.'s avatarLinh Nguyen T. H.
Table of Contents8 sections

Có một thói quen mà gần như mọi Frontend Developer đều từng mắc phải.

Muốn mở menu?

menu.classList.add("open");

Muốn highlight ô input?

input.classList.add("focus");

Muốn hiện lỗi?

input.classList.add("error");

Muốn animation khi dialog mở?

dialog.classList.add("show");

Cứ có một interaction là thêm một class.

Cứ thêm một class là phải viết thêm JavaScript.


Rồi theo thời gian...

Chúng ta có hàng chục event listener.

Hàng chục đoạn code chỉ để làm đúng một việc:

Thông báo cho CSS biết giao diện đang ở trạng thái gì.

Điều thú vị là...

Browser đã biết điều đó từ lâu.

Chỉ là trước đây CSS không có cách nào để "đọc" những trạng thái ấy.


CSS chưa từng biết Event

Nhiều người nghĩ CSS xử lý event.

Thực ra không phải.

CSS không hề biết click, mouseover hay keydown là gì.

Những thứ đó thuộc về JavaScript.

CSS chỉ nhìn thấy kết quả sau khi event xảy ra.

Ví dụ:

Người dùng đưa chuột vào.

JavaScript nhận được sự kiện mouseover.

Còn CSS chỉ biết:

"À, phần tử này hiện đang được hover."

Đó là state.

Không phải event.

Điều này đúng với tất cả pseudo-class quen thuộc.

  • :hover

  • :focus

  • :checked

  • :disabled

  • :target

CSS không quan tâm điều gì đã xảy ra.

CSS chỉ quan tâm:

UI hiện đang ở trạng thái nào.

Đây là một khác biệt rất nhỏ...

...nhưng lại thay đổi hoàn toàn cách chúng ta nghĩ về việc xây dựng giao diện.


CSS đang ngày càng hiểu nhiều trạng thái hơn

Nếu cách đây vài năm CSS chỉ có vài state cơ bản như :hover hay :focus, thì hiện nay danh sách đó đã dài hơn rất nhiều.

Ví dụ:

  • :focus-visible

  • :focus-within

  • :placeholder-shown

  • :user-valid

  • :user-invalid

  • :modal

  • :popover-open

  • :fullscreen

  • :has()

Nhìn qua có vẻ chỉ là thêm vài pseudo-class.

Nhưng thực chất chúng đang lấy đi khá nhiều công việc mà trước đây JavaScript phải đảm nhận.


Chúng ta từng dùng JavaScript để "đồng bộ state"

Hãy lấy ví dụ đơn giản.

Khi dialog được mở.

Rất nhiều project sẽ làm như thế này.

dialog.showModal();
dialog.classList.add("open");

Sau đó CSS sẽ viết:

.open {
    animation: fade-in .3s;
}

Có gì sai không?

Không.

Nhưng hãy để ý.

showModal() đã khiến browser biết dialog đang mở.

Sau đó chúng ta lại thêm một class "open"...

...chỉ để báo với CSS rằng:

"Ê, dialog đang mở nhé."

Điều này giống như việc browser đã biết hôm nay là thứ Hai.

Nhưng bạn vẫn tạo thêm một biến:

const isMonday = true;

Rồi truyền khắp ứng dụng.

Hoàn toàn dư thừa.

Ngày nay CSS có thể viết đơn giản:

dialog:modal {
    animation: fade-in .3s;
}

Không cần class.

Không cần đồng bộ.

Không cần nhớ remove sau khi đóng dialog.

Browser đã quản lý state giúp bạn.


Form validation cũng vậy

Có lẽ ai cũng từng viết kiểu này.

input.addEventListener("input", () => {
    if (!input.validity.valid) {
        input.classList.add("error");
    } else {
        input.classList.remove("error");
    }
});

Logic không hề phức tạp.

Phức tạp nằm ở việc...

Chúng ta đang lặp lại thứ browser đã biết.

HTML Validation đã tồn tại rất lâu.

Browser biết input hợp lệ hay không.

CSS giờ cũng biết.

input:user-invalid {
    border-color: red;
}

Không cần event.

Không cần class.

Không cần JavaScript.


:has() là mảnh ghép còn thiếu

Nếu phải chọn một tính năng khiến mình thay đổi cách viết CSS nhiều nhất trong vài năm gần đây, thì đó chắc chắn là :has().

Trước đây.

Muốn đổi style của parent khi child thay đổi.

Không còn cách nào ngoài JavaScript.

card.classList.toggle("active");

Giờ thì:

.card:has(input:checked) {
    border-color: dodgerblue;
}

Hoặc:

form:has(input:user-invalid) {
    border: 2px solid tomato;
}

Lần đầu tiên.

CSS có thể "nhìn xuống" cây DOM để quyết định cách render.

Một điều từng được xem là bất khả thi.


Điều này có nghĩa JavaScript sắp thất nghiệp?

Không.

JavaScript vẫn là linh hồn của ứng dụng.

Bạn vẫn cần nó để:

  • call API

  • handle business logic

  • manage state

  • realtime

  • drag & drop

  • animation

  • handle data

Nhưng vai trò của JavaScript đang thay đổi.

Trước đây.

Nó vừa xử lý logic.

Vừa đóng vai người phiên dịch giữa browser và CSS.

Hiện nay.

Browser có thể nói chuyện trực tiếp với CSS.

JavaScript không cần chen vào giữa nữa.


Hãy để CSS làm đúng việc của CSS

Một nguyên tắc mình rất thích là:

Nếu browser đã biết trạng thái của UI, đừng bắt JavaScript phải đi thông báo lại cho CSS.

Đó cũng là hướng phát triển của nền tảng web hiện đại.

Chúng ta có:

  • :has()

  • :popover-open

  • :modal

  • :user-invalid

  • Scroll-driven Animations

  • View Transitions

  • Anchor Positioning

  • Custom States của Web Components

Mỗi phiên bản mới của CSS đều mang theo một thông điệp rất rõ ràng:

Đừng dùng JavaScript cho những việc mà trình duyệt đã có thể làm giúp bạn.


Kết luận

Trong nhiều năm, chúng ta quen với suy nghĩ rằng:

"Có tương tác thì phải dùng JavaScript."

Nhưng web platform đang dần thay đổi điều đó.

CSS không còn chỉ là ngôn ngữ để tô màu hay căn chỉnh layout. Nó ngày càng hiểu rõ trạng thái của giao diện và có thể phản ứng trực tiếp với chúng mà không cần một lớp JavaScript trung gian.

Điều này không khiến JavaScript trở nên kém quan trọng. Ngược lại, nó giải phóng JavaScript khỏi những công việc lặp đi lặp lại như thêm hoặc xóa class chỉ để phản ánh trạng thái của UI.

Có lẽ, trong tương lai gần, khi bắt đầu một tính năng mới, câu hỏi đầu tiên sẽ không còn là:

"Mình cần lắng nghe event nào?"

Mà sẽ là:

"Browser đã có state cho việc này chưa?"

Đó là một thay đổi nhỏ trong tư duy, nhưng có thể tạo ra những đoạn code đơn giản hơn, ít lỗi hơn và tận dụng tốt hơn những gì nền tảng web hiện đại đã mang lại.

Nguồn tham khảo: