Khi code UI, mình thấy có một vài trường hợp khá hay, có thể nói là một vài bài toán thú vị. Cùng mình tìm hiểu nhé 😄
Border collapse
Đối với table
, chúng ta có một thuộc tính đặc biệt là border-collapse
. Thuộc tính này giúp chúng ta hợp nhất các border cạnh nhau của các ô (td, th) trong table. Bạn có thể xem ví dụ ở đây.
Nhưng nếu chúng ta phải hiện thực border-collapse đối với layout thông thường hiện thực với div
thì sao?
Chúng ta có một UI ở desktop gồm 2 hàng và 2 cột, xung quanh là border như thế này:
Về mobile sẽ như thế này:
Code HTML dành cho UI đó như sau:
<div class="container">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
Thông thường, giải pháp chúng ta nghĩ đến đầu tiên đó chính là border
:
/* Dành cho desktop */
.container {
display: flex;
flex-wrap: wrap;
}
.item {
border: 4px solid purple;
width: 50%;
...;
}
Và đây là kết quả:
Vấn đề chúng ta cần giải quyết chính là ở đây, các border cạnh nhau không hợp nhất, gây nên việc border bị dày gấp đôi.
Và thông thường chúng ta sẽ nghĩ tiếp đến sử dụng điều kiện để style border cho các item, chẳng hạn như sau:
.item {
border: 4px solid purple;
...;
}
.item:nth-child(2n) {
/* Cho thằng con thứ 2 và thứ 4, sẽ không border bên trái */
border-left-width: 0;
}
.item:nth-child(n + 3) {
/* Cho thằng con thứ 3 và thứ 4, sẽ không border bên trên */
border-top-width: 0;
}
Kết quả sẽ giống như chúng ta mong muốn, tuy nhiên, khi về mobile, phải sửa một chút vì các item sẽ thay đổi vị trí.
.item {
border: 4px solid purple;
...;
}
.item:nth-child(2n) {
/* Cho thằng con thứ 2 và thứ 4, sẽ không border bên trái */
border-left-width: 0;
}
.item:nth-child(n + 3) {
/* Cho thằng con thứ 3 và thứ 4, sẽ không border bên trên */
border-top-width: 0;
}
@media (max-width: 768px) {
.item:nth-child(n + 2) {
/* Từ item thứ 2 trở đi sẽ không border trên */
border-top-width: 0;
}
.item:nth-child(2n) {
/* Đồng thời phải cập nhật lại border cho các item 2, 3, 4 */
border-left-width: 4px;
}
}
Đó là solution có thể dễ nghĩ tới nhất, nhưng nếu còn nhiều yêu cầu khác, ví dụ như lên màn hình lớn hơn sẽ có 3 item nằm trên 1 hàng. Chúng ta lại phải cập nhật style khá thủ công để đạt được điều mong muốn.
Có một solution tốt và đơn giản hơn, đó là sử dụng kết hợp CSS Grid
và box-shadow
:
.container {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
}
.item {
box-shadow: 0 0 0 4px purple;
}
@media (max-width: 768px) {
.container {
grid-template-columns: 1fr;
}
}
Điều quan trọng trong solution này là gap
và box-shadow
Overlap content
Mình có một design nhìn khá bình thường như này:
Các card lúc bình thường sẽ như vị trí 1, 3. Khi hover
vào sẽ thấy nội dung mô tả như ở card vị trí 2.
Khi nhìn vào UI này, mình sẽ nghĩ ngay đến solution là phần mô tả sẽ được bọc trong 1 div absolute
, khi hover vào sẽ xuất hiện trên phần nền kia.
Tuy nhiên, nội dung mô tả đó là động (tức do người dùng nhập liệu), nó có thể ngắn, nó có thể dài. Tại thời điểm chúng ta hiện thực UI này chưa thể biết trước.
Vậy vấn đề ở đây là gì? Đó chính là kích thước (chiều cao) của card sẽ được cho là bao nhiêu?
Để cho bạn dễ hình dung, chúng ta có minh họa sau:
Investment Protocol
Khi hover vào, nội dung sẽ được hiện lên phần nội dung của card:
Investment Protocol
Vấn đề ở đây là nội dung phần mô tả quá dài, chiều cao của card chúng ta đang mặc định sẵn lại không chứa hết.
Solution chúng ta mong muốn ở đây chính là: nếu nội dung mô tả ngắn hơn phần nền của card, chúng ta sẽ lấy kích thước của phần nền; nếu nội dung mô tả dài hơn phần nền, chúng ta sẽ lấy kích thước của phần mô tả.
Một lần nữa, solution này sẽ là một ứng dụng của CSS Grid
:
<div class="container">
<div class="main-content">
<img src="..." />
<h3>Investment Protocol</h3>
</div>
<div class="description">Contrary to popular belief, Lorem Ips.....</div>
</div>
.container {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
.main-content {
padding: 16px;
grid-column: 1/-1;
grid-row: 1/-1;
}
.description {
padding: 16px;
grid-column: 1/-1;
grid-row: 1/-1;
opacity: 0;
}
.container:hover .description {
opacity: 1;
}
Ở đây, chúng ta cho phần main-content
và description
nằm chồng lên nhau, cùng 1 vị trí, như vậy Grid sẽ chọn kích thước của phần nào có kích thước lớn hơn.
Gradient border
Một UI bình thường như này:
<button>I am a button</button>
button {
border: 5px solid transparent;
background-image: linear-gradient(white, white), linear-gradient(90deg, darkblue, darkorchid);
background-clip: padding-box, border-box;
background-origin: border-box;
}