如何實現同等間隙的卡片佈局
在列表展示中,經常會使用卡片的內容展示形式,為了美觀,常常要求各卡片間的間隙是一致的。
卡片內容不一樣可能高度不等,但一般來說為了整體的一致性,會限制每個卡片的寬高都相等。
本文就基於寬高一致的多個卡片,在不同螢幕大小下,每行卡片數量可能有調整,考量如何實現等間隙的佈局。
放置一張張卡片項,為了設定間距,最常見的就是直接使用一個特定的margin值了,這種方式雖然可以(通過精確計算後確實也可以)
直接設定一個間距,比如統一 margin-left 和 margin-bottom都為 20px ,並不能保證每行最後一個卡片之後的間距是20px
關於如何定這個 margin的值,需要通過一個規則來計算,這個後文再說明
設定同等間距,常用的還有 flex佈局中的 justify-content: space-between,可以定義各子專案以相同間距佈局,但不好處理左右子專案與邊框的間距。 space-around這個就更用不得了,會使得左右子專案右margin == 左margin * 2
所以最終還是回到使用margin值來設定,通過一個可用的規則,來保證間距是一致的。
先把基本結構搭上
<div class="container"> <h2>專案列表</h2> <ul class="proj-items"></ul> </div> <!-- 模板結構 --> <script type="text/template" id="proj-item-tpl"> <li class="proj-item"> <a href="#/p/{{projectID}}"> <h3 class="proj-item__title">{{projectName}}</h3> <p class="proj-item__author">{{author}}</p> </a> </li> </script>
JS生成N個專案
function addEvent(elem, type, handler) { elem.addEventListener(type, handler, false); } function qs(selector) { return document.querySelector(selector); } function qsa(selectors) { return document.querySelectorAll(selectors); } var mockData = (function(num) { var data = []; for (var i = 1; i <= num; ++i) { data.push({ projectID: i, projectName: '專案' + i, author: '張大大' }); } return data; })(8); var itemTpl = qs('#proj-item-tpl').innerHTML; var itemsDOM = qs('.proj-items'); /** * 渲染資料 * @param {[type]} data [description] * @return {[type]} [description] */ function renderList(data) { var html = ''; var fragment = document.createDocumentFragment(); data.forEach(function(item) { var divTemp = document.createElement('div'); // 模板替換 divTemp.innerHTML = itemTpl.replace(/{{(\w+)}}/g, function(input, match) { return match ? item[match] || '' : ''; }); fragment.appendChild(divTemp.firstElementChild); }); // 渲染 itemsDOM.appendChild(fragment); } renderList(mockData);
把基礎樣式放上,這裡我們先指定一個特定的itemMargin值為20px
$itemMargin: 20px; $itemWidth: 130px; $itemHeight: 150px; .container { margin: 20px auto; width: 450px; background-color: #f2f2f2; color: #666; h2 { margin: 20px; padding-top: 20px; font-size: 20px; } } .proj-items { display: flex; flex-wrap: wrap; /* justify-content: space-between; */ padding: 0; list-style: none; &:after { content: ""; display: block; flex-grow: 99999; } } .proj-item { margin-left: $itemMargin; margin-bottom: $itemMargin; width: $itemWidth; height: $itemHeight; background-color: #fff; border-radius: 3px; text-align: center; &:hover { box-shadow: 0 0 20px #ddd; } a { display: block; padding: 15px; height: 100%; color: #666; text-decoration: none; } &__title { margin-top: 0; font-size: 16px; } &__author { font-size: 12px; } }
可以看到,每行最後一個間距不一致了,所以不能簡單的寫個margin值
再來看看設定 space-between的時候
.proj-items { justify-content: space-between; ... } .proj-item { /* margin-left: $itemMargin; */ margin-bottom: $itemMargin; ... }
看來並不夠強大
如果看得仔細,應該能看到專案7和8是挨在一起的,為何沒有間距呢
其一是因為沒有margin-left值,其二是在專案列表後放了一個坑來佔位,防止最後一行專案過少時 space-between的值太大了
把這個撤掉看看這個影響
&:after { content: ""; display: block; flex-grow: 99999; }
還是把目光投向margin值的設定規則吧
在設計一個頁面佈局時,至少已經確定了XX頁面大小的情況下,容器寬度應該設定為多少(比如為1200px),每行放n個專案,專案的寬高是多少
有了這些指標(也必須有這些指標),我們就可以用來計算margin值了
containerWidth == n * itemWidth + (n + 1) * itemMargin
得出
itemMargin = (containerWidth - n * itemWidth) / (n + 1)
代入這裡的情況,containerWidth 450px,itemWidth 130px,每行 3個,即可得出 itemMargin 正好為 15px
有了某種特定情況下的佈局規則之後,接下來還要考慮不同螢幕大小的情況下,怎麼調整這個margin值
這個需要結合媒體查詢來設定,同時相應的計算規則也可以通過scss來處理
第一種情況是每行3個,n只可能為整數,即可推算出需要處理的臨界值為1 2 3 4 5 6 ... 這些整數值
加入n為4,如果要保證 itemMargin值15px在各種情況下都相等,計算可得 容器寬度containerWidth值 為 595px
同理求得 n是5時為 740px ,n是2時為 305px
當然,如果覺得這個containerWidth值不太好看,也可以自己定義,比如 n是4的時候設定為 600px,代入公式那麼 itemMargin值為16px。
為了保證各種請下間距都相等,我個人就不推薦這麼幹了
通過上述的規則計算,我們可以得出每行專案數量遞增時的容器寬度臨界值。把這些臨界值放在媒體查詢裡面配置,即可方便地實現這種佈局的自適應。
/* 這兩個為初始就確定的基準值 */ $containerWidth: 305px; $itemMargin: 15px; $itemWidth: 130px; $itemHeight: 150px; /* 每行專案數量為itemNum時的容器寬度 */ @function getContainerWidth($itemNum) { @return $itemNum * $itemWidth + ($itemNum + 1) * $itemMargin; } /* 配置各個頁面寬度下的容器寬度(應用) */ @mixin adjustContainerWidth( $from: 2, $to: 5 ) { @for $i from $from through $to { $minWidth: getContainerWidth($i); $maxWidth: getContainerWidth($i + 1); @media only screen and (min-width: $minWidth) and (max-width: $maxWidth) { .container { width: $minWidth; } } } } .container { margin: 20px auto; width: $containerWidth; background-color: #f2f2f2; color: #666; h2 { margin: 20px; padding-top: 20px; font-size: 20px; } } @include adjustContainerWidth( $from: 1, $to: 7 );
即可實現各個頁面大小下的自適應效果
完整的CSS部分
1 /* 這兩個為初始就確定的基準值 */ 2 $containerWidth: 305px; 3 $itemMargin: 15px; 4 5 $itemWidth: 130px; 6 $itemHeight: 150px; 7 8 /* 每行專案數量為itemNum時的容器寬度 */ 9 @function getContainerWidth($itemNum) { 10 @return $itemNum * $itemWidth + ($itemNum + 1) * $itemMargin; 11 } 12 13 /* 配置各個頁面寬度下的容器寬度(應用) */ 14 @mixin adjustContainerWidth( 15 $from: 2, 16 $to: 5 17 ) { 18 @for $i from $from through $to { 19 $minWidth: getContainerWidth($i); 20 $maxWidth: getContainerWidth($i + 1); 21 22 @media only screen and (min-width: $minWidth) and (max-width: $maxWidth) { 23 .container { 24 width: $minWidth; 25 } 26 } 27 } 28 } 29 30 .container { 31 margin: 20px auto; 32 width: $containerWidth; 33 background-color: #f2f2f2; 34 color: #666; 35 36 h2 { 37 margin: 20px; 38 padding-top: 20px; 39 font-size: 20px; 40 } 41 } 42 43 @include adjustContainerWidth( 44 $from: 1, 45 $to: 7 46 ); 47 48 .proj-items { 49 display: flex; 50 flex-wrap: wrap; 51 padding: 0; 52 list-style: none; 53 } 54 55 .proj-item { 56 margin-left: $itemMargin; 57 margin-bottom: $itemMargin; 58 width: $itemWidth; 59 height: $itemHeight; 60 background-color: #fff; 61 border-radius: 3px; 62 text-align: center; 63 64 &:hover { 65 box-shadow: 0 0 20px #ddd; 66 } 67 68 a { 69 display: block; 70 padding: 15px; 71 height: 100%; 72 color: #666; 73 text-decoration: none; 74 } 75 76 &__title { 77 margin-top: 0; 78 font-size: 16px; 79 } 80 81 &__author { 82 font-size: 12px; 83 } 84 }View Code