Facebook đã giải quyết vấn đề modal như thế nào?

Đầu tiên, hãy định nghĩa xem như thế nào được gọi là một modal "chuẩn chỉ"?Một modal cần phải đảm bảo các yêu cầu sau:

1. DOM được tách biệt hoàn toàn với "root dom" của app;
2. Khi modal hiển thị, không được làm thay đổi bất cứ trạng thái hiển thị nào của "root dom"; Sau khi đóng modal, trạng thái hiển thị trước của "root dom" phải được bảo toàn;
3. Nếu có thêm child modal, sau khi đóng child modal thì trạng thái hiển thị của parent modal cũng phải được bảo toàn.

Hãy phân tích kỹ hơn các vấn đề trên xem:

1: Có lẽ ai cũng biết rằng lý do vì sao phải tách biệt DOM của modal ra rồi, react-dom đã cung cấp sẵn phương thức "createPortal" cho chúng ta dùng rồi.

2: Khi modal hiển thị, thông thường chúng ta đã biết cách để giữ trạng thái root dom là: Lock trạng thái scroll của body, bằng cách thêm thuộc tính overflow-y: hidden vào body, lúc này thanh scrolll bar của body theo phương Y sẽ biến mất. => Xuất hiện hiệu ứng giật (do srool bar bị mất), sau khi đóng thì làm ngược lại. Có 1 cách để xử lý vấn đề này mà mình thấy modal của material-ui đã làm là thêm một thuộc tính "padding-right: 15px" vào cho body, để đánh lừa trình duyệt rằng giống như thanh scroolbar vẫn đang còn. Cách này mình thấy nó khá bựa vì bề rộng của scroll bar trên mỗi trình duyệt là khác nhau, đấy là còn chưa kể một số web còn custom cái scroll bar nữa.

[2-a]: Sau khi đóng modal, tại sao cần phải bảo toàn trạng thái hiển thị của root dom?: Hãy tưởng tượng bạn đang đọc một trang đến đoạn XXX, bạn click vào 1 cái modal và sau đó tắt nó đi để trở về đọc tiếp. Điều mà bạn mong muốn trong trường hợp này là bạn phải quay về đúng vị trí XXX mà bạn đang đọc dở trước đó.

3: Nếu trong modal có xuất hiện thêm 1 modal con nữa (ví dụ như confirm để đóng cái modal cha chẳng hạn), thì sau khi đóng modal con này trạng thái của modal cha vẫn phải được bảo toàn đúng như mục [2-a] ở trên.

Để giải quyết các vấn đề 1, 2 là không khó, có lẽ các thư viện modal hiện tại đều làm được. Nhưng riêng vấn đề thứ 3 thì không thấy ai giải quyết, không biết lý do vì sao?!.

Dưới đây là những gì mà Facebook đã làm:

- Một Wrapperr Context (tạm gọi là ContextA) sẽ listen sự kiện scroll của document, ghi nhớ giá trị của scroll khi cần (chẳng hạn như khi modal xuất hiện).

- App sẽ được đặt trong Context này, đồng thời Modal cũng được wrap trong 1 Context này để listen mỗi khi có một Modal con xuất hiện trong nó.

- Cấu trúc của toàn bộ ứng dụng như sau:
```
<ContextA>
// App content
<div ........./>
<div ........./>

// Modals
<Modal1 ...../>
<Modal2 ...../>
</ContextA>
```
- Và cấu trúc của một Modal cũng như vậy:
```
<ContextA>
// Modal content
<div ........./>
<div ........./>
</ContextA>
```
Cây DOM sẽ như thế này:


Khác với cách xử lý thông thường ta thường thấy, Facebook đã sử dụng position = relative cho STYLE_1 ở trạng thái modals chưa hiển thị, khi có modals hiển thị thì STYLE_1 chuyển sang FIXED, zIndex = 0. STYLE_2 là style của modals thì luôn luôn là RELATIVE, zIndex = 1, (không giống cách xử lý thông thường là FIXED, zIndex = 1000000xxx). Bạn thử dành 1 phút suy nghĩ kỹ vấn đề này sẽ nghiệm ra cách làm như vậy rất thông minh. Cho dù có 1000 modals xuất hiện thì trạng thái view trước luôn luôn được bảo toàn sau khi mỗi modal đóng lại.

Thêm một điều mình mới nghiệm ra khi xem code của FB đó là họ detect trạng thái đóng của modal bằng cách listen nó trên DOM thật, (khác với cách chúng ta biết là listen khi component UN_MOUNT). Mục đích của việc này là những thay đổi trên DOM phát sinh sau khi đóng Modal phải đảm bảo chỉ được phép xảy ra khi modal đã đóng thật sự (biến mất trên DOM, không phải UN_MOUNT), nếu không đôi khi sẽ có nhứng hiện tượng giật cục (STATE thì luôn thay đổi trước DOM). Phần này rất khó giải thích cho các bạn hiểu, nhưng đúng là một pha xử lý đi vào lòng người. Rất nể!

Lý thuyết chỉ đơn giản vậy thôi, nhưng để xử lý được triệt để thì có khoảng 800 dòng code cho cái modal này. Mình đã viết lại xong rồi nhưng đang refactor lại chút cho dễ hiểu và hẹn sẽ public vào tuần sau. Nếu bạn thấy chán các thư viện modal hiện tại và muốn tìm hiểu xem cách viết code FB như thế nào thì follow mình để tải về ngâm cứu nhé! (Mình sẽ share từ từ một số thư viện cover lại được của Facebook trên trang cá nhân của mình).

Cảm ơn vì đã đọc hết bài! Hẹn gặp lại vào chủ đề tiếp theo: "Viết một component Drag & Drop đúng chuẩn Facebook."
Post a Comment