如何实现同等间隙的卡片布局
在列表展示中,经常会使用卡片的内容展示形式,为了美观,常常要求各卡片间的间隙是一致的。
卡片内容不一样可能高度不等,但一般来说为了整体的一致性,会限制每个卡片的宽高都相等。
本文就基于宽高一致的多个卡片,在不同屏幕大小下,每行卡片数量可能有调整,考量如何实现等间隙的布局。
放置一张张卡片项,为了设置间距,最常见的就是直接使用一个特定的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 }