摘要
隨着網頁設計越來越複雜,尤其是圖片牆、商品展示、內容卡片這類頁面,瀑布流佈局(也叫 Masonry 佈局)成了非常受歡迎的設計方案。傳統實現往往依賴大量 JavaScript 或第三方庫,但其實利用 CSS Grid 加上少量 JS,就能實現既響應式又性能優越的瀑布流佈局。
本文將深入拆解這種方法的原理,帶你一步步理解關鍵代碼的作用,配合可運行的示例,讓你能輕鬆上手並靈活應用到實際項目中。
引言
你有沒有注意到 Pinterest、淘寶、微博這樣的網站,他們的內容區往往是“磚塊”式的排列,且高度不一致,卻能做到看起來緊湊、自然,不留大空白?這就是瀑布流佈局。
早期,瀑布流多用 JavaScript 計算每個元素的位置,或者用專門的 Masonry.js 庫,雖然功能強大,但有時會帶來性能負擔,且對響應式適配不夠友好。
而現代 CSS Grid 給了我們強大的佈局能力。只要結合 grid-auto-rows 設定行高基準和 JS 動態計算跨行數,就能用純 CSS + 少量 JS 實現性能優、響應式友好的瀑布流效果。
本文將以實用示例為核心,逐步深入代碼細節,幫助你徹底掌握這套方法。
CSS Grid 實現瀑布流的核心思路
為什麼用 CSS Grid?
- CSS Grid 本身支持二維佈局(行+列),天生適合網格排列
- 可設置列數自動適應容器寬度,實現響應式
- 結合
grid-auto-rows可做出“網格行高基準”,方便動態跨行 - 省去手動計算每個元素位置的複雜邏輯
主要技術點
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))
這個屬性意思是“根據容器寬度自動創建列,列寬最小200px,最大自動撐滿”,所以列數會隨屏幕寬度變。grid-auto-rows: 10px
設定每行的高度為 10px。為什麼 10px?這是給 JS 計算跨行數一個基準單位。grid-auto-flow: dense
告訴瀏覽器緊湊排列,遇到空隙就儘量填滿,避免斷層。grid-row-end: span X
讓每個元素根據自身高度跨越多行,實現高低不一的佈局。
需要 JS 的原因
純 CSS 無法動態根據內容高度調整跨行數,因為 CSS Grid 只能接受固定或預定義的跨行數。必須用 JS 獲取元素高度,計算跨幾行,然後用 CSS 變量傳遞給 grid-row-end。
示例代碼詳解
HTML 結構
HTML 很簡單,就是一個父容器 .container,裏面有一堆內容塊 .item:
<div class="container">
<div class="item">內容1</div>
<div class="item">內容2</div>
<div class="item">內容3</div>
<div class="item">內容4</div>
<div class="item">內容5</div>
<div class="item">內容6</div>
</div>
.item 可以是任意內容,比如圖片、文字卡片、商品展示塊等。
CSS 樣式詳解
.container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /* 自動適應列數 */
grid-auto-rows: 10px; /* 設定每行高度基準為10px */
grid-auto-flow: dense; /* 緊湊排列,填滿空隙 */
gap: 10px; /* 網格間距 */
padding: 10px;
background: #f5f5f5;
}
.item {
background: white;
border-radius: 6px;
padding: 10px;
box-sizing: border-box;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
/* 動態跨行 */
grid-row-end: span var(--row-span, 1);
}
/* 通過偽選擇器製造高度差異,模擬真實內容 */
.item:nth-child(odd) {
min-height: 80px;
}
.item:nth-child(even) {
min-height: 150px;
}
重點説明:
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))
會根據父容器寬度自動決定列數,列寬不小於200px,最大填滿父容器剩餘空間。這樣能自適應桌面、平板、手機各種屏幕。grid-auto-rows: 10px
設定了基礎行高是10px。為啥不直接用更大數值?用小的基準值方便 JS 計算更加精確。grid-auto-flow: dense
保證“碎片”儘量填充滿網格空白,不會留下難看的空隙。grid-row-end: span var(--row-span, 1)
通過 CSS 變量--row-span控制每個元素跨幾行,實現不同高度的塊在網格中的合理擺放。
JavaScript 詳解
const container = document.querySelector('.container');
const rowHeight = 10; // 必須和 CSS 中 grid-auto-rows 保持一致
const gap = 10; // 必須和 CSS 中 gap 保持一致
function resizeAllGridItems() {
const allItems = container.querySelectorAll('.item');
allItems.forEach(item => {
// 獲取元素實際高度(包含padding和border)
const height = item.getBoundingClientRect().height;
/*
計算跨行數:
由於每行高度是 rowHeight + gap(行高加行間距),
元素的高度 + gap 除以這個值,得到需要跨多少行
Math.ceil 向上取整,避免內容被截斷
*/
const rowSpan = Math.ceil((height + gap) / (rowHeight + gap));
// 通過 CSS 變量傳給 grid-row-end 控制跨行數
item.style.setProperty('--row-span', rowSpan);
});
}
// 頁面加載時計算一次
window.addEventListener('load', resizeAllGridItems);
// 窗口大小變化時重新計算,保證響應式
window.addEventListener('resize', resizeAllGridItems);
JS 核心點:
- 用
getBoundingClientRect().height取到元素真實渲染高度 - 用
Math.ceil((height + gap) / (rowHeight + gap))計算跨行數 - 通過 CSS 變量設置跨行數,讓 CSS Grid 能正確渲染布局
- 綁定到
load和resize事件,動態響應不同屏幕尺寸和內容變更
實際場景舉例
圖片牆
<div class="container">
<img class="item" src="img1.jpg" alt="圖片1" />
<img class="item" src="img2.jpg" alt="圖片2" />
<img class="item" src="img3.jpg" alt="圖片3" />
</div>
.item {
width: 100%; /* 圖片寬度撐滿格子寬 */
object-fit: cover;
border-radius: 6px;
display: block;
grid-row-end: span var(--row-span, 1);
}
JS 同理,動態計算每張圖片的高度,設置跨行數。
效果:圖片尺寸各異但排列緊湊,不留空白。
商品列表
<div class="container">
<div class="item">
<img src="product1.jpg" />
<h3>商品名A</h3>
<p>詳細描述內容,可以有長短不一的文字。</p>
</div>
<div class="item">
<img src="product2.jpg" />
<h3>商品名B</h3>
<p>較短的描述。</p>
</div>
</div>
圖片和文字高度不固定,JS 自動根據內容撐開高度,計算跨行數,保證整齊排列。
博客文章卡片
<div class="container">
<div class="item">
<h2>標題1</h2>
<p>摘要文字,長度不定...</p>
</div>
<div class="item">
<h2>標題2</h2>
<p>更長的摘要文字示例,這裏會撐開卡片高度...</p>
</div>
</div>
同樣適用該瀑布流方案,保證不同字數卡片排列自然。
深入代碼理解與優化
為什麼 grid-auto-rows 要和 JS 計算嚴格對應?
grid-auto-rows 定義了每一行的“高度單位”,比如 10px,這個數值直接影響 JS 計算跨行數的基準。如果兩者不匹配,佈局就會錯位。
例如:
- CSS 定義:
grid-auto-rows: 10px; - JS 計算跨行數公式:
rowSpan = Math.ceil((height + gap) / (rowHeight + gap)) - 這裏
rowHeight必須和 CSS 中的 10px 一致,gap同理。
如果不一致,內容塊會錯跨行,出現空隙或重疊。
為什麼計算公式要加上 gap?
因為每個網格單元格之間有間距 gap,比如 10px。如果不加這個間距,元素高度除以單行高度會偏小,導致佈局不準確。
加上 gap 後,計算更精確:
const rowSpan = Math.ceil((height + gap) / (rowHeight + gap));
grid-auto-flow: dense 的作用
dense 讓佈局儘可能填滿空隙,比如當一個內容塊比較矮的時候,它會往前擠,填補前面因為高塊留下的空白,不留空隙。
缺點是可能會打亂原本元素的排列順序,不過大多數瀑布流場景這是可接受的。
響應式適配原理
使用 repeat(auto-fill, minmax(200px, 1fr)),列數自動適配屏幕寬度:
- 大屏幕時列數多,每列至少200px寬
- 小屏幕時列數減少,自動縮放到一列或兩列
- 無需額外 JS 處理列數變化,純 CSS 實現
性能優化建議
- 只在必要時調用 JS 計算,比如頁面加載和窗口尺寸改變時
- 計算前判斷元素是否可見,避免不必要計算
- 結合
requestAnimationFrame優化 resize 事件響應 - 對於大量內容,考慮分頁加載或虛擬滾動,減輕計算壓力
完整示例:可直接複製運行
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>CSS Grid 瀑布流示例</title>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
background: #eee;
}
.container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
grid-auto-rows: 12px;
grid-auto-flow: dense;
gap: 12px;
padding: 12px;
max-width: 1200px;
margin: 0 auto;
}
.item {
background: white;
border-radius: 8px;
padding: 15px;
box-sizing: border-box;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
grid-row-end: span var(--row-span, 1);
}
.item img {
width: 100%;
height: auto;
border-radius: 6px;
display: block;
margin-bottom: 8px;
}
</style>
</head>
<body>
<div class="container" id="grid">
<div class="item">
<img src="https://picsum.photos/300/200?random=1" alt="隨機圖片1" />
<h3>標題1</h3>
<p>這是一段簡介文字,可以長也可以短。</p>
</div>
<div class="item">
<img src="https://picsum.photos/300/350?random=2" alt="隨機圖片2" />
<h3>標題2</h3>
<p>描述內容更長一些,撐高卡片高度測試。</p>
</div>
<div class="item">
<img src="https://picsum.photos/300/150?random=3" alt="隨機圖片3" />
<h3>標題3</h3>
<p>短內容。</p>
</div>
<div class="item">
<img src="https://picsum.photos/300/400?random=4" alt="隨機圖片4" />
<h3>標題4</h3>
<p>更多內容測試,看看效果如何。</p>
</div>
<div class="item">
<img src="https://picsum.photos/300/250?random=5" alt="隨機圖片5" />
<h3>標題5</h3>
<p>適合博客、商品或圖片展示。</p>
</div>
<div class="item">
<img src="https://picsum.photos/300/300?random=6" alt="隨機圖片6" />
<h3>標題6</h3>
<p>內容隨意,卡片高度自然變化。</p>
</div>
</div>
<script>
const container = document.getElementById('grid');
const rowHeight = 12; // 要和 grid-auto-rows 保持一致
const gap = 12; // 要和 gap 保持一致
function resizeAllGridItems() {
const allItems = container.querySelectorAll('.item');
allItems.forEach(item => {
const height = item.getBoundingClientRect().height;
const rowSpan = Math.ceil((height + gap) / (rowHeight + gap));
item.style.setProperty('--row-span', rowSpan);
});
}
window.addEventListener('load', resizeAllGridItems);
window.addEventListener('resize', () => {
// 防抖優化
clearTimeout(window._resizeTimer);
window._resizeTimer = setTimeout(resizeAllGridItems, 100);
});
</script>
</body>
</html>
QA 環節(再深入一點)
Q1:如果內容動態變化,比如異步加載圖片怎麼辦?
A1:圖片加載完成後會影響元素高度,所以需要監聽圖片加載事件,或者在所有圖片加載完成後調用 resizeAllGridItems(),確保佈局正確。
Q2:支持 IE 瀏覽器嗎?
A2:CSS Grid 需要 IE11+ 支持,低版本瀏覽器不支持。grid-auto-flow: dense 在 IE 支持不好,可能有兼容問題。現代瀏覽器均支持良好。
Q3:能用 CSS Variables 做更復雜動畫嗎?
A3:可以利用 CSS 變量配合 JS 動態調整佈局,結合過渡動畫效果,做出更生動的瀑布流。
七、總結
本文分享了用 CSS Grid + 少量 JS 實現響應式瀑布流佈局的完整方案,重點在於:
- 用
grid-template-columns和grid-auto-rows定義基礎網格 - JS 動態計算每個元素高度對應跨幾行,實現靈活高度
grid-auto-flow: dense保證緊湊無縫佈局- 結合響應式設計,實現手機、平板、PC多端兼容
這套方案性能優、代碼簡潔、易維護,特別適合圖片牆、電商商品頁、內容卡片列表等常見場景。
你可以直接拿本文完整示例代碼試一試,按需調整參數和樣式,打造屬於自己的漂亮瀑布流頁面。