Ai thì không biết nhưng với mình bộ môn Reponsive website nó luôn là một cái gì đó rất ám ảnh, là một web dev nhưng thú thật mình không hào hứng với việc Reponsive website chút nào, cho dù đây là yêu cầu cơ bản của bất cứ website thế kỷ 21 nào =))
Câu chuyện không phải vì kỹ thuật của nó quá khó mà do mình gặp vấn đề trong việc căn chỉnh @media của các dạng màn hình, được màn này thì màn khác lại méo xệch làm mình rất khó chịu. Dần dần mình cho rằng việc sử dụng công cụ @media để căn chỉnh màn hình là không hiệu quả và bắt đầu tìm đến những phương thức khác có nhiều ưu điểm hơn.
Trong bài viết này mình sẽ điểm qua một vài ví dụ về việc sử dụng những công cụ/thuộc tính mới có cũ có của CSS kết hợp với một vài phép tính toán để thực hiện reponsive, tất nhiên là không sử dụng framework rồi!
Phân chia số lượng item mỗi hàng tùy theo độ rộng của element
Với cách này chúng ta sẽ phải động đến tính toán một chút. Cụ thể ở đây là sẽ sử dụng các con số để tính toán lại độ rộng của element cha và các element con sao cho thằng cha rộng bao nhiêu thì chứa được bấy nhiêu thằng con theo hàng ngang, các thằng khác sẽ được xếp xuống dưới
Đầu tiên hãy dùng một demo thế này:
<div class="container"> <div>1</div> <div>2</div> <div>3</div> <div>4</div> <div>5</div>
</div>
<style>
.container { display:flex; padding: 20px;
}
.container > div { text-align: center; height:100px; margin: 4px; width: 100%; background: radial-gradient(circle, rgba(238,174,202,1) 0%, rgba(148,187,233,1) 100%);
}
</style>
Như các bạn có thể thấy các div ở trên hoàn toàn chưa được tinh chỉnh phù hợp với thay đổi của màn hình
Bây giờ mình sẽ thực hiện Reponsive để số lượng item kia sẽ thay đổi dựa vào độ rộng của màn hình, ví dụ như với màn hình máy tính thì là 5 item nhưng khi về tablet hoặc mobile thì sẽ về 3 item rồi 1 item chẳng hạn.
Sử dụng 2 thuộc tính phổ biến là flex và flex-wrap để thực hiện reponsive lại container như sau:
.container { display:flex; flex-wrap:wrap; gap: 10px;
}
.container > div { text-align: center; height:100px; flex: 200px /* thêm flex */ background: radial-gradient(circle, rgba(238,174,202,1) 0%, rgba(148,187,233,1) 100%);
}
Cho bạn nào chưa biết thì gap dùng đễ giãn cách giữa các div trong một class có display: flex (gần tương tự padding, margin)
Ở class dưới thì mình thêm một thuộc tính flex và loại bỏ width để cho máy tính tự tính toán độ rộng của khung hình phù hợp với bao nhiêu item, mỗi lần màn hình máy tính thay đổi thì giá trị này tự động được tính toán lại, demo:
Ưu điểm của cách làm này là rất nhanh gọn, tốn ít code nhưng bạn sẽ khó kiểm soát được số lượng item trên một hàng và những item nằm hàng cuối thì có độ rộng không đều nhau. Do đó sử dụng flex có vẻ không được tối ưu cho lắm do ý tưởng ban đầu mình muốn các item nằm lần lượt theo hàng ngang từ trên xuống dưới
Để căn chỉnh lại độ rộng này thì mình sẽ đổi thuộc tính display của container sang grid như sau:
.container { display:grid; grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); gap:10px;
}
.container > div { height:100px; background: radial-gradient(circle, rgba(238,174,202,1) 0%, rgba(148,187,233,1) 100%);
}
Tương tự như ví dụ trên grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
sẽ chia lại grid các item thành các cột với độ rộng ban đầu của mỗi cột tương ứng là 200px, khi độ rộng màn hình thay đổi thì giá trị này sẽ được tính toán lại và số lượng các cột sẽ được căn chỉnh nếu kích thước màn hình lúc đó không còn đủ cho giá trị độ rộng ban đầu của mỗi cột nữa
Tuy nhiên vấn đề còn tồn đọng là mình chưa thực sự kiểm soát được số lượng item trên mỗi hàng khi màn hình ở độ giãn nở tối đa. Ví dụ bây giờ mình cần kiểm soát sao cho mỗi hàng chỉ có 3 item ở độ rộng màn hình tối thiểu cho 3 item trở lên, nghĩa là với mỗi hàng 3 item, mỗi item 400px thì ở độ rộng lớn hơn 1200px, sẽ chỉ có 3 item, để thực hiện điều này mình sẽ sử dụng đến một số công thức toán học.
Kiểm soát số lượng item trên một hàng
Mình gọi đây là một công thức kiểm soát bởi vì thật sự nó phải động đến khá nhiều những con số. Công thức này đúng với cả việc sử dụng flex và grid nên mình sẽ chia ra cho cả 2 thuộc tính này, nếu ai đó thích dùng cái nào hơn thì đều có thể áp dụng được
1. Với flex
Thay đổi giá trị flex:400px thành công thức như bên dưới:
flex: max(400px, 100%/3 - 20px)
Vì mình muốn có 3 item trên một hàng, với mỗi item rộng tối thiểu 400px, vậy là với độ rộng từ 1200px trở lên, mình sẽ luôn có 3 item 1 hàng. Giải thích một chút các con số ở trên:
- 100%/3 : Lấy kích cỡ tối đa của màn hình chia 3 (số item bạn muốn trên 1 hàng) và đặt trong hàm max()
- Nếu độ dài màn hình lớn hơn 400px * 3 thì hàm max() sẽ sử dụng giá trị này để làm độ dài, ngược lại các item sẽ được xuống dòng
-
- 20px: khoảng gap được bù trừ. Với 3 item chúng ta sẽ có 2 gap vậy suy ra với N item ta sẽ có:
flex: max(400px, 100%/N - (N - 1) * gap)
- 20px: khoảng gap được bù trừ. Với 3 item chúng ta sẽ có 2 gap vậy suy ra với N item ta sẽ có:
- Tiếp tục tối ưu công thức ở trên bằng cách sử dụng biến số trong CSS như sau:
.container { --gap:10px; --n:3; display:flex; flex-wrap:wrap; gap:var(--gap);
}
.container > div { height:100px; flex:max(400px, 100%/var(--n) - var(--gap) * (var(--n) - 1)); background:red;
}
Kết quả:
2. Với grid
Tương tự như flex, grid cũng sẽ được điều chỉnh lại như sau
.container { --w:400px; --n:3; --gap:10px; display:grid; grid-template-columns:repeat(auto-fit,minmax(max(var(--w), 100%/(var(--n) + 1) + 0.1%),1fr)); gap:var(--gap);
}
Kết quả:
Tạm kết
Những phương thức ở trên vẫn còn nhiều cách giải quyết và biến đổi tuy nhiên mình xin phép được tạm kết ở đây và sẽ quay lại với một bài viết thứ 2 về chủ đề này sớm thôi. Nếu có ý kiến đóng góp, xin mời bạn comment phía bên dưới