Stories

Detail Return Return

一步步教你用 CSS Grid 實現靈活又高效的瀑布流佈局,適配所有屏幕! - Stories Detail

在這裏插入圖片描述

摘要

隨着網頁設計越來越複雜,尤其是圖片牆、商品展示、內容卡片這類頁面,瀑布流佈局(也叫 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 能正確渲染布局
  • 綁定到 loadresize 事件,動態響應不同屏幕尺寸和內容變更

實際場景舉例

圖片牆

<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-columnsgrid-auto-rows 定義基礎網格
  • JS 動態計算每個元素高度對應跨幾行,實現靈活高度
  • grid-auto-flow: dense 保證緊湊無縫佈局
  • 結合響應式設計,實現手機、平板、PC多端兼容

這套方案性能優、代碼簡潔、易維護,特別適合圖片牆、電商商品頁、內容卡片列表等常見場景。

你可以直接拿本文完整示例代碼試一試,按需調整參數和樣式,打造屬於自己的漂亮瀑布流頁面。

user avatar incerry Avatar user_2dx56kla Avatar
Favorites 2 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.