使用包装将具有不同宽度的任意数量的元素对齐到网格

时间:2017-12-06 14:35:36

标签: javascript css grid flexbox vuejs2

修改

我接受了以下@ user943702给出的答案。我需要稍微修改它以使用我的Vue实现,如下面的代码段所示。

const theElements = [{
  name: "ele1",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }, {
    name: 4
  }, {
    name: 5
  }]
}, {
  name: "ele2",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele3",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele4",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele5",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele6",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele7",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele8",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele9",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele10",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele11",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }, {
    name: 4
  }, {
    name: 5
  }]
}, {
  name: "ele12",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}];

new Vue({
  el: '#ele-grid',
  data: {
    elements: theElements
  },
  methods: {
    // find the first grid line excess {max}
    // return index; -1 means no overflow
    firstoverflowline: function(cols, max) {
      var sum = 0;
      for (var i = 0; i<cols.length; ++i) {
        sum += cols[i];
        if (sum >= max)
          return i;
      }
      return -1;
    },
    // compute max no of columns in grid
    // use by `grid-template-columns:repeat(<max>, max-content)`
    computegridlines: function(container) {
      var cols = getComputedStyle(container).gridTemplateColumns.split(/\s+/).map(parseFloat);
      var x = this.firstoverflowline(cols, parseFloat(getComputedStyle(container).width));
      if (x == -1) return;
      container.style.gridTemplateColumns = `repeat(${x}, max-content)`;
      this.computegridlines(container);
    },
    // polyfill `width:max-content`
    maxcontent: function(container) {
      var items = Array.from(container.children);
      for(var i = 0; i < items.length; i++) {
      	var item = items[i];
        item.style.display = "flex";
        item.style.flexFlow = "column";
        item.style.alignItems = "start";
        var max = Array.from(item.children).reduce(function(max,item) {
          var {left, right} = item.getBoundingClientRect();
          return Math.max(max, right - left);
        }, 0);
        item.style.width = `${max}px`;
      }
    },
    // flex-grid-ify a container
    flexgrid: function(container) {
      container.style.display = `grid`;
      container.style.gridTemplateColumns = `repeat(${container.children.length}, max-content)`;
      this.computegridlines(container);
      this.maxcontent(container);
    }
  },
  mounted: function() {
    var container = document.getElementById('ele-grid');
    var _this = this;
    this.flexgrid(container);
    window.onresize = function(e) { _this.flexgrid(container); }
  }
});
#ele-grid {
  width:100vw;
}

.ele-card {
  border: 1px solid black;
  background: cyan;
  margin: 5px 3px;
}
.ele-card .children {
  display: flex;
  flex-wrap: nowrap;
  padding: 5px;
}
.ele-card .child {
  margin: 0 5px;
  width: 30px;
  height: 30px;
  text-align: center;
  line-height: 30px;
  border: 1px solid black;
  background: magenta;
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.11/vue.min.js"></script>
<div id="ele-grid">
  <div class="ele-card" v-for="ele in elements" :key="ele.name">
      <div class="element">{{ele.name}}</div>
      <div class="children">
        <div class="child" v-for="child in ele.children" :key="child.name">{{child.name}}</div>
      </div>
  </div>
</div>

我有不同数量的元素可以有不同的宽度。我想在网格中对齐这些元素,使其左侧在每列中对齐。此外,我希望在窗口尺寸较小并保持网格时包装元素。我在下面的图片中嘲笑了我想要的东西。

enter image description here

enter image description here

我正在使用VueJS 2将元素填充到网格和CSS Flexbox中,以使用以下CSS组织元素。下面是它现在如何运作的示例片段:

const theElements = [{
  name: "ele1",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }, {
    name: 4
  }, {
    name: 5
  }]
}, {
  name: "ele2",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele3",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele4",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele5",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele6",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele7",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele8",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele9",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele10",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}, {
  name: "ele11",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }, {
    name: 4
  }, {
    name: 5
  }]
}, {
  name: "ele12",
  children: [{
    name: 1
  }, {
    name: 2
  }, {
    name: 3
  }]
}];

new Vue({
  el: '#ele-grid',
  data: {
    elements: theElements
  }
});
#ele-grid {
  display: flex;
  flex-wrap: wrap;
}
.ele-card {
  border: 1px solid black;
  background: cyan;
  margin: 5px 3px;
}
.ele-card .children {
  display: flex;
  flex-wrap: nowrap;
  padding: 5px;
}
.ele-card .child {
  margin: 0 5px;
  width: 30px;
  height: 30px;
  text-align: center;
  line-height: 30px;
  border: 1px solid black;
  background: magenta;
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.11/vue.min.js"></script>
<div id="ele-grid">
  <div class="ele-card" v-for="ele in elements" :key="ele.name">
    <div class="element">{{ele.name}}</div>
    <div class="children">
      <div class="child" v-for="child in ele.children" :key="child.name">{{child.name}}</div>
    </div>
  </div>
</div>

这几乎有效;每个元素都有自己的宽度,并在调整窗口大小时进行换行。但是,元素与网格对齐。

我也研究过使用CSS Grid,但看起来你必须要指定每个元素的宽度或列数,这两者都需要是任意的。

我对使用CSS或JavaScript的任何解决方案持开放态度(请不要使用JQuery)。我宁愿不包括第三方库,但如果它是唯一的选择,我会考虑它。

2 个答案:

答案 0 :(得分:6)

修改:

  • 正如@ user943702指出我们可以使用max-content属性来删除每列中的无关空格(不要将此属性与解释中的属性混淆,尽管这是每个元素的宽度值,这个是每列的基础)
  • 对于空间分布:有一个名为justify-content的便捷属性我选择将其设置为居中,除了其他值之外,您可以将其设置为:

    space-between; /* The first item is flush with the start,the last is flush with the end */
    space-around;  /* Items have a half-size space on either end */
    space-evenly;  /* Items have equal space around them */
    stretch;       /* Stretch 'auto'-sized items to fitthe container */
    

在进入剧本之前,有几点注意事项:

  • 您可以将其设置为仅使用css响应式更改:@media query每个宽度一个,然后使用它完成,但是单个元素也有任意宽度,所以我将使用JavaScript

编辑:这是一个使用CSS media query方法通知的脚本,您尝试将其自定义为不同的设备宽度越多,当单个元素宽度意外更改时,您抓到的风险就越大

const theElements = [{  name: "ele1",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }, {    name: 4  }, {    name: 5  }]}, {  name: "ele2",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele3",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele4",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele5",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele6",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele7",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele8",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele9",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele10",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele11",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }, {    name: 4  }, {    name: 5  }]}, {  name: "ele12",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}];

new Vue({
  el: '#ele-grid',
  data: {
    elements: theElements
  }
});
@media (min-width: 1020px) {
	#ele-grid {
		display:grid;
		grid-template-columns:repeat(5, 1fr); 	
        justify-content: center;
	}
}
@media (min-width:400px) and (max-width: 1020px) {
	#ele-grid {
		display:grid;
		grid-template-columns:max-content max-content max-content; 	
	}
}
@media (max-width: 400px) {
	#ele-grid {
		display:grid;
		grid-template-columns:max-content; 	
	}
}
.ele-card {
  margin: 5px 3px;
}
.ele-card .children {
  display: flex;
  flex-wrap: nowrap;
  padding: 5px;
}
.ele-card .child {
  margin: 0 5px;
  width: 30px;
  height: 30px;
  text-align: center;
  line-height: 30px;
  border: 1px solid black;
  background: magenta;
}
.wrapper{
	border: 1px solid black;
  background: cyan;
  display:inline-block;
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.28/vue.min.js"></script>

<div id="ele-grid">
  <div class="ele-card" v-for="ele in elements" :key="ele.name">
  	<div class="wrapper">
	    <div class="element">{{ele.name}}</div>
	    <div class="children">
	      <div class="child" v-for="child in ele.children" :key="child.name">{{child.name}}</div>
	    </div>
	  </div>
	</div>
</div>

  • 为了获得所需的宽度,有一个优秀的-moz-max-content属性,遗憾的是其它浏览器还不支持它,所以我添加了一个子包装器并使其成为display:inline-block预期行为
  • 我正在使用CSS grid layout,您可以使用css列或垂直flex代替,但元素将从上到下对齐,从而更改整个布局。

这是针对css的JavaScript:

  • 简而言之,这个脚本采用了最大列的布局(这里有10个你可以增加它),看看它是否适合而不滚动,如果没有减少。
  • 在此脚本中,元素使用resize事件进行响应。

const theElements = [{  name: "ele1",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }, {    name: 4  }, {    name: 5  }]}, {  name: "ele2",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele3",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele4",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele5",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele6",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele7",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele8",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele9",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele10",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}, {  name: "ele11",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }, {    name: 4  }, {    name: 5  }]}, {  name: "ele12",  children: [{    name: 1  }, {    name: 2  }, {    name: 3  }]}];

new Vue({
  el: '#ele-grid',
  data: {
    elements: theElements
  }
});
function resizeHandler(){
	colStart=10; 		// max number of columns to start with
	allCards= document.getElementsByClassName('wrapper');
	totalWidth=0;
	maxWidTab=[];
	for (i=colStart;i>0;i--){
		for(j=0;j<i; j++){									//initializing and resetting
			maxWidTab[j]=0;
		}
		for (j=0; j<allCards.length; j++){
			cellWidth=parseInt(getComputedStyle(allCards[j]).width);		//parseInt to remove the tailing px
			maxWidTab[j%i]<cellWidth?maxWidTab[j%i]=cellWidth:'nothing to be done';
		}
		for(j=0;j<i; j++){									//sum to see if fit
			totalWidth+=maxWidTab[j]+2+6		//borders and margins
		}
		if (totalWidth<innerWidth){
			grEl = document.getElementById("ele-grid");
			grEl.style.gridTemplateColumns="repeat("+i+", max-content)";
			/*console.log(i);*/
			break;
		}else{
				totalWidth=0;							//resetting
		}
	}
}
	window.addEventListener("resize",resizeHandler);
	document.addEventListener ("DOMContentLoaded",resizeHandler);
#ele-grid {
	display:grid;
    justify-content: center;
	grid-template-columns:repeat(10, max-content); 	/* starting by 10 columns*/
}
.ele-card {

  margin: 5px 3px;
}
.ele-card .children {
  display: flex;
  flex-wrap: nowrap;
  padding: 5px;
}
.ele-card .child {
  margin: 0 5px;
  width: 30px;
  height: 30px;
  text-align: center;
  line-height: 30px;
  border: 1px solid black;
  background: magenta;
}
.wrapper{
	border: 1px solid black;
  background: cyan;
  display:inline-block;
}

</style>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.28/vue.min.js"></script>

<div id="ele-grid">
  <div class="ele-card" v-for="ele in elements" :key="ele.name">
  	<div class="wrapper">
	    <div class="element">{{ele.name}}</div>
	    <div class="children">
	      <div class="child" v-for="child in ele.children" :key="child.name">{{child.name}}</div>
	    </div>
	  </div>
	</div>
</div>

答案 1 :(得分:2)

CSS grid-template-columns确实支持内容感知值max-content。唯一的问题是应该有多少列。

我编写了一个算法来探测最大列数。实现涉及JS并要求浏览器支持CSS Grid。可以找到演示here。 (我使用Pug创建与您相同的源结构,样式也与您的相同,以便我们可以专注于JS面板,实现)。

在演示中,更改视口大小将重新生成网格项。您可以通过调用flexgrid(container)手动触发其他有趣时刻的重新流动,例如异步加载项目然后重新流动。只要源结构保持不变,就允许更改项的维度属性。

这是算法

Step1)将容器设置为网格格式上下文,将所有网格项布局在一行中,将每列宽度设置为max-content

|---container---|
|aaaaa|bbb|ccc|ddd|eee|fff|ggggg|hhh|iii|

Step2)找到第一个溢出网格线

|---container---|
|aaaaa|bbb|ccc|ddd|eee|fff|ggggg|hhh|iii|
                  ^overflowed

Step3)在我们的例子中将grid-template-columns减少到3。 由于grid-row默认为auto,因此CSS引擎会布局网格项 在超出最后一列网格线的下一行。我叫了这个 “包装”。此外,网格项目由于grid-template-columns:max-content自动扩展(例如“ddd”扩展到第一列最宽内容的长度)

|---container---|
|aaaaa|bbb|ccc|
|ddd  |eee|fff|
|ggggg|hhh|iii|

由于所有列网格线都位于“内部”容器中,我们已经完成了。在某些情况下,在“包装”之后引入新的溢出网格线,我们需要重复步骤2和3,直到所有网格线都位于“内部”容器中,例如,

#layout in one row
|---container---|
|aaaaa|bbb|ccc|ddd|eee|fff|ggggggg|hhhhh|iii|

#find the first overflowed grid line
|---container---|
|aaaaa|bbb|ccc|ddd|eee|fff|ggggggg|hhhhh|iii|
                  ^overflowed

#reduce `grid-template-columns`
|---container---|
|aaaaa  |bbb  |ccc|
|ddd    |eee  |fff|
|ggggggg|hhhhh|iii|

#find the first overflowed grid line
|---container---|
|aaaaa  |bbb  |ccc|
|ddd    |eee  |fff|
|ggggggg|hhhhh|iii|
                  ^overflowed

#reduce `grid-template-columns`
|---container---|
|aaaaa  |bbb  |
|ccc    |ddd  |
|eee    |fff  |
|ggggggg|hhhhh|
|iii    |

#find the first overflowed grid line
#None, done.