[譯] 使用 CSS 柵格和 Flexbox 打造 Trello 佈局

磊仔發表於2017-09-14

通過本教程,我將帶你完成 Trello 看板 (檢視示例)的基本佈局。這是一個響應式的、純 CSS 的解決方案,並且我們將只開釋出局的結構特性。

這是一個 CodePen demo,可預覽一下最終結果。

除了柵格佈局Flexbox,這個方案還採用了 calc檢視單位。我們也將利用 Sass 變數,讓程式碼更可讀和高效。

不提供向下相容,所以請確保在支援的瀏覽器上執行。一切就緒,就讓我們開始一步一步開發看板元件吧。

螢幕佈局

一個 Trello 看板由一個 app 欄、一個 board 欄和一個包含卡片列表的部分組成。我使用以下標籤骨架搭建出這一結構:

<div class="ui">
  <nav class="navbar app">...</nav>
  <nav class="navbar board">...</nav>
  <div class="lists">
    <div class="list">
      <header>...</header>
      <ul>
        <li>...</li>
        ...
        <li>...</li>
      </ul>
      <footer>...</footer>
    </div>
  </div>
</div>複製程式碼

這個佈局將通過 CSS 柵格實現。確切地說是 3×1 柵格(就是指一列三行)。第一行用於 app 欄,第二行用於 board 欄,第三行用於 .lists 元素。

前兩行各自有一個固定的高度,而第三行將撐起可變視窗高度的其餘部分:

.ui {
  height: 100vh;
  display: grid;
  grid-template-rows: $appbar-height $navbar-height 1fr;
}複製程式碼

檢視單位可以確保 .ui 容器總是和瀏覽器的視窗高度一致。

一個柵格化的上下文被分配給容器,並且指定了上文說的行和列。確切地說,是隻指定了行,因為宣告單獨的列是沒有必要的。一對 Sass 變數指定了兩個欄目的高度,使用 fr 單位指定 .lists 元素高度使其撐起可變視窗高度的其餘部分,這樣每行的大小就設定完成了。

卡片列表部分

如上所述,螢幕柵格的第三行託管著卡片列表的容器。這是標籤的輪廓:

<div class="lists">
  <div class="list">
    ...
  </div>
  ...
  <div class="list">
    ...
  </div>
</div>複製程式碼

我用一個滿屏寬的 Flexbox 單行行容器來格式化列表:

.lists {
  display: flex;
  overflow-x: auto;
  > * {
    flex: 0 0 auto; // 'rigid' lists
    margin-left: $gap;
  }
  &::after {
    content: '';
    flex: 0 0 $gap;
  }
}複製程式碼

overflow-x 指定 auto 值,當列表不適合視口提供的寬度時,瀏覽器會在螢幕底部顯示一個水平滾動條。

flex 簡寫屬性用於 flex item 使列表更嚴格。flex-basis (簡寫的方式使用)的 auto 值指示佈局引擎從 .list 元素的寬度屬性取值,flex-growflex-shrink 的 0 值可以防止寬度的改變。

接下來我將在列表之間新增一個水平分隔。如果給列表設定右間距,當水平溢位時看板上最後一個列表之後的間距不會被渲染。為了解決這個問題,列表被一個左間距分隔並且最後一個列表和視窗右邊緣的間距通過給每個 .lists 元素新增一個偽元素 ::after 來實現。預設值 flex-shrink: 1 一定要被重寫,否則這個偽元素會”吸收“所有的負空間,然後消失。

注意在 Firefox < 54 的版本上要給 .lists 指定 width: 100% 以確保正確的佈局渲染。

卡片列表

每個卡片列表由一個 header 欄、一個卡片序列和一個 footer 欄目組成。以下 HTML 程式碼段實現了這一結構:

<div class="list">
  <header>List header</header>
  <ul>
    <li>...</li>
    ...
    <li>...</li>
  </ul>
  <footer>Add a card...</footer>
</div>複製程式碼

這裡的關鍵任務是如何管理列表的高度。header 和 footer 有固定的高度(未必相等)。然後有一些不定數量的卡片,每個卡片都有不定量的內容。因此隨著卡片的新增和移除,這個列表也會增大和縮小。

但是高度不能無限增大,它需要有一個取決於 .lists 元素高度的上限。一旦突破上線,我想有一個垂直滾動條出現來允許訪問溢位列表的卡片。

這聽起來是 max-heightoverflow 屬效能做的。但如果根容器 .list 提供了這些屬性,一旦列表達到了它的最大高度,所有的 .list 元素包括 header 和 footer 在內都會出現滾動條。下圖左右兩邊分別顯示錯誤的和正確的側邊條:

因此,讓我們把 max-height 約束給內部的 <ul>。應該提供什麼值呢?header 和 footer 的高度必須從列表父容器(.lists)的高度之中扣除:

ul {
  max-height: calc(100% - #{$list-header-height} - #{$list-footer-height});
}複製程式碼

但還有一個問題。百分比數值並不參照 .lists 而是參照 <ul> 元素的父元素 .list,並且這個元素沒有定義高度,因此這個百分比不能確定。這個問題可以通過設定 .list.lists 同樣高度來解決:

.list {
  height: 100%;
}複製程式碼

這樣,既然 .list.lists 總是一樣高,它的 background-color 屬性不能用於列表背景色,但可以使用它的子元素(header, footer 和卡片)來實現這一目的。

最後一個 list 高度的調整很有必要,可用來計算列表底部和視窗底部的一點空間($gap)。

.list {
  height: calc(100% - #{$gap} - #{$scrollbar-thickness});
}複製程式碼

還有一個 $scrollbar-thickness 需要被減去,防止列表觸及 .list 元素的水平滾動條。 事實上這個滾動條”增長“在 .lists 盒子內部。也就是說,100% 這個值是指包括滾動條在內的 .lists 的高度。

而在火狐中,這個滾動條被”附加“給 .lists 高度的外部,就是說 .lists 高度的 100% 並不包含滾動條。所以這個減法就沒什麼必要了。結果是當滾動條可見時,在火狐中已經觸及最大高度的底部邊框和滾動條的頂部之間的可視空間會稍大一些。

這是這個元件相應的 CSS 規則:

.list {
  width: $list-width;
  height: calc(100% - #{$gap} - #{$scrollbar-thickness});

  > * {
    background-color: $list-bg-color;
    color: #333;
    padding: 0 $gap;
  }

  header {
    line-height: $list-header-height;
    font-size: 16px;
    font-weight: bold;
    border-top-left-radius: $list-border-radius;
    border-top-right-radius: $list-border-radius;
  }

  footer {
    line-height: $list-footer-height;
    border-bottom-left-radius: $list-border-radius;
    border-bottom-right-radius: $list-border-radius;
    color: #888;
  }

  ul {
    list-style: none;
    margin: 0;
    max-height: calc(100% - #{$list-header-height} - #{$list-footer-height});
    overflow-y: auto;
  }
}複製程式碼

如上所述,列表背景色通過給每一個 .list 元素的子元素的 background-color 屬性指定 $list-bg-color 值而被渲染。overflow-y 使得卡片滾動條只有按需顯示。最後,給 header 和 footer 新增一些簡單的樣式。

完成收尾

單個卡片包含的一個列表元素 HTML:

<li>Lorem ipsum dolor sit amet, consectetur adipiscing elit</li>複製程式碼

卡片也有可能包含一個封面圖片:

<li>
  <img src="..." alt="...">
  Lorem ipsum dolor sit amet
</li>複製程式碼

這是相應的樣式:

li {
  background-color: #fff;
  padding: $gap;

  &:not(:last-child) {
    margin-bottom: $gap;
  }

  border-radius: $card-border-radius;
  box-shadow: 0 1px 1px rgba(0,0,0, 0.1);

  img {
    display: block;
    width: calc(100% + 2 * #{$gap});
    margin: -$gap 0 $gap (-$gap);
    border-top-left-radius: $card-border-radius;
    border-top-right-radius: $card-border-radius;
  }
}複製程式碼

設定完一個背景、填充、和底部間距就差背景圖片的佈局了。這個圖片寬度一定是跨越整個卡片的,從左填充的邊緣到右填充的邊緣:

width: calc(100% + 2 * #{$gap});複製程式碼

然後,指定負邊距以使圖片水平和垂直對齊:

margin: -$gap 0 $gap (-$gap);複製程式碼

第三個正邊距的值用於指定封面圖片和文字之間的空間。

最後我給佔據螢幕佈局第一行的兩條新增了一個 flex 格式化上下文,但它們只是草圖。通過擴充套件 demo 自由構建你自己的實現吧。

總結

這只是實現這種設計的一種可行方法,如果能看見其他方式那一定很有趣。此外,如果能完成整個佈局那就更好了,比如完成最後的兩個欄目。

另一個潛在的改進是能夠為卡片列表實現自定義的滾動條。

所以,fork 這個 demo 盡情發揮吧,記得在下面的討論區留下你的連結哦。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章