EDIT
I've accepted the answer given by @user943702 below. I needed to modify it slightly to work with my Vue implementation as shown in the snippet below.
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;
},
// pute max no of columns in grid
// use by `grid-template-columns:repeat(<max>, max-content)`
putegridlines: 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)`;
thisputegridlines(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)`;
thisputegridlines(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=".3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src=".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>
EDIT
I've accepted the answer given by @user943702 below. I needed to modify it slightly to work with my Vue implementation as shown in the snippet below.
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;
},
// pute max no of columns in grid
// use by `grid-template-columns:repeat(<max>, max-content)`
putegridlines: 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.putegridlines(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.putegridlines(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./bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare./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>
I have an unknown number of elements that can have different widths. I want to align these elements in a grid so that their left sides line up in each column. Additionally, I want the elements to wrap when the window is sized smaller and maintain the grid. I mocked up what I want in the images below.
I am using VueJS 2 to populate the elements into the grid and CSS Flexbox to organize the elements using the following CSS. Below is an example snippet of how it functions now:
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./bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare./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>
This almost works; the elements each have their own width and wrap when the window is resized. However, the elements do not align to a grid.
I've also looked into using CSS Grid, but it looks like you either have to specify the width of each element or the number of columns, both of which I need to be arbitrary.
I'm open to any solution using CSS or JavaScript (not JQuery please). I'd prefer to not include a 3rd party library but will consider it if it's the only option.
Share Improve this question edited Dec 19, 2017 at 13:45 sorayadragon asked Dec 6, 2017 at 14:35 sorayadragonsorayadragon 1,0877 silver badges21 bronze badges 3- 2 I delete my answer since that won't solve your question. And using script will be a non-trivial solution. If you check these 4 fiddle, which start with a none fixed alignment, then manually aligned with margins, one-by-one fiddle adds a margin to set up the alignment, you'll see when fixing the 4th, it breaks all and one need to run through the items, over and over, until finally get them aligned ... jsfiddle/ejubqfxf ... jsfiddle/ejubqfxf/1 ... jsfiddle/ejubqfxf/2 ... jsfiddle/ejubqfxf/3 – Asons Commented Dec 6, 2017 at 17:16
- And I used a fixed width for these demo so we both see the same wrapping – Asons Commented Dec 6, 2017 at 17:23
- I found this when looking for something else ... stackoverflow./questions/24194537/… ... it might help you – Asons Commented Dec 8, 2017 at 14:18
2 Answers
Reset to default 6Edit:
- As @user943702 has pointed out we can make use of
max-content
property, to remove the extraneous spaces in each column (do not confuse this property with that ing in the explanation though which is a widths value per element basis, and this one is per column basis) For space distribution : there is a handy property called
justify-content
I've chosen to set it to center, among other values, you can set it to :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 */
Before getting to the script, there are a couple of notes :
- You can set it to responsively changing using only css by:
@media query
one for each width and be done with it however the individual elements have an arbitrary width too so I'm gonna use JavaScript
Edit: Here is a script using the CSS media query
method notice that the more you try to customize it to different device widths the more you risk to be caught when individual elements width changes unexpectedly.
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./bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare./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>
- To get the width as needed there is an excellent -moz-max-content property unfortunately it is not supported yet by the other browsers, so I've appended a child wrapper and make it
display:inline-block
which have the intended behavior - I'm using
CSS grid layout
and you can use css columns or verticalflex
s instead but the elements would be aligned from top to bottom changing the whole layout.
That was for the css, for the JavaScript:
- In a nutshell this scripts takes a layout with max columns (here 10 you can increase it), and see if it fits without scrolling, if not decrements.
- In this script, elements are responsive using a the resize event.
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./bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare./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>
CSS grid-template-columns
does support content-aware value which is max-content
. The only question is that how many columns should be there.
I write an algorithm to probe maximum number of column. The implementation involves JS and requires browser to support CSS Grid. Demo can be found here. (I use Pug to create same source structure as yours and styling is also same as yours so that we can focus on JS panel, the implementation).
In demo, changing viewport size will re-flow grid items. You may trigger re-flow at other interesting moments manually by calling flexgrid(container)
, e.g. loading items asynchronously then re-flow. Changing dimension properties of items is allowed as long as source structure keeps unchanged.
Here's the algorithm
Step1) Set container as grid formatting context, layout all grid items in one row, set each column width to max-content
|---container---|
|aaaaa|bbb|ccc|ddd|eee|fff|ggggg|hhh|iii|
Step2) find first overflow grid line
|---container---|
|aaaaa|bbb|ccc|ddd|eee|fff|ggggg|hhh|iii|
^overflowed
Step3) reduce grid-template-columns
to, in our case, 3.
Since grid-row
default to auto
, CSS engine layouts a grid item
on next row when it goes beyond last column grid line. I called this
"wrapping". In addition, grid items are auto expanded due to grid-template-columns:max-content
(e.g. "ddd" is expanded to the length of widest content of first column)
|---container---|
|aaaaa|bbb|ccc|
|ddd |eee|fff|
|ggggg|hhh|iii|
Since all column grid lines sit "inside" container, we have done. In some cases, a new overflowed grid line is being introduced after "wrapping", we need to repeat step2&3 until all grid lines sit "inside" container, e.g.
#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.
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745395039a4625839.html
评论列表(0条)