Table of Contents13 sections
Xây dựng lưới thác nước linh hoạt chỉ với vài dòng CSS — không cần thư viện, không cần JavaScript phức tạp.
1. Masonry Layout là gì?
Masonry layout (hay còn gọi là "lưới thác nước") là kiểu bố cục trong đó các phần tử được sắp xếp theo cột dọc — tương tự cách những viên gạch được xếp lên nhau không đều nhau trong một bức tường. Mỗi item chiếm chiều cao tự nhiên của nó, item tiếp theo lấp đầy khoảng trống phía dưới, tạo ra một bố cục liên tục, mượt mà.
Pinterest là trang web phổ biến nhất sử dụng kiểu layout này — ảnh dọc, ảnh ngang, ảnh vuông đều được xếp hài hòa mà không lãng phí khoảng trắng.

Thuật ngữ "masonry" lấy cảm hứng từ nghề xây gạch (masonry = thợ nề). Những viên gạch được xếp liền kề không đều, tận dụng tối đa không gian — đúng như cách layout này hoạt động.
2. Ba cách xây dựng Masonry Layout
Phương pháp | Hỗ trợ trình duyệt | JS cần thiết? | Độ khó |
|---|---|---|---|
CSS Columns (khuyến nghị) | Tất cả | Không | Dễ |
CSS Grid masonry (thực nghiệm) | Safari only | Không | Trung bình |
JavaScript (Masonry.js) | Tất cả | Có | Trung bình |
Trong bài này, chúng ta tập trung vào CSS Columns — đơn giản nhất, hoạt động trên mọi trình duyệt hiện đại, và không cần một dòng JavaScript nào để tạo ra bố cục cơ bản.
3. Cấu trúc HTML
Cấu trúc HTML rất đơn giản: một container bọc toàn bộ lưới, bên trong là các card. Mỗi card chứa ảnh và thông tin mô tả.
<!-- Masonry container -->
<div class="masonry">
<div class="pin">
<img src="photo.jpg" alt="Mô tả ảnh" loading="lazy" />
<button class="save-btn">Lưu</button>
</div>
<!-- Lặp lại các .pin tiếp theo -->
</div>4. CSS — Phần quan trọng nhất
Toàn bộ hiệu ứng masonry chỉ đến từ 2 dòng CSS. Phần còn lại là styling thông thường cho card.
/* ĐÂY LÀ PHẦN QUAN TRỌNG NHẤT */
.masonry {
columns: 5 200px; /* tối đa 5 cột, tối thiểu 200px mỗi cột */
column-gap: 12px; /* khoảng cách giữa các cột */
padding: 20px 16px;
}
.pin {
break-inside: avoid; /* ngăn card bị cắt ngang cột */
margin-bottom: 12px;
}Giải thích
columns: 5 200px: Trình duyệt sẽ tự tính — tạo tối đa 5 cột, nhưng mỗi cột không được nhỏ hơn 200px. Nếu màn hình hẹp, số cột tự giảm xuống. Hoàn toàn responsive mà không cần media query!
CSS hoàn chỉnh cho card
.pin {
break-inside: avoid; /* Không cho card bị tách giữa các cột Masonry */
margin-bottom: 12px;
border-radius: 16px;
overflow: hidden; /* Ẩn phần nội dung vượt ra ngoài bo góc */
background: #fff;
position: relative; /* Làm mốc cho nút Save position: absolute */
cursor: pointer;
}
.pin-cover {
display: flex;
align-items: flex-end; /* Đưa nội dung xuống cuối card */
padding: 16px;
transition: filter 0.2s;
}
.pin:hover .pin-cover {
filter: brightness(0.85); /* Làm tối card khi hover */
}
.pin-content {
color: #222;
}
.pin-label {
font-size: 12px;
opacity: 0.7; /* Giảm độ nổi bật của label */
margin-bottom: 6px;
}
.pin-title {
font-size: 20px;
font-weight: 700;
line-height: 1.2;
}
.pin-sub {
margin-top: 8px;
font-size: 13px;
opacity: 0.75;
}
/* Nút Save xuất hiện khi hover */
.save-btn {
position: absolute;
top: 10px;
right: 10px;
background: #e60023;
color: #fff;
border: none;
border-radius: 20px;
padding: 8px 16px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
opacity: 0; /* Ẩn mặc định */
transition: opacity 0.2s;
}
.pin:hover .save-btn {
opacity: 1; /* Hiện nút Save khi hover card */
}5. JavaScript — Render card động
Nếu bạn muốn render card từ dữ liệu (ví dụ: từ API), dưới đây là đoạn JavaScript mẫu:
Vì là mục đích testing nên đang sử dụng thẻ div và sử dụng chiều cao cố định để hiện thị, cần sửa lại bằng thẻ <img/> khi sử dụng thực tế.
const items = [
{ h: 260, color: "#c8e6c9", label: "Nature photography", sub: "Save to board", icon: "ti-photo", title: "Misty forest morning" },
{ h: 180, color: "#bbdefb", label: "Architecture", sub: "Save to board", icon: "ti-building", title: "Glass facade reflections" },
//Thêm hình ảnh tại đây
];
const grid = document.getElementById("grid");
items.forEach((item) => {
const pin = document.createElement("div");
pin.className = "pin";
pin.innerHTML = `
<div
class="pin-cover"
style="
height:${item.h}px;
background:${item.color};
"
>
<div class="pin-content">
<div class="pin-label">${item.label}</div>
<div class="pin-title">${item.title}</div>
<div class="pin-sub">${item.sub}</div>
</div>
<button class="save-btn">Lưu</button>
</div>
`;
grid.appendChild(pin);
});Luôn thêm thuộc tính
loading="lazy"cho thẻ<img>để trình duyệt chỉ tải ảnh khi chúng xuất hiện trong vùng nhìn thấy. Điều này cải thiện đáng kể hiệu suất trang khi có nhiều ảnh.
6. Responsive không cần Media Query
Một trong những ưu điểm lớn nhất của columns là tự động responsive. Với columns: 5 200px, trình duyệt sẽ:
Màn hình lớn (≥1000px) Hiển thị 5 cột đầy đủ — mỗi cột khoảng 200px. Trải nghiệm Pinterest chuẩn.
Tablet (600px – 1000px) Tự giảm xuống 3–4 cột. Layout vẫn đẹp, không bị vỡ.
Mobile (dưới 600px) Thu gọn còn 2 cột. Người dùng vẫn thấy được nội dung thoải mái.
CSS — Responsive tùy chỉnh (nếu cần)
/* Mặc định: tự động responsive */
.masonry { columns: 5 200px; }
/* Nếu muốn kiểm soát chi tiết hơn */
@media (max-width: 768px) {
.masonry { columns: 2; }
}
@media (max-width: 480px) {
.masonry { columns: 1; } /* feed dạng đơn trên điện thoại nhỏ */
}8. Mẹo và những lưu ý quan trọng
Luôn đặt height: auto cho ảnh
Nếu bạn cứng nhắc chiều cao ảnh, masonry sẽ mất tác dụng vì tất cả card đều bằng nhau. Để ảnh giữ tỷ lệ tự nhiên chính là bí quyết tạo ra cái "thác nước" đặc trưng.
Đừng dùng display: flex hoặc display: grid cho container
columns là thuộc tính riêng, không tương thích với flex/grid. Để container ở display: block (mặc định) là đủ.
Sử dụng column-fill: balance (tùy chọn)
CSS
.masonry {
columns: 5 200px;
column-gap: 12px;
column-fill: balance; /* phân phối đều nội dung giữa các cột */
}Animate card khi tải
CSS — Hiệu ứng xuất hiện
.pin {
animation: fadeUp 0.4s ease both;
}
.pin:nth-child(odd) { animation-delay: 0.05s; }
.pin:nth-child(even) { animation-delay: 0.1s; }
@keyframes fadeUp {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
/* giảm chuyển động */
@media (prefers-reduced-motion: reduce) {
.pin { animation: none; }
}9. Tổng kết
Masonry layout với CSS Columns là một trong những kỹ thuật đơn giản nhưng mang lại hiệu ứng thị giác ấn tượng nhất. Chỉ với 2 dòng CSS bạn đã có một layout hoàn toàn responsive, không cần JavaScript, không cần thư viện bên ngoài.
Tóm tắt nhanh: Dùng
columns: N Wpxtrên container vàbreak-inside: avoidtrên từng card. Để ảnh dùngheight: auto. Thêm hover effect quaopacityvàfilter. Vậy là xong!
